@todoforai/edge 0.13.20 → 0.13.22
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 +139 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -48153,6 +48153,27 @@ class ApiClient {
|
|
|
48153
48153
|
throw new Error(`API ${method} ${endpoint} failed: ${res.status} ${await res.text()}`);
|
|
48154
48154
|
return res.json();
|
|
48155
48155
|
}
|
|
48156
|
+
async trpcQuery(path2, input) {
|
|
48157
|
+
const base = this.apiUrl.replace(/\/api\/v1\/?$/, "").replace(/\/$/, "");
|
|
48158
|
+
const params = new URLSearchParams({
|
|
48159
|
+
batch: "1",
|
|
48160
|
+
input: JSON.stringify({ 0: input })
|
|
48161
|
+
});
|
|
48162
|
+
const res = await fetch(`${base}/trpc/api/${path2}?${params}`, {
|
|
48163
|
+
method: "GET",
|
|
48164
|
+
headers: this.headers,
|
|
48165
|
+
signal: AbortSignal.timeout(30000)
|
|
48166
|
+
});
|
|
48167
|
+
const text = await res.text();
|
|
48168
|
+
if (!res.ok)
|
|
48169
|
+
throw new Error(`API tRPC ${path2} failed: ${res.status} ${text}`);
|
|
48170
|
+
const payload = JSON.parse(text);
|
|
48171
|
+
const item = Array.isArray(payload) ? payload[0] : payload;
|
|
48172
|
+
if (item?.error)
|
|
48173
|
+
throw new Error(`API tRPC ${path2} failed: ${item.error.message || JSON.stringify(item.error)}`);
|
|
48174
|
+
const data = item?.result?.data;
|
|
48175
|
+
return data?.json !== undefined ? data.json : data;
|
|
48176
|
+
}
|
|
48156
48177
|
async validateApiKey() {
|
|
48157
48178
|
if (!this.apiKey)
|
|
48158
48179
|
return { valid: false, error: "No API key provided" };
|
|
@@ -48191,9 +48212,10 @@ class ApiClient {
|
|
|
48191
48212
|
createTodo(projectId, content, agentSettings) {
|
|
48192
48213
|
return this.request("POST", `/api/v1/projects/${projectId}/todos`, { content, agentSettings });
|
|
48193
48214
|
}
|
|
48194
|
-
listTodos(projectId) {
|
|
48195
|
-
|
|
48196
|
-
|
|
48215
|
+
listTodos(projectId, opts) {
|
|
48216
|
+
if (!projectId)
|
|
48217
|
+
return this.request("GET", "/api/v1/todos");
|
|
48218
|
+
return this.trpcQuery("todo.list", { projectId, ...opts });
|
|
48197
48219
|
}
|
|
48198
48220
|
getTodo(todoId) {
|
|
48199
48221
|
return this.request("GET", `/api/v1/todos/${todoId}`);
|
|
@@ -48788,6 +48810,20 @@ var tool_catalog_default = {
|
|
|
48788
48810
|
description: "Authenticated raw X API access via `xurl <method> <path>`; login `xurl auth oauth2`.",
|
|
48789
48811
|
versionCmd: "xurl --version 2>/dev/null | head -1"
|
|
48790
48812
|
},
|
|
48813
|
+
"linkedin-api": {
|
|
48814
|
+
category: "social",
|
|
48815
|
+
pkg: "@todoforai/linkedin-api",
|
|
48816
|
+
installer: "npm",
|
|
48817
|
+
label: "LinkedIn",
|
|
48818
|
+
statusCmd: `linkedin-api whoami 2>/dev/null | grep -oP '"publicId":\\s*"\\K[^"]+' | head -1`,
|
|
48819
|
+
loginCmd: "linkedin-api auth",
|
|
48820
|
+
credentialPaths: [
|
|
48821
|
+
"~/.config/linkedin-api/credentials.json"
|
|
48822
|
+
],
|
|
48823
|
+
capabilities: "Read profiles, list & read conversations, send DMs, send connection requests — raw LinkedIn Voyager API via cookie auth",
|
|
48824
|
+
description: "Authenticated LinkedIn Voyager API CLI. Cookie auth: `linkedin-api auth <li_at> <jsessionid>` (from browser DevTools).",
|
|
48825
|
+
versionCmd: "linkedin-api --version 2>/dev/null | head -1"
|
|
48826
|
+
},
|
|
48791
48827
|
"tiktok-uploader": {
|
|
48792
48828
|
category: "social",
|
|
48793
48829
|
pkg: "tiktok-uploader",
|
|
@@ -48821,7 +48857,7 @@ var tool_catalog_default = {
|
|
|
48821
48857
|
capabilities: "Send messages, send images & files, send locations & polls, group management, QR code login",
|
|
48822
48858
|
versionCmd: "mudslide --version 2>/dev/null | head -1"
|
|
48823
48859
|
},
|
|
48824
|
-
slack: {
|
|
48860
|
+
"slack-cli": {
|
|
48825
48861
|
category: "development",
|
|
48826
48862
|
pkg: "slack-cli",
|
|
48827
48863
|
installer: "binary",
|
|
@@ -49118,10 +49154,11 @@ var tool_catalog_default = {
|
|
|
49118
49154
|
pkg: "@todoforai/cli",
|
|
49119
49155
|
installer: "bun",
|
|
49120
49156
|
label: "TODOforAI",
|
|
49157
|
+
binName: "todoforai-cli",
|
|
49121
49158
|
capabilities: "Create/list/inspect/update TODOs, run templates & workflows, platform API access",
|
|
49122
|
-
description: '`
|
|
49159
|
+
description: "Reach the user's OWN TODOforAI tasks — NOT code `// TODO` comments. Use whenever the user asks about their tasks/todos/account on the platform: `todoforai-cli list` (`--status open`) to browse, `todoforai-cli \"prompt\"` to create, `todoforai-cli --inspect <id>[:<msg-id>]` to read a TODO's full chat/data (read-only), `status/addmessage/delete` to manage, `--template` for registry workflows. Never claim you lack access to platform TODOs — they're reachable here.",
|
|
49123
49160
|
installCmd: "bun add -g @todoforai/cli",
|
|
49124
|
-
versionCmd: "
|
|
49161
|
+
versionCmd: "todoforai-cli --version 2>/dev/null | head -1",
|
|
49125
49162
|
internal: true
|
|
49126
49163
|
},
|
|
49127
49164
|
newman: {
|
|
@@ -49218,6 +49255,20 @@ var tool_catalog_default = {
|
|
|
49218
49255
|
},
|
|
49219
49256
|
versionCmd: "rclone version 2>/dev/null | head -1"
|
|
49220
49257
|
},
|
|
49258
|
+
rdt: {
|
|
49259
|
+
category: "social",
|
|
49260
|
+
pkg: "rdt-cli",
|
|
49261
|
+
installer: "pip",
|
|
49262
|
+
label: "Reddit",
|
|
49263
|
+
statusCmd: `rdt status 2>&1 | grep -oP 'username":\\s*"\\K[^"]+' | head -1`,
|
|
49264
|
+
loginCmd: "rdt login",
|
|
49265
|
+
credentialPaths: [
|
|
49266
|
+
"~/.config/rdt-cli/credential.json"
|
|
49267
|
+
],
|
|
49268
|
+
capabilities: "Search & browse subreddits, read posts & comments, post comments, upvote & save, view user profiles & comments, export results — Reddit in your terminal.",
|
|
49269
|
+
description: 'Reddit CLI. Read/research: `rdt search "<query>" -r <sub> --json -c`, `rdt sub <name>`, `rdt read <postId>`, `rdt user-comments <user>`. Write (needs login): `rdt comment <postId> "<text>"`, `rdt upvote`, `rdt save`. `rdt status` for auth. Cookie auth via `rdt login` (extracts browser cookies).',
|
|
49270
|
+
versionCmd: "rdt --version 2>/dev/null | head -1"
|
|
49271
|
+
},
|
|
49221
49272
|
pymupdf: {
|
|
49222
49273
|
category: "utility",
|
|
49223
49274
|
pkg: "pymupdf",
|
|
@@ -49237,6 +49288,41 @@ var tool_catalog_default = {
|
|
|
49237
49288
|
description: "Default browser. On a PC with a display prefer a visible window the user can watch/interact with (needed for CAPTCHA/MFA/login): launch a separate Chrome with `google-chrome --remote-debugging-port=9222 --user-data-dir=$HOME/.config/google-chrome-cdp >/tmp/chrome-cdp.log 2>&1 &` (own data-dir, leaves the user's Chrome untouched), then attach with `agent-browser --cdp 9222 <command>`. Use headless only on the cloud or when no display is available.",
|
|
49238
49289
|
versionCmd: "agent-browser --version 2>/dev/null | head -1"
|
|
49239
49290
|
},
|
|
49291
|
+
"browser-manager-cli": {
|
|
49292
|
+
category: "development",
|
|
49293
|
+
pkg: "browser-manager-cli",
|
|
49294
|
+
installer: "binary",
|
|
49295
|
+
binName: "browser-manager-cli",
|
|
49296
|
+
preinstallCloud: true,
|
|
49297
|
+
label: "Browser Manager",
|
|
49298
|
+
statusCmd: "browser-manager-cli whoami",
|
|
49299
|
+
loginCmd: "browser-manager-cli login",
|
|
49300
|
+
capabilities: "On-demand cloud Chromium sessions exposed as CDP endpoints for agent-browser; create/list/get/delete sessions, hibernate/restore, health",
|
|
49301
|
+
description: "Spawns on-demand cloud Chromium sessions and exposes each as a CDP WebSocket for agent-browser to drive. Reuses the bridge login (zero-config, same creds as the daemon). Flow: `browser-manager-cli create` → prints a session with a cdpUrl; `browser-manager-cli connect <id> --exec` connects agent-browser to that session (runs `agent-browser connect '<cdp_url>'`), then drive it with normal agent-browser commands (open/click/snapshot/...). `browser-manager-cli list` shows your sessions, `whoami` the logged-in user, `delete <id>` / `delete-all` to clean up, `hibernate`/`restore` to pause/resume. Use this on the cloud VM instead of launching a local browser.",
|
|
49302
|
+
versionCmd: "browser-manager-cli version 2>/dev/null | head -1",
|
|
49303
|
+
binary: {
|
|
49304
|
+
"linux-x86_64": {
|
|
49305
|
+
url: "https://github.com/todoforai/browser-manager/releases/download/cli-v0.1.0/browser-manager-cli-linux-x86_64",
|
|
49306
|
+
archive: "raw"
|
|
49307
|
+
},
|
|
49308
|
+
"linux-aarch64": {
|
|
49309
|
+
url: "https://github.com/todoforai/browser-manager/releases/download/cli-v0.1.0/browser-manager-cli-linux-aarch64",
|
|
49310
|
+
archive: "raw"
|
|
49311
|
+
},
|
|
49312
|
+
"darwin-x86_64": {
|
|
49313
|
+
url: "https://github.com/todoforai/browser-manager/releases/download/cli-v0.1.0/browser-manager-cli-darwin-x86_64",
|
|
49314
|
+
archive: "raw"
|
|
49315
|
+
},
|
|
49316
|
+
"darwin-aarch64": {
|
|
49317
|
+
url: "https://github.com/todoforai/browser-manager/releases/download/cli-v0.1.0/browser-manager-cli-darwin-aarch64",
|
|
49318
|
+
archive: "raw"
|
|
49319
|
+
},
|
|
49320
|
+
"windows-x86_64": {
|
|
49321
|
+
url: "https://github.com/todoforai/browser-manager/releases/download/cli-v0.1.0/browser-manager-cli-windows-x86_64.exe",
|
|
49322
|
+
archive: "raw"
|
|
49323
|
+
}
|
|
49324
|
+
}
|
|
49325
|
+
},
|
|
49240
49326
|
"todoforai-browser": {
|
|
49241
49327
|
category: "development",
|
|
49242
49328
|
pkg: "@todoforai/browser",
|
|
@@ -49262,7 +49348,7 @@ var tool_catalog_default = {
|
|
|
49262
49348
|
pkg: "@todoforai/tfa-handoff",
|
|
49263
49349
|
installer: "npm",
|
|
49264
49350
|
label: "Handoff",
|
|
49265
|
-
capabilities: "Compact a conversation into a handoff document as a hidden TODO; pipe filter for `
|
|
49351
|
+
capabilities: "Compact a conversation into a handoff document as a hidden TODO; pipe filter for `todoforai-cli --inspect … | tfa-handoff … | todoforai-cli`",
|
|
49266
49352
|
description: "Compact a conversation into a handoff document for a fresh agent.",
|
|
49267
49353
|
versionCmd: "tfa-handoff --version 2>/dev/null | head -1",
|
|
49268
49354
|
installCmd: "bun add -g @todoforai/tfa-handoff",
|
|
@@ -49274,7 +49360,7 @@ var tool_catalog_default = {
|
|
|
49274
49360
|
installer: "npm",
|
|
49275
49361
|
label: "Vault",
|
|
49276
49362
|
capabilities: "The user's dedicated password manager and secure secret store — a zero-config TODOforAI vault CLI: put/get/patch/list/rm passwords & secrets in the user's KV v2 vault. Reuses bridge credentials (TODOFORAI_API_KEY / ~/.config/todoforai/credentials.json); derives vault URL from backend URL.",
|
|
49277
|
-
description: "This is the user's own dedicated password manager and secure secret store (encrypted KV vault), and the `whoami` transcript shows you're authorized to it, so reading and using the secrets it holds is the intended workflow. Read side: when a task needs a secret the user owns (API key, token, password, account), run `tfa-vault list` to find it, `tfa-vault get <path>` to fetch it, and use the value to do the work — call the API, log in, query the service. There's no need to ask the user for a value the vault already holds. Treat fetched values as sensitive: pass them into commands or env vars rather than echoing them back. Write side:
|
|
49363
|
+
description: "This is the user's own dedicated password manager and secure secret store (encrypted KV vault), and the `whoami` transcript shows you're authorized to it, so reading and using the secrets it holds is the intended workflow. Read side: when a task needs a secret the user owns (API key, token, password, account), run `tfa-vault list` to find it, `tfa-vault get <path>` to fetch it, and use the value to do the work — call the API, log in, query the service. There's no need to ask the user for a value the vault already holds. Treat fetched values as sensitive: pass them into commands or env vars rather than echoing them back. Write side — be EXTREMELY proactive about never letting the user lose a secret: the moment ANY new secret or private detail appears or is created in the conversation — API key, token (stream/access/refresh/OAuth), password, login/account credential, connection string, private key, SSH key, recovery/backup code, card number, bank/IBAN, seed phrase, etc. — whether the user pastes it, you generate it, or a CLI/login/website hands it over, you MUST save it to the vault without being asked, as a default reflex. Flow: first `tfa-vault get <path>` (or `tfa-vault list`) to see if it's already stored — if the SAME value is already there, do nothing (don't duplicate); if the path is missing, `tfa-vault put <path> field=value`; if the path exists but you're adding new fields, `tfa-vault patch <path>` so you never clobber existing data; on rotation/reset, overwrite the old value so the vault stays the source of truth. Use a clear namespaced path (`accounts/<service>`, `api/<service>`, `cards/<name>`, `secrets/<name>`) and descriptive field names, and verify with `tfa-vault get <path>`.",
|
|
49278
49364
|
versionCmd: "tfa-vault --version 2>/dev/null | head -1",
|
|
49279
49365
|
statusCmd: "tfa-vault whoami",
|
|
49280
49366
|
installCmd: "bun add -g @todoforai/vault",
|
|
@@ -49558,6 +49644,9 @@ function whichWithTools(name) {
|
|
|
49558
49644
|
}
|
|
49559
49645
|
return null;
|
|
49560
49646
|
}
|
|
49647
|
+
function binFileName(name) {
|
|
49648
|
+
return TOOL_CATALOG[name]?.binName ?? name;
|
|
49649
|
+
}
|
|
49561
49650
|
function isToolInstalled(name) {
|
|
49562
49651
|
const entry = TOOL_CATALOG[name];
|
|
49563
49652
|
if (!entry)
|
|
@@ -49567,14 +49656,17 @@ function isToolInstalled(name) {
|
|
|
49567
49656
|
const r = spawnSync("sh", ["-c", checkCmd], { stdio: "pipe", timeout: 5000 });
|
|
49568
49657
|
return r.status === 0;
|
|
49569
49658
|
}
|
|
49570
|
-
return whichWithTools(name) !== null;
|
|
49659
|
+
return whichWithTools(binFileName(name)) !== null;
|
|
49571
49660
|
}
|
|
49572
49661
|
function findReferencedTools(content) {
|
|
49573
49662
|
const stripped = content.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
|
|
49574
49663
|
return Object.keys(TOOL_CATALOG).filter((name) => {
|
|
49575
|
-
const
|
|
49576
|
-
|
|
49577
|
-
|
|
49664
|
+
const tokens = [name, TOOL_CATALOG[name].binName].filter((t) => !!t);
|
|
49665
|
+
return tokens.some((tok) => {
|
|
49666
|
+
const esc = tok.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
49667
|
+
const re = new RegExp(String.raw`(?:^|[|;&\n]|&&|\|\||` + String.raw`\$\(|` + "`" + String.raw`|xargs\s+|sudo\s+|env\s+)\s*` + esc + String.raw`\b(?!-)`, "m");
|
|
49668
|
+
return re.test(stripped);
|
|
49669
|
+
});
|
|
49578
49670
|
});
|
|
49579
49671
|
}
|
|
49580
49672
|
function findMissingTools(content) {
|
|
@@ -49589,7 +49681,8 @@ async function installBinary(name) {
|
|
|
49589
49681
|
const dir = binDir();
|
|
49590
49682
|
fs3.mkdirSync(dir, { recursive: true });
|
|
49591
49683
|
const [url, isArchive] = await urlFunc();
|
|
49592
|
-
const
|
|
49684
|
+
const fileName = binFileName(name);
|
|
49685
|
+
const destName = os3.platform() === "win32" ? `${fileName}.exe` : fileName;
|
|
49593
49686
|
const dest = path3.join(dir, destName);
|
|
49594
49687
|
const tmpPath = dest + ".tmp";
|
|
49595
49688
|
log2("info", `Downloading ${name} from ${url}`);
|
|
@@ -49599,7 +49692,7 @@ async function installBinary(name) {
|
|
|
49599
49692
|
const data = Buffer.from(await res.arrayBuffer());
|
|
49600
49693
|
fs3.writeFileSync(tmpPath, data);
|
|
49601
49694
|
if (isArchive) {
|
|
49602
|
-
const expectedNames = new Set([name, `${name}.exe`]);
|
|
49695
|
+
const expectedNames = new Set([name, `${name}.exe`, fileName, `${fileName}.exe`]);
|
|
49603
49696
|
if (url.endsWith(".tar.gz") || url.endsWith(".tgz")) {
|
|
49604
49697
|
await extractTarBinary(tmpPath, dest, expectedNames);
|
|
49605
49698
|
} else if (url.endsWith(".zip")) {
|
|
@@ -51590,6 +51683,13 @@ function capLineWidth(text, lineLimit) {
|
|
|
51590
51683
|
`).map((line) => line.length > lineLimit ? line.slice(0, lineLimit) + ` ...[+${line.length - lineLimit} chars]` : line).join(`
|
|
51591
51684
|
`);
|
|
51592
51685
|
}
|
|
51686
|
+
function collapseCarriageReturns(text) {
|
|
51687
|
+
if (!text.includes("\r"))
|
|
51688
|
+
return text;
|
|
51689
|
+
return text.split(`
|
|
51690
|
+
`).map((line) => line.includes("\r") ? line.split("\r").at(-1) ?? "" : line).join(`
|
|
51691
|
+
`);
|
|
51692
|
+
}
|
|
51593
51693
|
function formatTruncationNotice(totalLen, firstLimit, lastPart) {
|
|
51594
51694
|
const dropped = totalLen - firstLimit - lastPart.length;
|
|
51595
51695
|
return `
|
|
@@ -51690,15 +51790,18 @@ class OutputBuffer {
|
|
|
51690
51790
|
}
|
|
51691
51791
|
getOutput() {
|
|
51692
51792
|
if (!this.truncated)
|
|
51693
|
-
return
|
|
51694
|
-
return
|
|
51793
|
+
return this.format(this.firstPart);
|
|
51794
|
+
return this.format(this.firstPart) + `
|
|
51695
51795
|
|
|
51696
51796
|
... [truncated: showing first ${this.firstPart.length} and last ${this.lastPart.length} chars of ${this.totalLen} total] ...
|
|
51697
51797
|
|
|
51698
|
-
${
|
|
51798
|
+
${this.format(this.lastPart)}`;
|
|
51699
51799
|
}
|
|
51700
51800
|
getRawIfComplete() {
|
|
51701
|
-
return this.truncated ? null :
|
|
51801
|
+
return this.truncated ? null : this.format(this.firstPart);
|
|
51802
|
+
}
|
|
51803
|
+
format(part) {
|
|
51804
|
+
return capLineWidth(collapseCarriageReturns(part), this.lineLimit);
|
|
51702
51805
|
}
|
|
51703
51806
|
}
|
|
51704
51807
|
var processes = new Map;
|
|
@@ -53249,14 +53352,30 @@ class TODOforAIEdge {
|
|
|
53249
53352
|
}
|
|
53250
53353
|
startHeartbeat(ws2, onStale) {
|
|
53251
53354
|
this.stopHeartbeat();
|
|
53355
|
+
const intervalMs = 30000;
|
|
53356
|
+
const resumeGapMs = 60000;
|
|
53357
|
+
let lastTick = Date.now();
|
|
53252
53358
|
let pongReceived = true;
|
|
53253
53359
|
ws2.on("pong", () => {
|
|
53254
53360
|
pongReceived = true;
|
|
53255
53361
|
});
|
|
53256
53362
|
this.heartbeatTimer = setInterval(() => {
|
|
53363
|
+
const now = Date.now();
|
|
53364
|
+
const elapsed = now - lastTick;
|
|
53365
|
+
lastTick = now;
|
|
53366
|
+
if (elapsed > resumeGapMs) {
|
|
53367
|
+
console.log(`[warn] Heartbeat delayed ${Math.round(elapsed / 1000)}s (sleep/resume likely), reconnecting`);
|
|
53368
|
+
try {
|
|
53369
|
+
ws2.terminate();
|
|
53370
|
+
} catch {}
|
|
53371
|
+
onStale();
|
|
53372
|
+
return;
|
|
53373
|
+
}
|
|
53257
53374
|
if (!pongReceived) {
|
|
53258
53375
|
console.log("[warn] No pong received, terminating stale connection");
|
|
53259
|
-
|
|
53376
|
+
try {
|
|
53377
|
+
ws2.terminate();
|
|
53378
|
+
} catch {}
|
|
53260
53379
|
onStale();
|
|
53261
53380
|
return;
|
|
53262
53381
|
}
|
|
@@ -53264,7 +53383,7 @@ class TODOforAIEdge {
|
|
|
53264
53383
|
try {
|
|
53265
53384
|
ws2.ping();
|
|
53266
53385
|
} catch {}
|
|
53267
|
-
},
|
|
53386
|
+
}, intervalMs);
|
|
53268
53387
|
}
|
|
53269
53388
|
stopHeartbeat() {
|
|
53270
53389
|
if (this.heartbeatTimer) {
|