@todoforai/edge 0.13.14 → 0.13.15
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 +123 -43
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -48790,7 +48790,6 @@ var tool_catalog_default = {
|
|
|
48790
48790
|
pkg: "tiktok-uploader",
|
|
48791
48791
|
installer: "pip",
|
|
48792
48792
|
label: "TikTok",
|
|
48793
|
-
statusCmd: "python3 -c 'import tiktok_uploader' 2>&1",
|
|
48794
48793
|
loginCmd: "npx @todoforai/tiktok-cookie-helper",
|
|
48795
48794
|
credentialPaths: [
|
|
48796
48795
|
"~/.tiktok/cookies.txt"
|
|
@@ -48806,7 +48805,6 @@ var tool_catalog_default = {
|
|
|
48806
48805
|
label: "Instagram",
|
|
48807
48806
|
capabilities: "Upload photos & reels, post stories, send DMs, like & comment, manage followers",
|
|
48808
48807
|
description: 'Use for Instagram automation: upload photos/reels, post stories, DMs, like/comment, follower management. Python library; call via `python3 -c "from instagrapi import Client; ..."`.',
|
|
48809
|
-
statusCmd: 'python3 -c "import instagrapi" 2>/dev/null',
|
|
48810
48808
|
versionCmd: "pip show instagrapi 2>/dev/null | grep -oP 'Version: \\K.*'"
|
|
48811
48809
|
},
|
|
48812
48810
|
mudslide: {
|
|
@@ -48828,7 +48826,6 @@ var tool_catalog_default = {
|
|
|
48828
48826
|
pkg: "telegram-send",
|
|
48829
48827
|
installer: "pip",
|
|
48830
48828
|
label: "Telegram",
|
|
48831
|
-
statusCmd: "test -f ~/.config/telegram-send/telegram-send.conf && echo 'configured'",
|
|
48832
48829
|
loginCmd: "telegram-send --configure",
|
|
48833
48830
|
credentialPaths: [
|
|
48834
48831
|
"~/.config/telegram-send/telegram-send.conf"
|
|
@@ -49052,7 +49049,6 @@ var tool_catalog_default = {
|
|
|
49052
49049
|
pkg: "@shopify/cli",
|
|
49053
49050
|
installer: "npm",
|
|
49054
49051
|
label: "Shopify",
|
|
49055
|
-
statusCmd: "test -f ~/.config/shopify/config.json && echo 'authenticated'",
|
|
49056
49052
|
loginCmd: "shopify auth login",
|
|
49057
49053
|
credentialPaths: [
|
|
49058
49054
|
"~/.config/shopify/config.json"
|
|
@@ -49152,7 +49148,7 @@ var tool_catalog_default = {
|
|
|
49152
49148
|
"~/.config/rclone/rclone.conf"
|
|
49153
49149
|
],
|
|
49154
49150
|
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).",
|
|
49155
|
-
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
|
|
49151
|
+
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 lsf <remote>:<path>`, `rclone cat <remote>:<file>`, `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>`. Note: FUSE `mount` needs /dev/fuse + CAP_SYS_ADMIN — works on VM sandboxes and bridge devices, but NOT in lite sandboxes; there use `lsf`/`cat`/`copy`/`sync` (HTTPS-only) instead. See `subProviders` for per-provider connect commands.",
|
|
49156
49152
|
subProviders: {
|
|
49157
49153
|
gdrive: {
|
|
49158
49154
|
label: "Google Drive",
|
|
@@ -49198,7 +49194,7 @@ var tool_catalog_default = {
|
|
|
49198
49194
|
installer: "pip",
|
|
49199
49195
|
label: "PDF",
|
|
49200
49196
|
capabilities: "PDF text editing, extraction, merge, split",
|
|
49201
|
-
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)`.
|
|
49197
|
+
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)`.",
|
|
49202
49198
|
versionCmd: "python3 -c 'import pymupdf; print(pymupdf.__version__)' 2>/dev/null",
|
|
49203
49199
|
preinstallCloud: true
|
|
49204
49200
|
},
|
|
@@ -49209,23 +49205,9 @@ var tool_catalog_default = {
|
|
|
49209
49205
|
preinstallCloud: true,
|
|
49210
49206
|
label: "Browser",
|
|
49211
49207
|
capabilities: "Headless browser automation, web scraping, accessibility tree snapshots, screenshots, PDF generation",
|
|
49212
|
-
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
|
|
49208
|
+
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. 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>.",
|
|
49213
49209
|
versionCmd: "agent-browser --version 2>/dev/null | head -1"
|
|
49214
49210
|
},
|
|
49215
|
-
firecrawl: {
|
|
49216
|
-
category: "development",
|
|
49217
|
-
pkg: "firecrawl-cli",
|
|
49218
|
-
installer: "npm",
|
|
49219
|
-
label: "Firecrawl",
|
|
49220
|
-
statusCmd: "firecrawl view-config 2>&1 | grep -q 'Authenticated' && echo authenticated",
|
|
49221
|
-
loginCmd: "firecrawl login",
|
|
49222
|
-
credentialPaths: [
|
|
49223
|
-
"~/.config/firecrawl-cli"
|
|
49224
|
-
],
|
|
49225
|
-
capabilities: "Web scraping, crawling, search, map URLs, parse local HTML/PDF/DOCX to markdown, AI agent extraction",
|
|
49226
|
-
description: "Use for web → markdown at scale: single pages (`firecrawl scrape <url>`), whole sites (`firecrawl crawl <url>`), URL discovery (`firecrawl map <url>`), web search (`firecrawl search <query>`), local doc conversion (`firecrawl parse <file.pdf|docx|html|xlsx>`), AI-guided extraction (`firecrawl agent <prompt>`). Prefer over `curl` when you want markdown not HTML. Auth: `firecrawl login` or FIRECRAWL_API_KEY.",
|
|
49227
|
-
versionCmd: "firecrawl --version 2>/dev/null | head -1"
|
|
49228
|
-
},
|
|
49229
49211
|
"todoforai-browser": {
|
|
49230
49212
|
category: "development",
|
|
49231
49213
|
pkg: "@todoforai/browser",
|
|
@@ -49266,7 +49248,6 @@ var tool_catalog_default = {
|
|
|
49266
49248
|
capabilities: "Explore a codebase as a real TODO: read-only agent maps structure, surfaces relevant files, streams findings to terminal",
|
|
49267
49249
|
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.',
|
|
49268
49250
|
versionCmd: "tfa-explore --version 2>/dev/null | head -1",
|
|
49269
|
-
statusCmd: "tfa-explore whoami",
|
|
49270
49251
|
installCmd: "bun add -g @todoforai/tfa-explore",
|
|
49271
49252
|
preinstall: true,
|
|
49272
49253
|
preinstallCloud: true,
|
|
@@ -49280,7 +49261,6 @@ var tool_catalog_default = {
|
|
|
49280
49261
|
capabilities: "Review a git diff as a real TODO: read-only agent assesses goal, finds issues, suggests simpler approaches",
|
|
49281
49262
|
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.',
|
|
49282
49263
|
versionCmd: "tfa-review --version 2>/dev/null | head -1",
|
|
49283
|
-
statusCmd: "tfa-review whoami",
|
|
49284
49264
|
installCmd: "bun add -g @todoforai/tfa-review",
|
|
49285
49265
|
preinstall: true,
|
|
49286
49266
|
preinstallCloud: true,
|
|
@@ -49294,7 +49274,6 @@ var tool_catalog_default = {
|
|
|
49294
49274
|
capabilities: "Summarize files or piped input as a real TODO with no-tools sub-agent",
|
|
49295
49275
|
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.',
|
|
49296
49276
|
versionCmd: "tfa-summary --version 2>/dev/null | head -1",
|
|
49297
|
-
statusCmd: "tfa-summary whoami",
|
|
49298
49277
|
installCmd: "bun add -g @todoforai/tfa-summary",
|
|
49299
49278
|
internal: true
|
|
49300
49279
|
},
|
|
@@ -51389,12 +51368,7 @@ import { spawn as nodeSpawn } from "child_process";
|
|
|
51389
51368
|
// src/shell-pause-detector.ts
|
|
51390
51369
|
import os5 from "os";
|
|
51391
51370
|
import { readFile, readlink } from "fs/promises";
|
|
51392
|
-
|
|
51393
|
-
class NullDetector {
|
|
51394
|
-
watch() {
|
|
51395
|
-
return { cancel: () => {}, reset: () => {} };
|
|
51396
|
-
}
|
|
51397
|
-
}
|
|
51371
|
+
var ECHO = 8;
|
|
51398
51372
|
var READ_SYSCALL_NR = {
|
|
51399
51373
|
x64: 0,
|
|
51400
51374
|
arm64: 63,
|
|
@@ -51404,23 +51378,29 @@ var READ_SYSCALL_NR = {
|
|
|
51404
51378
|
var READ_NR = READ_SYSCALL_NR[process.arch] ?? -1;
|
|
51405
51379
|
var POLL_MS = 250;
|
|
51406
51380
|
var GRACE_TICKS = 2;
|
|
51381
|
+
var IS_LINUX2 = os5.platform() === "linux" && READ_NR >= 0;
|
|
51407
51382
|
|
|
51408
|
-
class
|
|
51409
|
-
watch(pid, onPaused) {
|
|
51383
|
+
class PtyPauseDetector {
|
|
51384
|
+
watch(pid, onPaused, terminal) {
|
|
51410
51385
|
let pausedTicks = 0;
|
|
51411
51386
|
let signalled = false;
|
|
51412
51387
|
let cancelled = false;
|
|
51413
51388
|
let rootStdin = null;
|
|
51414
|
-
|
|
51415
|
-
|
|
51416
|
-
|
|
51389
|
+
if (IS_LINUX2)
|
|
51390
|
+
readFdTarget(pid, 0).then((t) => {
|
|
51391
|
+
rootStdin = t;
|
|
51392
|
+
});
|
|
51417
51393
|
const tick = async () => {
|
|
51418
51394
|
if (cancelled)
|
|
51419
51395
|
return;
|
|
51420
|
-
|
|
51421
|
-
|
|
51422
|
-
|
|
51423
|
-
|
|
51396
|
+
let blocked = terminal != null && !(terminal.localFlags & ECHO);
|
|
51397
|
+
if (!blocked && IS_LINUX2) {
|
|
51398
|
+
const fgPid = await getForegroundPid(pid);
|
|
51399
|
+
const sc2 = fgPid != null ? await readSyscall(fgPid) : null;
|
|
51400
|
+
const leafTarget = sc2 && sc2.nr === READ_NR && sc2.fd >= 0 && fgPid != null ? await readFdTarget(fgPid, sc2.fd) : null;
|
|
51401
|
+
blocked = isTerminalReadPause(sc2, leafTarget, rootStdin);
|
|
51402
|
+
}
|
|
51403
|
+
if (blocked) {
|
|
51424
51404
|
if (!signalled && ++pausedTicks >= GRACE_TICKS) {
|
|
51425
51405
|
signalled = true;
|
|
51426
51406
|
onPaused();
|
|
@@ -51491,7 +51471,7 @@ async function readFdTarget(pid, fd3) {
|
|
|
51491
51471
|
return null;
|
|
51492
51472
|
}
|
|
51493
51473
|
}
|
|
51494
|
-
var pauseDetector =
|
|
51474
|
+
var pauseDetector = new PtyPauseDetector;
|
|
51495
51475
|
|
|
51496
51476
|
// src/shell.ts
|
|
51497
51477
|
var IS_WIN = os6.platform() === "win32";
|
|
@@ -51651,13 +51631,13 @@ async function executeBlock(blockId, content, send, todoId, messageId, timeout,
|
|
|
51651
51631
|
}
|
|
51652
51632
|
}, timeout * 1000);
|
|
51653
51633
|
let cancelPauseWatch = null;
|
|
51654
|
-
const startPauseWatch = (pid) => {
|
|
51634
|
+
const startPauseWatch = (pid, terminal) => {
|
|
51655
51635
|
if (!keepAliveOnTimeout)
|
|
51656
51636
|
return;
|
|
51657
51637
|
const watcher = pauseDetector.watch(pid, () => {
|
|
51658
51638
|
if (processes.has(blockId))
|
|
51659
51639
|
resolveAlive();
|
|
51660
|
-
});
|
|
51640
|
+
}, terminal);
|
|
51661
51641
|
cancelPauseWatch = watcher.cancel;
|
|
51662
51642
|
const h = processes.get(blockId);
|
|
51663
51643
|
if (h)
|
|
@@ -51707,7 +51687,7 @@ async function executeBlock(blockId, content, send, todoId, messageId, timeout,
|
|
|
51707
51687
|
const handle = { terminal, proc, pid: proc.pid };
|
|
51708
51688
|
processes.set(blockId, handle);
|
|
51709
51689
|
const timer = startTimeout();
|
|
51710
|
-
startPauseWatch(proc.pid);
|
|
51690
|
+
startPauseWatch(proc.pid, terminal);
|
|
51711
51691
|
proc.exited.then((code) => {
|
|
51712
51692
|
terminal.close();
|
|
51713
51693
|
onExit(code ?? -1, timer);
|
|
@@ -52465,6 +52445,106 @@ register("read_file_base64", async (args) => {
|
|
|
52465
52445
|
const data = fs11.readFileSync(fullPath);
|
|
52466
52446
|
return { path: fullPath, base64: data.toString("base64"), bytes: data.length };
|
|
52467
52447
|
});
|
|
52448
|
+
register("search_files", async (args) => {
|
|
52449
|
+
const { pattern, path: p10 = ".", cwd = args.root_path ?? "", head = 100, max_count = 5, glob: globPattern = "", ignore_case = true } = args;
|
|
52450
|
+
const { execSync: execWhich } = await import("child_process");
|
|
52451
|
+
const whichCmd = process.platform === "win32" ? "where" : "which";
|
|
52452
|
+
const which = (bin) => {
|
|
52453
|
+
try {
|
|
52454
|
+
return execWhich(`${whichCmd} ${bin}`, { encoding: "utf-8" }).trim().split(`
|
|
52455
|
+
`)[0].trim();
|
|
52456
|
+
} catch {
|
|
52457
|
+
return null;
|
|
52458
|
+
}
|
|
52459
|
+
};
|
|
52460
|
+
let rgPath = which("rg");
|
|
52461
|
+
if (!rgPath) {
|
|
52462
|
+
await ensureTool("rg");
|
|
52463
|
+
rgPath = which("rg");
|
|
52464
|
+
}
|
|
52465
|
+
let searchPath = p10.replace(/^~/, process.env.HOME || "~");
|
|
52466
|
+
if (!path8.isAbsolute(searchPath) && cwd)
|
|
52467
|
+
searchPath = path8.join(cwd, searchPath);
|
|
52468
|
+
searchPath = path8.resolve(searchPath);
|
|
52469
|
+
if (!fs11.existsSync(searchPath))
|
|
52470
|
+
throw new Error(`Search path does not exist: ${searchPath}`);
|
|
52471
|
+
let cmd;
|
|
52472
|
+
if (rgPath) {
|
|
52473
|
+
cmd = [rgPath, "--no-heading", "--line-number", "--color=never"];
|
|
52474
|
+
if (ignore_case)
|
|
52475
|
+
cmd.push("--ignore-case");
|
|
52476
|
+
if (max_count > 0)
|
|
52477
|
+
cmd.push(`--max-count=${max_count}`);
|
|
52478
|
+
if (globPattern)
|
|
52479
|
+
cmd.push("--glob", globPattern);
|
|
52480
|
+
cmd.push(pattern, searchPath);
|
|
52481
|
+
} else {
|
|
52482
|
+
console.warn("[search_files] ripgrep (rg) not found, falling back to grep");
|
|
52483
|
+
const grepPath = which("grep") || "grep";
|
|
52484
|
+
cmd = [grepPath, "-rn", "--color=never"];
|
|
52485
|
+
if (ignore_case)
|
|
52486
|
+
cmd.push("-i");
|
|
52487
|
+
if (max_count > 0)
|
|
52488
|
+
cmd.push(`--max-count=${max_count}`);
|
|
52489
|
+
if (globPattern) {
|
|
52490
|
+
cmd.push(`--include=${globPattern}`);
|
|
52491
|
+
}
|
|
52492
|
+
cmd.push(pattern, searchPath);
|
|
52493
|
+
}
|
|
52494
|
+
const { spawn: spawnChild } = await import("child_process");
|
|
52495
|
+
const { stdout, stderr, code } = await new Promise((resolve) => {
|
|
52496
|
+
const child = spawnChild(cmd[0], cmd.slice(1));
|
|
52497
|
+
let out = "", err2 = "";
|
|
52498
|
+
child.stdout?.on("data", (d) => {
|
|
52499
|
+
out += d.toString();
|
|
52500
|
+
});
|
|
52501
|
+
child.stderr?.on("data", (d) => {
|
|
52502
|
+
err2 += d.toString();
|
|
52503
|
+
});
|
|
52504
|
+
child.on("close", (exitCode) => resolve({ stdout: out, stderr: err2, code: exitCode ?? 1 }));
|
|
52505
|
+
});
|
|
52506
|
+
if (code === 0) {
|
|
52507
|
+
let output = stdout;
|
|
52508
|
+
const lines = output.split(`
|
|
52509
|
+
`).filter((l) => l.trim());
|
|
52510
|
+
if (lines.length > head) {
|
|
52511
|
+
output = lines.slice(0, head).join(`
|
|
52512
|
+
`) + `
|
|
52513
|
+
... (${lines.length - head} more matches truncated)`;
|
|
52514
|
+
}
|
|
52515
|
+
if ((cwd || searchPath) && output) {
|
|
52516
|
+
const searchBase = searchPath && fs11.existsSync(searchPath) && fs11.statSync(searchPath).isDirectory() ? searchPath : path8.dirname(searchPath);
|
|
52517
|
+
const bases = Array.from(new Set([cwd, searchBase].filter(Boolean)));
|
|
52518
|
+
const lines2 = output.split(`
|
|
52519
|
+
`).map((line) => {
|
|
52520
|
+
if (line.includes(":")) {
|
|
52521
|
+
const colonIdx = line.indexOf(":");
|
|
52522
|
+
let filePart = line.slice(0, colonIdx);
|
|
52523
|
+
const rest = line.slice(colonIdx);
|
|
52524
|
+
try {
|
|
52525
|
+
const candidates = [filePart, ...bases.map((b) => path8.relative(b, filePart))].filter((p11) => (p11.match(/\.\.\//g) || []).length <= 2);
|
|
52526
|
+
filePart = candidates.reduce((a, b) => a.length <= b.length ? a : b, filePart);
|
|
52527
|
+
} catch {}
|
|
52528
|
+
let fullLine = filePart + rest;
|
|
52529
|
+
if (fullLine.length > 300) {
|
|
52530
|
+
fullLine = fullLine.slice(0, 300) + "...";
|
|
52531
|
+
}
|
|
52532
|
+
return fullLine;
|
|
52533
|
+
}
|
|
52534
|
+
return line;
|
|
52535
|
+
});
|
|
52536
|
+
output = lines2.join(`
|
|
52537
|
+
`);
|
|
52538
|
+
}
|
|
52539
|
+
if (output.length > 1e5)
|
|
52540
|
+
output = output.slice(0, 1e5) + `
|
|
52541
|
+
... (output truncated)`;
|
|
52542
|
+
return { result: output };
|
|
52543
|
+
}
|
|
52544
|
+
if (code === 1)
|
|
52545
|
+
return { result: "No matches found." };
|
|
52546
|
+
throw new Error(`search error (exit ${code}): ${stderr}`);
|
|
52547
|
+
});
|
|
52468
52548
|
register("download_attachment", async (args, client) => {
|
|
52469
52549
|
if (!client)
|
|
52470
52550
|
throw new Error("Client instance required");
|