@todoforai/edge 0.13.11 → 0.13.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +78 -100
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -47927,7 +47927,6 @@ function credentialsPath() {
|
|
|
47927
47927
|
return path.join(xdg, "todoforai", "credentials.json");
|
|
47928
47928
|
}
|
|
47929
47929
|
var CREDENTIALS_PATH = credentialsPath();
|
|
47930
|
-
var LEGACY_CREDENTIALS_PATH = path.join(os.homedir(), ".todoforai", "credentials.json");
|
|
47931
47930
|
function readFileMap(p) {
|
|
47932
47931
|
try {
|
|
47933
47932
|
return JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
@@ -47935,9 +47934,6 @@ function readFileMap(p) {
|
|
|
47935
47934
|
return {};
|
|
47936
47935
|
}
|
|
47937
47936
|
}
|
|
47938
|
-
function readCredentials() {
|
|
47939
|
-
return { ...readFileMap(LEGACY_CREDENTIALS_PATH), ...readFileMap(CREDENTIALS_PATH) };
|
|
47940
|
-
}
|
|
47941
47937
|
function writeNewFile(creds) {
|
|
47942
47938
|
const dir = path.dirname(CREDENTIALS_PATH);
|
|
47943
47939
|
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
@@ -47947,7 +47943,7 @@ function writeNewFile(creds) {
|
|
|
47947
47943
|
} catch {}
|
|
47948
47944
|
}
|
|
47949
47945
|
function loadSavedApiKey(apiUrl) {
|
|
47950
|
-
return
|
|
47946
|
+
return readFileMap(CREDENTIALS_PATH)[apiUrl] || null;
|
|
47951
47947
|
}
|
|
47952
47948
|
function saveApiKey(apiUrl, apiKey) {
|
|
47953
47949
|
const creds = readFileMap(CREDENTIALS_PATH);
|
|
@@ -47960,13 +47956,6 @@ function clearApiKey(apiUrl) {
|
|
|
47960
47956
|
delete creds[apiUrl];
|
|
47961
47957
|
writeNewFile(creds);
|
|
47962
47958
|
}
|
|
47963
|
-
const legacy = readFileMap(LEGACY_CREDENTIALS_PATH);
|
|
47964
|
-
if (apiUrl in legacy) {
|
|
47965
|
-
delete legacy[apiUrl];
|
|
47966
|
-
try {
|
|
47967
|
-
fs.writeFileSync(LEGACY_CREDENTIALS_PATH, JSON.stringify(legacy, null, 2), { mode: 384 });
|
|
47968
|
-
} catch {}
|
|
47969
|
-
}
|
|
47970
47959
|
}
|
|
47971
47960
|
|
|
47972
47961
|
// node_modules/ws/wrapper.mjs
|
|
@@ -48488,12 +48477,14 @@ var BRIDGE_PORT = parseInt(process.env.TODOFORAI_BROWSER_BRIDGE_PORT || "43127",
|
|
|
48488
48477
|
var BRIDGE_EDGE_ID = "local-browser-bridge";
|
|
48489
48478
|
var REQUEST_TIMEOUT_MS = 30000;
|
|
48490
48479
|
var isOpen = (ws) => !!ws && ws.readyState === import_websocket.default.OPEN;
|
|
48480
|
+
var describeExt = (ext) => ext ? `${ext.build ?? "?"}@${ext.version ?? "?"} (${ext.browser ?? "?"})` : "unknown extension";
|
|
48491
48481
|
|
|
48492
48482
|
class BrowserExtensionBridge {
|
|
48493
48483
|
debug;
|
|
48494
48484
|
server;
|
|
48495
48485
|
wss;
|
|
48496
48486
|
extensionWs = null;
|
|
48487
|
+
extIdentity = null;
|
|
48497
48488
|
pending = new Map;
|
|
48498
48489
|
constructor(debug = false) {
|
|
48499
48490
|
this.debug = debug;
|
|
@@ -48574,8 +48565,18 @@ class BrowserExtensionBridge {
|
|
|
48574
48565
|
console.log("[browser-bridge:recv]", data.type);
|
|
48575
48566
|
if (data.type === "hello") {
|
|
48576
48567
|
const isTabChannel = data.role === "extension-tab" || data.role === "extension";
|
|
48577
|
-
if (data.role === "extension-control")
|
|
48568
|
+
if (data.role === "extension-control") {
|
|
48569
|
+
const ident = describeExt(data.ext);
|
|
48570
|
+
const prev = this.extensionWs;
|
|
48571
|
+
if (isOpen(prev) && prev !== ws) {
|
|
48572
|
+
console.log(`[browser-bridge] control taken over by ${ident} (was ${describeExt(this.extIdentity)})`);
|
|
48573
|
+
prev.send(JSON.stringify({ type: "control_superseded", payload: { by: ident } }));
|
|
48574
|
+
} else if (this.debug) {
|
|
48575
|
+
console.log(`[browser-bridge] control acquired by ${ident}`);
|
|
48576
|
+
}
|
|
48578
48577
|
this.extensionWs = ws;
|
|
48578
|
+
this.extIdentity = data.ext ?? null;
|
|
48579
|
+
}
|
|
48579
48580
|
if (data.role === "extension-control" || isTabChannel) {
|
|
48580
48581
|
if (isOpen(ws))
|
|
48581
48582
|
ws.send(JSON.stringify({ type: "connected_edge", payload: { edgeId: BRIDGE_EDGE_ID } }));
|
|
@@ -48628,8 +48629,12 @@ class BrowserExtensionBridge {
|
|
|
48628
48629
|
}
|
|
48629
48630
|
}
|
|
48630
48631
|
handleClose(ws) {
|
|
48631
|
-
if (this.extensionWs === ws)
|
|
48632
|
+
if (this.extensionWs === ws) {
|
|
48633
|
+
if (this.debug)
|
|
48634
|
+
console.log(`[browser-bridge] control released by ${describeExt(this.extIdentity)}`);
|
|
48632
48635
|
this.extensionWs = null;
|
|
48636
|
+
this.extIdentity = null;
|
|
48637
|
+
}
|
|
48633
48638
|
for (const [requestId, pending] of this.pending) {
|
|
48634
48639
|
if (pending.ws !== ws)
|
|
48635
48640
|
continue;
|
|
@@ -48860,6 +48865,15 @@ var tool_catalog_default = {
|
|
|
48860
48865
|
description: "Use to generate speech audio from text, clone voices, or list voices. Needs ELEVENLABS_API_KEY.",
|
|
48861
48866
|
versionCmd: "elevenlabs-api --version 2>/dev/null | head -1"
|
|
48862
48867
|
},
|
|
48868
|
+
"codex-imagegen-api": {
|
|
48869
|
+
category: "media",
|
|
48870
|
+
pkg: "@todoforai/codex-imagegen-api",
|
|
48871
|
+
installer: "npm",
|
|
48872
|
+
label: "Image Gen",
|
|
48873
|
+
capabilities: "AI image generation & editing via TODOFORAI backend (gpt-image)",
|
|
48874
|
+
description: 'Use to generate or edit images. Subcommands: `codex-imagegen-api generate "<prompt>" -o out.png [--size 1024x1024] [--quality low|medium|high]` and `codex-imagegen-api edit "<instruction>" -i in.png -o out.png` (repeat `-i` for multiple reference images).',
|
|
48875
|
+
versionCmd: "codex-imagegen-api --version 2>/dev/null | head -1"
|
|
48876
|
+
},
|
|
48863
48877
|
"suno-api": {
|
|
48864
48878
|
category: "media",
|
|
48865
48879
|
pkg: "@todoforai/suno-api",
|
|
@@ -48940,7 +48954,7 @@ var tool_catalog_default = {
|
|
|
48940
48954
|
pkg: "netlify-cli",
|
|
48941
48955
|
installer: "npm",
|
|
48942
48956
|
label: "Netlify",
|
|
48943
|
-
statusCmd: `
|
|
48957
|
+
statusCmd: `netlify api getCurrentUser 2>&1 | grep -oP '"email":\\s*"\\K[^"]+' | head -1`,
|
|
48944
48958
|
loginCmd: "netlify login",
|
|
48945
48959
|
credentialPaths: [
|
|
48946
48960
|
"~/.netlify/config.json"
|
|
@@ -48954,7 +48968,7 @@ var tool_catalog_default = {
|
|
|
48954
48968
|
pkg: "firebase-tools",
|
|
48955
48969
|
installer: "npm",
|
|
48956
48970
|
label: "Firebase",
|
|
48957
|
-
statusCmd: "
|
|
48971
|
+
statusCmd: "firebase login:list 2>&1 | grep '@'",
|
|
48958
48972
|
loginCmd: "firebase login",
|
|
48959
48973
|
credentialPaths: [
|
|
48960
48974
|
"~/.config/configstore/firebase-tools.json"
|
|
@@ -48968,7 +48982,7 @@ var tool_catalog_default = {
|
|
|
48968
48982
|
pkg: "wrangler",
|
|
48969
48983
|
installer: "npm",
|
|
48970
48984
|
label: "Cloudflare",
|
|
48971
|
-
statusCmd: "
|
|
48985
|
+
statusCmd: "wrangler whoami 2>&1 | grep -v 'Failed' | grep '@'",
|
|
48972
48986
|
loginCmd: "wrangler login",
|
|
48973
48987
|
credentialPaths: [
|
|
48974
48988
|
"~/.config/.wrangler/config/default.toml"
|
|
@@ -49010,7 +49024,7 @@ var tool_catalog_default = {
|
|
|
49010
49024
|
pkg: "supabase",
|
|
49011
49025
|
installer: "binary",
|
|
49012
49026
|
label: "Supabase",
|
|
49013
|
-
statusCmd: "
|
|
49027
|
+
statusCmd: "supabase projects list >/dev/null 2>&1 && echo authenticated",
|
|
49014
49028
|
loginCmd: "supabase login",
|
|
49015
49029
|
credentialPaths: [
|
|
49016
49030
|
"~/.config/supabase/access-token"
|
|
@@ -49024,7 +49038,7 @@ var tool_catalog_default = {
|
|
|
49024
49038
|
pkg: "@railway/cli",
|
|
49025
49039
|
installer: "npm",
|
|
49026
49040
|
label: "Railway",
|
|
49027
|
-
statusCmd: "
|
|
49041
|
+
statusCmd: "railway whoami 2>&1 | grep -v 'Unauthorized'",
|
|
49028
49042
|
loginCmd: "railway login",
|
|
49029
49043
|
credentialPaths: [
|
|
49030
49044
|
"~/.railway/config.json"
|
|
@@ -49061,7 +49075,7 @@ var tool_catalog_default = {
|
|
|
49061
49075
|
pkg: "@sentry/cli",
|
|
49062
49076
|
installer: "npm",
|
|
49063
49077
|
label: "Sentry",
|
|
49064
|
-
statusCmd: "
|
|
49078
|
+
statusCmd: "sentry-cli info 2>&1 | grep -q 'Authentication Info' && echo authenticated",
|
|
49065
49079
|
loginCmd: "sentry-cli login",
|
|
49066
49080
|
credentialPaths: [
|
|
49067
49081
|
"~/.sentryclirc"
|
|
@@ -49077,7 +49091,7 @@ var tool_catalog_default = {
|
|
|
49077
49091
|
label: "TODOforAI",
|
|
49078
49092
|
capabilities: "Create & manage TODOs, run workflows, API access",
|
|
49079
49093
|
description: "Use to programmatically create/list/update TODOs in TODOforAI, kick off workflows, call the platform API.",
|
|
49080
|
-
statusCmd: "todoai
|
|
49094
|
+
statusCmd: "todoai whoami >/dev/null 2>&1",
|
|
49081
49095
|
installCmd: "bun add -g @todoforai/cli",
|
|
49082
49096
|
versionCmd: "todoai --version 2>/dev/null | head -1",
|
|
49083
49097
|
internal: true
|
|
@@ -49087,7 +49101,6 @@ var tool_catalog_default = {
|
|
|
49087
49101
|
pkg: "newman",
|
|
49088
49102
|
installer: "npm",
|
|
49089
49103
|
label: "Postman",
|
|
49090
|
-
statusCmd: "newman --version",
|
|
49091
49104
|
capabilities: "Run API test collections, CI/CD integration, HTML reports",
|
|
49092
49105
|
description: "Use to run Postman collections from CLI (API smoke tests, CI checks). `newman run <collection.json> -e <env.json>`.",
|
|
49093
49106
|
versionCmd: "newman --version 2>/dev/null | head -1"
|
|
@@ -49098,7 +49111,6 @@ var tool_catalog_default = {
|
|
|
49098
49111
|
installer: "system",
|
|
49099
49112
|
label: "Web Request",
|
|
49100
49113
|
binName: "curl",
|
|
49101
|
-
statusCmd: "curl --version >/dev/null 2>&1",
|
|
49102
49114
|
capabilities: "HTTP/HTTPS/FTP client: GET/POST/PUT/DELETE, headers, auth, file upload/download, follow redirects, cookies, TLS.",
|
|
49103
49115
|
description: "Default tool for any HTTP request: hitting REST APIs, downloading files, testing endpoints. Common flags: `-s` silent, `-L` follow redirects, `-H 'Header: ...'`, `-X POST -d '...'`, `-o file`, `-f` fail on HTTP errors. Pipe into `jq` for JSON.",
|
|
49104
49116
|
versionCmd: "curl --version 2>/dev/null | head -1",
|
|
@@ -49110,7 +49122,6 @@ var tool_catalog_default = {
|
|
|
49110
49122
|
pkg: "cloudflared",
|
|
49111
49123
|
installer: "binary",
|
|
49112
49124
|
label: "Tunnel",
|
|
49113
|
-
statusCmd: "cloudflared --version",
|
|
49114
49125
|
capabilities: "Tunnel to localhost, expose local services, zero-trust access",
|
|
49115
49126
|
description: "Use to expose a local port to the public internet via a Cloudflare tunnel. Quick: `cloudflared tunnel --url http://localhost:<port>` prints a public https URL.",
|
|
49116
49127
|
versionCmd: "cloudflared --version 2>/dev/null | head -1"
|
|
@@ -49121,13 +49132,13 @@ var tool_catalog_default = {
|
|
|
49121
49132
|
installer: "binary",
|
|
49122
49133
|
preinstallCloud: true,
|
|
49123
49134
|
label: "HashiCorp Vault",
|
|
49124
|
-
statusCmd: "
|
|
49135
|
+
statusCmd: "vault token lookup >/dev/null 2>&1 && echo authenticated",
|
|
49125
49136
|
loginCmd: "vault login",
|
|
49126
49137
|
credentialPaths: [
|
|
49127
49138
|
"~/.vault-token"
|
|
49128
49139
|
],
|
|
49129
49140
|
capabilities: "Secrets management, dynamic credentials",
|
|
49130
|
-
description: "HashiCorp Vault CLI for Terraform/vals/ecosystem integrations. For
|
|
49141
|
+
description: "HashiCorp Vault CLI — only for Terraform/vals/ecosystem integrations that expect it. For all credential read/write tasks use `tfa-vault` instead. Needs VAULT_ADDR + VAULT_TOKEN.",
|
|
49131
49142
|
versionCmd: "vault --version 2>/dev/null | head -1"
|
|
49132
49143
|
},
|
|
49133
49144
|
rclone: {
|
|
@@ -49136,16 +49147,17 @@ var tool_catalog_default = {
|
|
|
49136
49147
|
installer: "system",
|
|
49137
49148
|
preinstallCloud: true,
|
|
49138
49149
|
label: "Cloud Sync",
|
|
49139
|
-
statusCmd: "rclone listremotes 2>&1 | grep -v 'NOTICE' |
|
|
49150
|
+
statusCmd: "rclone listremotes 2>&1 | grep -v 'NOTICE' | grep -q ':' && echo authenticated",
|
|
49140
49151
|
loginCmd: "rclone config",
|
|
49141
49152
|
credentialPaths: [
|
|
49142
49153
|
"~/.config/rclone/rclone.conf"
|
|
49143
49154
|
],
|
|
49144
49155
|
capabilities: "Access Google Drive, OneDrive, Dropbox, S3 and 40+ cloud providers. List, copy, sync, move, mount files as virtual filesystem (FUSE). Use 'rclone config create <name> <provider>' to connect (opens browser for OAuth).",
|
|
49145
|
-
description: "
|
|
49156
|
+
description: "Use to access cloud storage (Drive/OneDrive/Dropbox/S3/40+). Connect: `rclone config create <name> <provider>` (OAuth via browser). Core ops: `rclone listremotes`, `rclone ls <remote>:<path>`, `rclone copy|sync <src> <dst>`. Mount as FUSE (lazy fetch): `rclone mount <remote>: ~/.todoforai/mnt/<remote> --vfs-cache-mode full --daemon`; unmount with `fusermount -u <path>`. See `subProviders` for per-provider connect commands.",
|
|
49146
49157
|
subProviders: {
|
|
49147
49158
|
gdrive: {
|
|
49148
49159
|
label: "Google Drive",
|
|
49160
|
+
authType: "oauth",
|
|
49149
49161
|
connectCmd: "rclone config create {{REMOTE}} drive",
|
|
49150
49162
|
statusCmd: "rclone lsd {{REMOTE}}: 2>&1 | head -1",
|
|
49151
49163
|
remoteName: "gdrive",
|
|
@@ -49154,6 +49166,7 @@ var tool_catalog_default = {
|
|
|
49154
49166
|
},
|
|
49155
49167
|
onedrive: {
|
|
49156
49168
|
label: "OneDrive",
|
|
49169
|
+
authType: "oauth",
|
|
49157
49170
|
connectCmd: "rclone config create {{REMOTE}} onedrive",
|
|
49158
49171
|
statusCmd: "rclone lsd {{REMOTE}}: 2>&1 | head -1",
|
|
49159
49172
|
remoteName: "onedrive",
|
|
@@ -49162,6 +49175,7 @@ var tool_catalog_default = {
|
|
|
49162
49175
|
},
|
|
49163
49176
|
dropbox: {
|
|
49164
49177
|
label: "Dropbox",
|
|
49178
|
+
authType: "oauth",
|
|
49165
49179
|
connectCmd: "rclone config create {{REMOTE}} dropbox",
|
|
49166
49180
|
statusCmd: "rclone lsd {{REMOTE}}: 2>&1 | head -1",
|
|
49167
49181
|
remoteName: "dropbox",
|
|
@@ -49170,6 +49184,7 @@ var tool_catalog_default = {
|
|
|
49170
49184
|
},
|
|
49171
49185
|
s3: {
|
|
49172
49186
|
label: "Amazon S3",
|
|
49187
|
+
authType: "credentials",
|
|
49173
49188
|
connectCmd: "rclone config create {{REMOTE}} s3",
|
|
49174
49189
|
statusCmd: "rclone lsd {{REMOTE}}: 2>&1 | head -1",
|
|
49175
49190
|
remoteName: "s3",
|
|
@@ -49183,7 +49198,6 @@ var tool_catalog_default = {
|
|
|
49183
49198
|
pkg: "pymupdf",
|
|
49184
49199
|
installer: "pip",
|
|
49185
49200
|
label: "PDF",
|
|
49186
|
-
statusCmd: "python3 -c 'import pymupdf' 2>/dev/null || /usr/bin/python3 -c 'import pymupdf' 2>/dev/null",
|
|
49187
49201
|
capabilities: "PDF text editing, extraction, merge, split",
|
|
49188
49202
|
description: "Use to programmatically read/edit/merge/split PDFs (text + positions). Call from `python3 -c`. Extract text: `pymupdf.open(p)[i].get_text()`; with bbox/font: `.get_text('dict')`. Replace text in place: `page.add_redact_annot(page.search_for('old')[0], text='new'); page.apply_redactions(); doc.save(out)`. For PDF → markdown prefer `firecrawl parse`.",
|
|
49189
49203
|
versionCmd: "python3 -c 'import pymupdf; print(pymupdf.__version__)' 2>/dev/null",
|
|
@@ -49195,9 +49209,8 @@ var tool_catalog_default = {
|
|
|
49195
49209
|
installer: "npm",
|
|
49196
49210
|
preinstallCloud: true,
|
|
49197
49211
|
label: "Browser",
|
|
49198
|
-
statusCmd: "agent-browser --version",
|
|
49199
49212
|
capabilities: "Headless browser automation, web scraping, accessibility tree snapshots, screenshots, PDF generation",
|
|
49200
|
-
description: "
|
|
49213
|
+
description: "Headless browser for JS-rendered pages, automated flows, screenshots, PDF export. **Default browser choice** — use this unless the user's own session/cookies are required (then use `todoforai-browser`). Prefer `curl` for plain HTTP, `firecrawl` for bulk scrape/crawl. Commands: open <url>, click/type/fill <selector> <text>, snapshot (accessibility tree with @refs — use these for clicking), screenshot [path], pdf <path>, eval <js>, wait <selector|ms>.",
|
|
49201
49214
|
versionCmd: "agent-browser --version 2>/dev/null | head -1"
|
|
49202
49215
|
},
|
|
49203
49216
|
firecrawl: {
|
|
@@ -49220,7 +49233,7 @@ var tool_catalog_default = {
|
|
|
49220
49233
|
installer: "npm",
|
|
49221
49234
|
label: "Browser (Extension)",
|
|
49222
49235
|
capabilities: "Browser automation via extension (non-headless), web scraping, accessibility tree snapshots, screenshots, PDF generation",
|
|
49223
|
-
description: "
|
|
49236
|
+
description: "Drives the user's real browser via the TODOforAI extension (non-headless). Use when the task needs the user's actual session — logged-in accounts, saved cookies, extensions, MFA. For anything else prefer headless `agent-browser`. Commands: open <url>, click/type/fill <selector> <text>, snapshot (accessibility tree with @refs for clicking), screenshot [path], pdf <path>, eval <js>, wait <selector|ms>. Supports drag/drop, file uploads, form interactions.",
|
|
49224
49237
|
versionCmd: `node -p "require(require('child_process').execSync('which todoforai-browser',{encoding:'utf8'}).trim().replace(/\\/dist\\/index\\.js$/, '/package.json')).version" 2>/dev/null`,
|
|
49225
49238
|
internal: true
|
|
49226
49239
|
},
|
|
@@ -49254,6 +49267,7 @@ var tool_catalog_default = {
|
|
|
49254
49267
|
capabilities: "Explore a codebase as a real TODO: read-only agent maps structure, surfaces relevant files, streams findings to terminal",
|
|
49255
49268
|
description: 'Codebase exploration as a real TODO with patched permissions (read/grep/bash only) and live streaming. Usage: `tfa-explore [--repo <path>] "<question>"`. Creates a TODO visible in the UI and streams block events to stdout until DONE.',
|
|
49256
49269
|
versionCmd: "tfa-explore --version 2>/dev/null | head -1",
|
|
49270
|
+
statusCmd: "tfa-explore whoami",
|
|
49257
49271
|
installCmd: "bun add -g @todoforai/tfa-explore",
|
|
49258
49272
|
preinstall: true,
|
|
49259
49273
|
preinstallCloud: true,
|
|
@@ -49267,6 +49281,7 @@ var tool_catalog_default = {
|
|
|
49267
49281
|
capabilities: "Review a git diff as a real TODO: read-only agent assesses goal, finds issues, suggests simpler approaches",
|
|
49268
49282
|
description: 'Capture `git diff` and ask a read-only sub-agent to review it as a real TODO. Usage: `tfa-review [--repo <path>] [--against <ref>] "<goal>"`. Default diffs uncommitted changes vs HEAD. Streams block events to stdout until DONE.',
|
|
49269
49283
|
versionCmd: "tfa-review --version 2>/dev/null | head -1",
|
|
49284
|
+
statusCmd: "tfa-review whoami",
|
|
49270
49285
|
installCmd: "bun add -g @todoforai/tfa-review",
|
|
49271
49286
|
preinstall: true,
|
|
49272
49287
|
preinstallCloud: true,
|
|
@@ -49280,6 +49295,7 @@ var tool_catalog_default = {
|
|
|
49280
49295
|
capabilities: "Summarize files or piped input as a real TODO with no-tools sub-agent",
|
|
49281
49296
|
description: 'Summarize file contents or stdin as a real TODO. Usage: `tfa-summary [-f <file>]... [<files...>] [<focus>]` or `cat x | tfa-summary "focus"`. No tools (content is inlined); streams block events to stdout until DONE.',
|
|
49282
49297
|
versionCmd: "tfa-summary --version 2>/dev/null | head -1",
|
|
49298
|
+
statusCmd: "tfa-summary whoami",
|
|
49283
49299
|
installCmd: "bun add -g @todoforai/tfa-summary",
|
|
49284
49300
|
internal: true
|
|
49285
49301
|
},
|
|
@@ -49289,13 +49305,12 @@ var tool_catalog_default = {
|
|
|
49289
49305
|
installer: "npm",
|
|
49290
49306
|
label: "Vault",
|
|
49291
49307
|
capabilities: "Zero-config TODOforAI vault CLI: put/get/patch/list/rm secrets in the user's KV v2 vault. Reuses bridge credentials (TODOFORAI_API_KEY / ~/.config/todoforai/credentials.json); derives vault URL from backend URL.",
|
|
49292
|
-
description: '
|
|
49308
|
+
description: 'Native TODOforAI secret store — free, zero-config (reuses bridge credentials; no VAULT_ADDR/VAULT_TOKEN/login). **Default credential source: for any task touching API keys, tokens, service accounts, or third-party auth, check `tfa-vault` before searching repo files, .env, or env vars — unless the user provides another source.** Discover-then-consume pattern: `tfa-vault list accounts/` to find keys, then `KEY=$(tfa-vault get -f api_key accounts/<service>)` in shell. Common paths: `accounts/<service>`, `api/<service>`, `secrets/<service>`. Usage: `tfa-vault put <path> k=v [k=v ...]`, `tfa-vault get [-f <field>] <path>`, `tfa-vault patch <path> k=v ...`, `tfa-vault list [<prefix>]`, `tfa-vault rm <path>`. Values: inline (`k=v`), file (`k=@/path`), stdin (`k=-`). `get -f <field>` writes raw value with no trailing newline — safe in `$(...)`. Exit 2 means "not found" (distinct from network errors).',
|
|
49293
49309
|
versionCmd: "tfa-vault --version 2>/dev/null | head -1",
|
|
49294
49310
|
statusCmd: "tfa-vault whoami",
|
|
49295
49311
|
installCmd: "bun add -g @todoforai/vault",
|
|
49296
49312
|
preinstall: true,
|
|
49297
|
-
preinstallCloud: true
|
|
49298
|
-
internal: true
|
|
49313
|
+
preinstallCloud: true
|
|
49299
49314
|
},
|
|
49300
49315
|
ripgrep: {
|
|
49301
49316
|
category: "development",
|
|
@@ -49531,17 +49546,6 @@ function isToolInstalled(name) {
|
|
|
49531
49546
|
}
|
|
49532
49547
|
return whichWithTools(name) !== null;
|
|
49533
49548
|
}
|
|
49534
|
-
function findReferencedTools(content) {
|
|
49535
|
-
const stripped = content.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
|
|
49536
|
-
return Object.keys(TOOL_CATALOG).filter((name) => {
|
|
49537
|
-
const esc = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
49538
|
-
const re = new RegExp(String.raw`(?:^|[|;&\n]|&&|\|\||` + String.raw`\$\(|` + "`" + String.raw`|xargs\s+|sudo\s+|env\s+)\s*` + esc + String.raw`\b(?!-)`, "m");
|
|
49539
|
-
return re.test(stripped);
|
|
49540
|
-
});
|
|
49541
|
-
}
|
|
49542
|
-
function findMissingTools(content) {
|
|
49543
|
-
return findReferencedTools(content).filter((name) => !isToolInstalled(name));
|
|
49544
|
-
}
|
|
49545
49549
|
async function installBinary(name) {
|
|
49546
49550
|
const urlFunc = BINARY_URL_FUNCS[name];
|
|
49547
49551
|
if (!urlFunc) {
|
|
@@ -51592,7 +51596,6 @@ ${this.lastPart}`;
|
|
|
51592
51596
|
var processes = new Map;
|
|
51593
51597
|
var outputBuffers = new Map;
|
|
51594
51598
|
var completionResolvers = new Map;
|
|
51595
|
-
var pendingToolApprovals = new Map;
|
|
51596
51599
|
var exitedOutputByPid = new Map;
|
|
51597
51600
|
async function executeBlock(blockId, content, send, todoId, messageId, timeout, cwd, manual = false, runMode, edgeId, agentSettingsId = "", keepAliveOnTimeout = false) {
|
|
51598
51601
|
if (processes.has(blockId)) {
|
|
@@ -51611,38 +51614,6 @@ async function executeBlock(blockId, content, send, todoId, messageId, timeout,
|
|
|
51611
51614
|
} else {
|
|
51612
51615
|
cwd = tmpDir;
|
|
51613
51616
|
}
|
|
51614
|
-
const missing = findMissingTools(content);
|
|
51615
|
-
if (missing.length && !pendingToolApprovals.has(blockId)) {
|
|
51616
|
-
pendingToolApprovals.set(blockId, missing);
|
|
51617
|
-
console.log(`[shell] Missing tools ${missing}, requesting approval for block ${blockId}`);
|
|
51618
|
-
await send({
|
|
51619
|
-
type: "BLOCK_UPDATE",
|
|
51620
|
-
payload: {
|
|
51621
|
-
todoId,
|
|
51622
|
-
blockId,
|
|
51623
|
-
messageId,
|
|
51624
|
-
updates: {
|
|
51625
|
-
status: "AWAITING_APPROVAL",
|
|
51626
|
-
approvalContext: { source: "edge", toolInstalls: missing, workspace: cwd, edgeId }
|
|
51627
|
-
}
|
|
51628
|
-
}
|
|
51629
|
-
});
|
|
51630
|
-
return;
|
|
51631
|
-
}
|
|
51632
|
-
if (pendingToolApprovals.has(blockId)) {
|
|
51633
|
-
const tools = pendingToolApprovals.get(blockId);
|
|
51634
|
-
pendingToolApprovals.delete(blockId);
|
|
51635
|
-
const installed = [];
|
|
51636
|
-
for (const t of tools) {
|
|
51637
|
-
if (await ensureTool(t))
|
|
51638
|
-
installed.push(t);
|
|
51639
|
-
}
|
|
51640
|
-
if (installed.length) {
|
|
51641
|
-
const notice = `[installed: ${installed.join(", ")}]
|
|
51642
|
-
`;
|
|
51643
|
-
await send(msg.shellBlockResult(todoId, blockId, notice, messageId));
|
|
51644
|
-
}
|
|
51645
|
-
}
|
|
51646
51617
|
await send({
|
|
51647
51618
|
type: "BLOCK_UPDATE",
|
|
51648
51619
|
payload: { todoId, blockId, messageId, updates: { status: "RUNNING" } }
|
|
@@ -51931,20 +51902,13 @@ var MAX_DIRS_PER_ROOT = 2000;
|
|
|
51931
51902
|
var FRONTMATTER_BYTES = 8 * 1024;
|
|
51932
51903
|
var MAX_NAME_LEN = 64;
|
|
51933
51904
|
var MAX_DESC_LEN = 1024;
|
|
51934
|
-
function shortestPath(filePath, relTo) {
|
|
51935
|
-
const rel = path6.relative(relTo, filePath);
|
|
51936
|
-
const home = os7.homedir();
|
|
51937
|
-
const homeRel = filePath.startsWith(home + path6.sep) ? "~" + filePath.slice(home.length) : filePath;
|
|
51938
|
-
return [rel, homeRel, filePath].reduce((a, b) => b.length < a.length ? b : a);
|
|
51939
|
-
}
|
|
51940
51905
|
async function discoverSkills(rootPaths, opts = {}) {
|
|
51941
51906
|
const includeUserScope = opts.includeUserScope ?? true;
|
|
51942
51907
|
const roots = [
|
|
51943
|
-
...rootPaths.map((p10) => ({ path: path6.join(p10, ".agents", "skills"),
|
|
51908
|
+
...rootPaths.map((p10) => ({ path: path6.join(p10, ".agents", "skills"), scope: "repo" }))
|
|
51944
51909
|
];
|
|
51945
51910
|
if (includeUserScope) {
|
|
51946
|
-
|
|
51947
|
-
roots.push({ path: path6.join(home, ".agents", "skills"), relTo: home, scope: "user" });
|
|
51911
|
+
roots.push({ path: path6.join(os7.homedir(), ".agents", "skills"), scope: "user" });
|
|
51948
51912
|
}
|
|
51949
51913
|
const skills = [];
|
|
51950
51914
|
const errors = [];
|
|
@@ -51962,11 +51926,11 @@ async function discoverSkills(rootPaths, opts = {}) {
|
|
|
51962
51926
|
}
|
|
51963
51927
|
if (!stat.isDirectory())
|
|
51964
51928
|
continue;
|
|
51965
|
-
walkRoot(root.path, root.
|
|
51929
|
+
walkRoot(root.path, root.scope, skills, errors, seenSkillPaths);
|
|
51966
51930
|
}
|
|
51967
51931
|
return { skills, errors };
|
|
51968
51932
|
}
|
|
51969
|
-
function walkRoot(root,
|
|
51933
|
+
function walkRoot(root, scope, skills, errors, seen) {
|
|
51970
51934
|
const queue = [{ dir: root, depth: 0 }];
|
|
51971
51935
|
let dirsVisited = 0;
|
|
51972
51936
|
for (let i10 = 0;i10 < queue.length; i10++) {
|
|
@@ -52002,14 +51966,14 @@ function walkRoot(root, relTo, scope, skills, errors, seen) {
|
|
|
52002
51966
|
if (seen.has(full))
|
|
52003
51967
|
continue;
|
|
52004
51968
|
seen.add(full);
|
|
52005
|
-
const skill = parseSkillFile(full,
|
|
51969
|
+
const skill = parseSkillFile(full, scope, errors);
|
|
52006
51970
|
if (skill)
|
|
52007
51971
|
skills.push(skill);
|
|
52008
51972
|
}
|
|
52009
51973
|
}
|
|
52010
51974
|
}
|
|
52011
51975
|
}
|
|
52012
|
-
function parseSkillFile(filePath,
|
|
51976
|
+
function parseSkillFile(filePath, scope, errors) {
|
|
52013
51977
|
let head;
|
|
52014
51978
|
try {
|
|
52015
51979
|
const fd3 = fs9.openSync(filePath, "r");
|
|
@@ -52042,7 +52006,7 @@ function parseSkillFile(filePath, relTo, scope, errors) {
|
|
|
52042
52006
|
name,
|
|
52043
52007
|
description,
|
|
52044
52008
|
shortDescription: shortDescription && shortDescription.length <= MAX_DESC_LEN ? shortDescription : undefined,
|
|
52045
|
-
path:
|
|
52009
|
+
path: filePath,
|
|
52046
52010
|
scope
|
|
52047
52011
|
};
|
|
52048
52012
|
}
|
|
@@ -52372,9 +52336,6 @@ register("execute_shell_command", async (args, client) => {
|
|
|
52372
52336
|
try {
|
|
52373
52337
|
await send(msg.shellBlockStart(todoId, blockId, "execute", messageId));
|
|
52374
52338
|
await executeBlock(blockId, execCmd, send, todoId, messageId, timeout, cwd, false, "internal", undefined, agentSettingsId, true);
|
|
52375
|
-
if (pendingToolApprovals.has(blockId)) {
|
|
52376
|
-
return { __awaiting_approval__: true };
|
|
52377
|
-
}
|
|
52378
52339
|
await waitForCompletion(blockId, (timeout + 5) * 1000);
|
|
52379
52340
|
const rawOutput = getBlockRawOutput(blockId);
|
|
52380
52341
|
let output = rawOutput ?? getBlockOutput(blockId);
|
|
@@ -52686,8 +52647,6 @@ async function handleFunctionCall(payload, send, client) {
|
|
|
52686
52647
|
throw new Error(`Unknown function: ${functionName}. Available: ${available.join(", ")}`);
|
|
52687
52648
|
}
|
|
52688
52649
|
const result = await fn2(args, client);
|
|
52689
|
-
if (result && result.__awaiting_approval__)
|
|
52690
|
-
return;
|
|
52691
52650
|
await send(makeSuccess(result));
|
|
52692
52651
|
} catch (e) {
|
|
52693
52652
|
log3("error", `Function call '${functionName}' failed:`, e.message);
|
|
@@ -53186,10 +53145,17 @@ class ServerError extends Error {
|
|
|
53186
53145
|
}
|
|
53187
53146
|
}
|
|
53188
53147
|
|
|
53189
|
-
//
|
|
53148
|
+
// node_modules/@todoforai/update-notifier/src/index.ts
|
|
53190
53149
|
import fs12 from "node:fs";
|
|
53191
53150
|
import path9 from "node:path";
|
|
53192
53151
|
import os9 from "node:os";
|
|
53152
|
+
function isLinkedInstall() {
|
|
53153
|
+
try {
|
|
53154
|
+
return !fs12.realpathSync(process.argv[1] || "").includes(`${path9.sep}node_modules${path9.sep}`);
|
|
53155
|
+
} catch {
|
|
53156
|
+
return false;
|
|
53157
|
+
}
|
|
53158
|
+
}
|
|
53193
53159
|
var TTL_MS = 24 * 60 * 60 * 1000;
|
|
53194
53160
|
var CACHE_DIR = path9.join(os9.homedir(), ".config", "todoforai");
|
|
53195
53161
|
function cmpVer(a, b) {
|
|
@@ -53203,7 +53169,7 @@ function cmpVer(a, b) {
|
|
|
53203
53169
|
return 0;
|
|
53204
53170
|
}
|
|
53205
53171
|
function checkForUpdates(pkg) {
|
|
53206
|
-
if (!process.stderr.isTTY || process.env.CI || process.env.NO_UPDATE_NOTIFIER)
|
|
53172
|
+
if (!process.stderr.isTTY || process.env.CI || process.env.NO_UPDATE_NOTIFIER || isLinkedInstall())
|
|
53207
53173
|
return;
|
|
53208
53174
|
const cacheFile = path9.join(CACHE_DIR, `notifier-${encodeURIComponent(pkg.name)}.json`);
|
|
53209
53175
|
let cache = {};
|
|
@@ -53302,7 +53268,19 @@ function releaseLock(lp2) {
|
|
|
53302
53268
|
fs13.unlinkSync(lp2);
|
|
53303
53269
|
} catch {}
|
|
53304
53270
|
}
|
|
53271
|
+
var MIN_BUN_VERSION = "1.3.14";
|
|
53272
|
+
function cmpSemver(a, b) {
|
|
53273
|
+
const pa2 = a.split(".").map(Number), pb2 = b.split(".").map(Number);
|
|
53274
|
+
for (let i10 = 0;i10 < 3; i10++)
|
|
53275
|
+
if ((pa2[i10] ?? 0) !== (pb2[i10] ?? 0))
|
|
53276
|
+
return (pa2[i10] ?? 0) - (pb2[i10] ?? 0);
|
|
53277
|
+
return 0;
|
|
53278
|
+
}
|
|
53305
53279
|
async function main() {
|
|
53280
|
+
if (typeof Bun !== "undefined" && cmpSemver(Bun.version, MIN_BUN_VERSION) < 0) {
|
|
53281
|
+
console.error(`\x1B[31mBun ${Bun.version} is too old — WebSocket upgrade fails after fetch on the same host. Upgrade to >= ${MIN_BUN_VERSION} via \`bun upgrade\`.\x1B[0m`);
|
|
53282
|
+
process.exit(1);
|
|
53283
|
+
}
|
|
53306
53284
|
const ownPkg = readOwnPackage();
|
|
53307
53285
|
if (ownPkg)
|
|
53308
53286
|
checkForUpdates(ownPkg);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@todoforai/edge",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"todoforai-edge": "dist/index.js"
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"prepublishOnly": "npm run build"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"@todoforai/update-notifier": "^0.1.0",
|
|
19
20
|
"fflate": "^0.8.2",
|
|
20
21
|
"ignore": "^7.0.5",
|
|
21
22
|
"unpdf": "^1.4.0",
|