app
Pup - the AI's own browser
Public Made by Adomby adom
pup gives your AI a real browser on your desktop that it drives completely, in the background, signed in as you: windows that never jump in front of your work, one shared profile that learns your logins, and every window labeled on the taskbar with its AI thread's name.
master
John Lauer
2.0.413: missing verbs + behaviors (assisted_click, import, fg-watch, url lint, notices, chrome-stable, eval frame, mov…
0dd55ee
2mo ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
//! URL lint (Node: urlLint): the three ways an AI in a container hands pup a URL that cannot work on
//! the user's desktop: a container file path, localhost, or an owner-less wiki path. Returned as
//! `_urlLint` + `urlLintCode` on open/navigate responses; never blocks the call.
use serde_json::{json, Value};
pub fn url_lint(u: &str, caller_container: &str) -> Option<Value> {
if u.is_empty() { return None; }
let low = u.to_ascii_lowercase();
if low.starts_with("file:") {
let fpath = u.splitn(2, "file://").nth(1).map(|p| p.trim_start_matches('/').to_string()).unwrap_or_default();
let fpath = percent_decode(&fpath);
let containerish = ["home/", "root/", "workspace/", "tmp/", "usr/", "var/", "opt/", "mnt/"].iter().any(|p| fpath.to_ascii_lowercase().starts_with(p));
if containerish {
return Some(json!({ "code": "url_file_container_path", "message": format!("CONTAINER PATH HANDED TO PUP. You passed \"{u}\", but pup drives Chrome on the USER'S DESKTOP (Windows/macOS): a file:// URL resolves against THEIR filesystem, not your container's. \"/{fpath}\" is a container path, so it can never exist there and this open is guaranteed to render Chrome's ERR_FILE_NOT_FOUND page. Do ONE of these instead: (1) SERVE it and open the public proxy URL (run `python3 -m http.server <port>` in that directory and open https://<your-container>.adom.cloud/proxy/<port>/<file>); or (2) PUSH it to the user's machine first with adom-bridge push_file, then open the file:// path it actually landed at on THEIR disk.") }));
}
let win = fpath.replace('/', "\\");
if !win.is_empty() && !std::path::Path::new(&win).exists() {
return Some(json!({ "code": "url_file_not_found", "message": format!("FILE DOES NOT EXIST ON THE USER'S MACHINE. pup pre-flighted \"{win}\" on the desktop before opening it and there is no such file, so this would have rendered Chrome's ERR_FILE_NOT_FOUND page. Check the path, or push the file to the desktop first with adom-bridge push_file and open the path it reports back. (pup's Chrome runs on the USER'S computer, so file:// is THEIR filesystem, not your container's.)") }));
}
return None;
}
let rest = u.splitn(2, "://").nth(1)?;
let hostport = rest.split(['/', '?', '#']).next().unwrap_or("");
let (host, port) = match hostport.rsplit_once(':') { Some((h, p)) if p.chars().all(|c| c.is_ascii_digit()) => (h.to_string(), p.to_string()), _ => (hostport.to_string(), String::new()) };
let host_l = host.to_ascii_lowercase();
if ["localhost", "127.0.0.1", "0.0.0.0", "[::1]", "::1"].contains(&host_l.as_str()) {
let path = rest.get(hostport.len()..).unwrap_or("").trim_start_matches('/');
let slug = if caller_container.is_empty() { "<your-container-slug>".to_string() } else { caller_container.to_ascii_lowercase().chars().map(|c| if c.is_ascii_alphanumeric() || c == '-' { c } else { '-' }).collect() };
let p = if port.is_empty() { "<port>".to_string() } else { port.clone() };
return Some(json!({ "code": "url_localhost", "message": format!("WRONG HOST FOR PUP. You passed \"{host}{}\", but pup drives a browser on the USER'S DESKTOP, not inside your container, so \"{host}\" resolves on THEIR machine and will hit whatever (if anything) is listening there, not your dev server. To show a service running INSIDE your container, use its public proxy URL: https://{slug}-<suffix>.adom.cloud/proxy/{p}/{path} (<suffix> is the 10-12 char id assigned to your container; `adom-cli container list` or your existing container URL has it). If you genuinely meant a server on the USER'S machine, say so explicitly, because that is almost never what an AI in a container intends.", if port.is_empty() { String::new() } else { format!(":{port}") }) }));
}
if host_l == "wiki.adom.inc" || host_l.ends_with(".wiki.adom.inc") {
let path = rest.get(hostport.len()..).unwrap_or("");
let path_only = path.split(['?', '#']).next().unwrap_or("");
let segs: Vec<&str> = path_only.split('/').filter(|s| !s.is_empty()).collect();
if segs.is_empty() { return None; }
const ROUTES: [&str; 14] = ["apps", "skills", "components", "bootstraps", "discover", "login", "search", "api", "download", "static", "hero", "settings", "new", "readme"];
if ROUTES.contains(&segs[0].to_ascii_lowercase().as_str()) { return None; }
let owner_missing = segs.len() == 1 || segs[1].eq_ignore_ascii_case("blob");
if owner_missing {
let query = path.get(path_only.len()..).unwrap_or("");
let fixed = format!("https://{host}/adom/{}{}{query}", segs[0], if segs.len() > 1 { format!("/{}", segs[1..].join("/")) } else { String::new() });
return Some(json!({ "code": "url_wiki_missing_owner", "message": format!("MALFORMED WIKI URL. wiki.adom.inc pages are OWNER-QUALIFIED (https://wiki.adom.inc/<owner>/<slug>, and /<owner>/<slug>/blob/<path> for a file) and yours has no owner segment, so it will 404 no matter who is logged in. Most pages are owned by \"adom\", so you probably want: {fixed} . Confirm the owner with `adom-wiki search` if unsure.") }));
}
}
None
}
fn percent_decode(s: &str) -> String {
let b = s.as_bytes(); let mut out = Vec::with_capacity(b.len()); let mut i = 0;
while i < b.len() { if b[i] == b'%' && i + 2 < b.len() { if let Ok(v) = u8::from_str_radix(&s[i + 1..i + 3], 16) { out.push(v); i += 3; continue; } } out.push(b[i]); i += 1; }
String::from_utf8_lossy(&out).to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test] fn container_path() { assert_eq!(url_lint("file:///home/adom/x.html", "").unwrap()["code"], "url_file_container_path"); }
#[test] fn localhost() { let l = url_lint("http://localhost:5173/app?x=1", "my-box").unwrap(); assert_eq!(l["code"], "url_localhost"); assert!(l["message"].as_str().unwrap().contains("my-box-<suffix>.adom.cloud/proxy/5173/app?x=1")); }
#[test] fn wiki_owner() { assert_eq!(url_lint("https://wiki.adom.inc/pup-bridge", "").unwrap()["code"], "url_wiki_missing_owner"); assert!(url_lint("https://wiki.adom.inc/adom/pup-bridge", "").is_none()); assert!(url_lint("https://wiki.adom.inc/apps/x", "").is_none()); }
#[test] fn fine() { assert!(url_lint("https://example.com/", "").is_none()); }
}