@todoforai/edge 0.13.21 → 0.13.23

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.
Files changed (2) hide show
  1. package/dist/index.js +184 -33
  2. 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
- const endpoint = projectId ? `/api/v1/projects/${projectId}/todos` : "/api/v1/todos";
48196
- return this.request("GET", endpoint);
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}`);
@@ -48245,6 +48267,12 @@ class ApiClient {
48245
48267
  listEdges() {
48246
48268
  return this.request("GET", "/api/v1/edges");
48247
48269
  }
48270
+ setAgentDeviceConfig(agentId, agentSettingsId, deviceId, config) {
48271
+ return this.request("PUT", `/api/v1/agents/${agentId}/device-config`, { agentSettingsId, deviceId, config });
48272
+ }
48273
+ listDevices() {
48274
+ return this.request("GET", "/api/v1/devices");
48275
+ }
48248
48276
  startFromTemplate(projectId, templateId, opts) {
48249
48277
  return this.request("POST", `/api/v1/projects/${projectId}/todos/from-template`, {
48250
48278
  templateId,
@@ -48767,12 +48795,13 @@ var tool_catalog_default = {
48767
48795
  installer: "npm",
48768
48796
  label: "Gmail",
48769
48797
  statusCmd: "zele whoami",
48770
- loginCmd: "zele login",
48798
+ loginCmd: "zele login --method google",
48771
48799
  credentialPaths: [
48772
48800
  "~/.zele/sqlite.db"
48773
48801
  ],
48774
48802
  capabilities: "Read inbox, search/send/reply email, email addresses — multi-account Gmail, OAuth browser login for setup.",
48775
- versionCmd: "zele --version 2>/dev/null | head -1"
48803
+ versionCmd: "zele --version 2>/dev/null | head -1",
48804
+ preinstallCloud: true
48776
48805
  },
48777
48806
  xurl: {
48778
48807
  category: "social",
@@ -48802,6 +48831,20 @@ var tool_catalog_default = {
48802
48831
  description: "Authenticated LinkedIn Voyager API CLI. Cookie auth: `linkedin-api auth <li_at> <jsessionid>` (from browser DevTools).",
48803
48832
  versionCmd: "linkedin-api --version 2>/dev/null | head -1"
48804
48833
  },
48834
+ "figma-api": {
48835
+ category: "development",
48836
+ pkg: "@todoforai/figma-api",
48837
+ installer: "npm",
48838
+ label: "Figma",
48839
+ statusCmd: `figma-api me 2>/dev/null | grep -oP '"email":\\s*"\\K[^"]+' | head -1`,
48840
+ loginCmd: "figma-api auth",
48841
+ credentialPaths: [
48842
+ "~/.config/figma-api/credentials.json"
48843
+ ],
48844
+ capabilities: "Read+write Figma REST API: files/nodes JSON, render images (png/svg/pdf), comments & reactions, components/styles/variables, dev resources, webhooks, projects, library analytics — full subcommand-per-endpoint CLI.",
48845
+ description: "Authenticated Figma REST API CLI. Auth: `figma-api auth <token>` (PAT from figma.com/settings). File commands take a file key or Figma URL. Read files/nodes/images/comments/components/styles/variables; write comments/dev-resources/webhooks. Canvas writes (frames/text/shapes) use `figma-api run` via the plugin bridge. Run `figma-api <cmd> --help` for params/scopes.",
48846
+ versionCmd: "figma-api --version 2>/dev/null | head -1"
48847
+ },
48805
48848
  "tiktok-uploader": {
48806
48849
  category: "social",
48807
48850
  pkg: "tiktok-uploader",
@@ -48812,7 +48855,8 @@ var tool_catalog_default = {
48812
48855
  "~/.tiktok/cookies.txt"
48813
48856
  ],
48814
48857
  capabilities: "Upload videos, batch uploads, schedule posts, custom covers, hashtags & mentions",
48815
- versionCmd: "pip show tiktok-uploader 2>/dev/null | grep -oP 'Version: \\K.*'"
48858
+ versionCmd: "pip show tiktok-uploader 2>/dev/null | grep -oP 'Version: \\K.*'",
48859
+ statusCmd: "test -f ~/.tiktok/cookies.txt && echo authenticated"
48816
48860
  },
48817
48861
  instagrapi: {
48818
48862
  category: "social",
@@ -48820,7 +48864,8 @@ var tool_catalog_default = {
48820
48864
  installer: "pip",
48821
48865
  label: "Instagram",
48822
48866
  capabilities: "Upload photos & reels, post stories, send DMs, like & comment, manage followers",
48823
- versionCmd: "pip show instagrapi 2>/dev/null | grep -oP 'Version: \\K.*'"
48867
+ versionCmd: "pip show instagrapi 2>/dev/null | grep -oP 'Version: \\K.*'",
48868
+ statusCmd: "python3 -c 'import instagrapi' 2>/dev/null && echo installed"
48824
48869
  },
48825
48870
  mudslide: {
48826
48871
  category: "messaging",
@@ -48835,7 +48880,7 @@ var tool_catalog_default = {
48835
48880
  capabilities: "Send messages, send images & files, send locations & polls, group management, QR code login",
48836
48881
  versionCmd: "mudslide --version 2>/dev/null | head -1"
48837
48882
  },
48838
- slack: {
48883
+ "slack-cli": {
48839
48884
  category: "development",
48840
48885
  pkg: "slack-cli",
48841
48886
  installer: "binary",
@@ -48887,7 +48932,8 @@ var tool_catalog_default = {
48887
48932
  "~/.config/telegram-send/telegram-send.conf"
48888
48933
  ],
48889
48934
  capabilities: "Send messages, send files & images, send video & audio, Markdown/HTML formatting, channel & group support",
48890
- versionCmd: "telegram-send --version 2>/dev/null | head -1"
48935
+ versionCmd: "telegram-send --version 2>/dev/null | head -1",
48936
+ statusCmd: "test -f ~/.config/telegram-send/telegram-send.conf && echo authenticated"
48891
48937
  },
48892
48938
  "meta-ads": {
48893
48939
  category: "marketing",
@@ -48904,7 +48950,12 @@ var tool_catalog_default = {
48904
48950
  installer: "npm",
48905
48951
  label: "ElevenLabs",
48906
48952
  capabilities: "Text-to-speech, voice cloning, multiple languages. Needs ELEVENLABS_API_KEY.",
48907
- versionCmd: "elevenlabs-api --version 2>/dev/null | head -1"
48953
+ versionCmd: "elevenlabs-api --version 2>/dev/null | head -1",
48954
+ statusCmd: 'test -f ~/.config/todoforai/elevenlabs.json && echo authenticated || test -n "$ELEVENLABS_API_KEY" && echo authenticated',
48955
+ loginCmd: "elevenlabs-api auth <api_key>",
48956
+ credentialPaths: [
48957
+ "~/.config/todoforai/elevenlabs.json"
48958
+ ]
48908
48959
  },
48909
48960
  "codex-imagegen-api": {
48910
48961
  category: "media",
@@ -48912,7 +48963,11 @@ var tool_catalog_default = {
48912
48963
  installer: "npm",
48913
48964
  label: "Image Gen",
48914
48965
  capabilities: "AI image generation & editing via TODOFORAI backend (gpt-image)",
48915
- versionCmd: "codex-imagegen-api --version 2>/dev/null | head -1"
48966
+ versionCmd: "codex-imagegen-api --version 2>/dev/null | head -1",
48967
+ statusCmd: 'test -f ~/.config/todoforai/credentials.json && echo authenticated || test -n "$TODOFORAI_API_KEY" && echo authenticated',
48968
+ credentialPaths: [
48969
+ "~/.config/todoforai/credentials.json"
48970
+ ]
48916
48971
  },
48917
48972
  "suno-api": {
48918
48973
  category: "media",
@@ -48920,7 +48975,8 @@ var tool_catalog_default = {
48920
48975
  installer: "npm",
48921
48976
  label: "Suno",
48922
48977
  capabilities: "AI music generation, custom lyrics, multiple genres. Needs SUNO_API_KEY.",
48923
- versionCmd: "suno-api --version 2>/dev/null | head -1"
48978
+ versionCmd: "suno-api --version 2>/dev/null | head -1",
48979
+ statusCmd: 'test -n "$SUNO_API_KEY" && echo authenticated'
48924
48980
  },
48925
48981
  ntn: {
48926
48982
  category: "utility",
@@ -48941,7 +48997,12 @@ var tool_catalog_default = {
48941
48997
  installer: "npm",
48942
48998
  label: "Perplexity",
48943
48999
  capabilities: "AI-powered web search, chat completions, async chat, embeddings and agent runs",
48944
- versionCmd: "perplexity-api --version 2>/dev/null | head -1"
49000
+ versionCmd: "perplexity-api --version 2>/dev/null | head -1",
49001
+ statusCmd: 'test -f ~/.config/todoforai/perplexity.json && echo authenticated || test -n "$PERPLEXITY_API_KEY" && echo authenticated',
49002
+ loginCmd: "perplexity-api auth <api_key>",
49003
+ credentialPaths: [
49004
+ "~/.config/todoforai/perplexity.json"
49005
+ ]
48945
49006
  },
48946
49007
  gh: {
48947
49008
  category: "development",
@@ -49094,7 +49155,8 @@ var tool_catalog_default = {
49094
49155
  "~/.config/shopify/config.json"
49095
49156
  ],
49096
49157
  capabilities: "Theme development, app scaffolding, store management",
49097
- versionCmd: "shopify version 2>/dev/null | head -1"
49158
+ versionCmd: "shopify version 2>/dev/null | head -1",
49159
+ statusCmd: "test -f ~/.config/shopify/config.json && echo authenticated"
49098
49160
  },
49099
49161
  "shop-app": {
49100
49162
  category: "ecommerce",
@@ -49111,7 +49173,8 @@ var tool_catalog_default = {
49111
49173
  installer: "npm",
49112
49174
  label: "Datadog",
49113
49175
  capabilities: "CI test visibility, sourcemap uploads, deployment tracking. Needs DATADOG_API_KEY.",
49114
- versionCmd: "datadog-ci version 2>/dev/null | head -1"
49176
+ versionCmd: "datadog-ci version 2>/dev/null | head -1",
49177
+ statusCmd: 'test -n "$DATADOG_API_KEY" && echo authenticated'
49115
49178
  },
49116
49179
  "sentry-cli": {
49117
49180
  category: "monitoring",
@@ -49132,10 +49195,11 @@ var tool_catalog_default = {
49132
49195
  pkg: "@todoforai/cli",
49133
49196
  installer: "bun",
49134
49197
  label: "TODOforAI",
49198
+ binName: "todoforai-cli",
49135
49199
  capabilities: "Create/list/inspect/update TODOs, run templates & workflows, platform API access",
49136
- description: '`todoai list` (`--status open`) to browse, `todoai "prompt"` to create, `--inspect <id>` to read a chat log, `status/addmessage/delete` to manage, `--template` for registry workflows.',
49200
+ 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.",
49137
49201
  installCmd: "bun add -g @todoforai/cli",
49138
- versionCmd: "todoai --version 2>/dev/null | head -1",
49202
+ versionCmd: "todoforai-cli --version 2>/dev/null | head -1",
49139
49203
  internal: true
49140
49204
  },
49141
49205
  newman: {
@@ -49232,6 +49296,20 @@ var tool_catalog_default = {
49232
49296
  },
49233
49297
  versionCmd: "rclone version 2>/dev/null | head -1"
49234
49298
  },
49299
+ rdt: {
49300
+ category: "social",
49301
+ pkg: "rdt-cli",
49302
+ installer: "pip",
49303
+ label: "Reddit",
49304
+ statusCmd: `rdt status 2>&1 | grep -oP 'username":\\s*"\\K[^"]+' | head -1`,
49305
+ loginCmd: "rdt login",
49306
+ credentialPaths: [
49307
+ "~/.config/rdt-cli/credential.json"
49308
+ ],
49309
+ capabilities: "Search & browse subreddits, read posts & comments, post comments, upvote & save, view user profiles & comments, export results — Reddit in your terminal.",
49310
+ 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).',
49311
+ versionCmd: "rdt --version 2>/dev/null | head -1"
49312
+ },
49235
49313
  pymupdf: {
49236
49314
  category: "utility",
49237
49315
  pkg: "pymupdf",
@@ -49243,7 +49321,7 @@ var tool_catalog_default = {
49243
49321
  },
49244
49322
  "agent-browser": {
49245
49323
  category: "development",
49246
- pkg: "agent-browser",
49324
+ pkg: "agent-browser@0.30.1",
49247
49325
  installer: "npm",
49248
49326
  preinstallCloud: true,
49249
49327
  label: "Browser",
@@ -49251,6 +49329,41 @@ var tool_catalog_default = {
49251
49329
  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.",
49252
49330
  versionCmd: "agent-browser --version 2>/dev/null | head -1"
49253
49331
  },
49332
+ "browser-manager-cli": {
49333
+ category: "development",
49334
+ pkg: "browser-manager-cli",
49335
+ installer: "binary",
49336
+ binName: "browser-manager-cli",
49337
+ preinstallCloud: true,
49338
+ label: "Browser Manager",
49339
+ statusCmd: "browser-manager-cli status",
49340
+ loginCmd: "browser-manager-cli login",
49341
+ capabilities: "On-demand cloud Chromium sessions exposed as CDP endpoints for agent-browser; create/list/get/delete sessions, hibernate/restore, health, per-session residential/datacenter proxy (stealth Chromium via CloakBrowser)",
49342
+ 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). `browser-manager-cli create` prints one line: status, cdpUrl, dimensions (e.g. `active\twss://...?token=...\t1280x720`) — copy the cdpUrl straight into `agent-browser connect '<cdp_url>'`. `list`/`get <id>` show the same concise line per session; `hibernated-list` shows paused ones. `delete <id>` / `delete-all` to clean up, `hibernate`/`restore` to pause/resume. Add `--json` to any command for raw machine-readable output. Sessions run a stealth-patched Chromium (CloakBrowser: navigator.webdriver/UA/plugins/canvas/WebGL spoofed at the binary level), so they read as a real desktop browser. For sites that block datacenter IPs (e.g. login flows), route a session through a residential/ISP proxy: `create --proxy socks5://host:port [--proxy-user U --proxy-pass P]` — the server geoip-matches timezone/locale to the proxy exit IP, and the identity is kept across hibernate/restore. Use this on the cloud VM instead of launching a local browser.",
49343
+ versionCmd: "browser-manager-cli version 2>/dev/null | head -1",
49344
+ binary: {
49345
+ "linux-x86_64": {
49346
+ url: "https://github.com/todoforai/browser-manager/releases/download/cli-v0.1.1/browser-manager-cli-linux-x86_64",
49347
+ archive: "raw"
49348
+ },
49349
+ "linux-aarch64": {
49350
+ url: "https://github.com/todoforai/browser-manager/releases/download/cli-v0.1.1/browser-manager-cli-linux-aarch64",
49351
+ archive: "raw"
49352
+ },
49353
+ "darwin-x86_64": {
49354
+ url: "https://github.com/todoforai/browser-manager/releases/download/cli-v0.1.1/browser-manager-cli-darwin-x86_64",
49355
+ archive: "raw"
49356
+ },
49357
+ "darwin-aarch64": {
49358
+ url: "https://github.com/todoforai/browser-manager/releases/download/cli-v0.1.1/browser-manager-cli-darwin-aarch64",
49359
+ archive: "raw"
49360
+ },
49361
+ "windows-x86_64": {
49362
+ url: "https://github.com/todoforai/browser-manager/releases/download/cli-v0.1.1/browser-manager-cli-windows-x86_64.exe",
49363
+ archive: "raw"
49364
+ }
49365
+ }
49366
+ },
49254
49367
  "todoforai-browser": {
49255
49368
  category: "development",
49256
49369
  pkg: "@todoforai/browser",
@@ -49269,18 +49382,20 @@ var tool_catalog_default = {
49269
49382
  capabilities: "Spawn a TODO for AI sub-agent (FluidAgent) from the CLI; pipe stdin in, get an answer out",
49270
49383
  versionCmd: "todoforai-subagent --version 2>/dev/null | head -1",
49271
49384
  installCmd: "bun add -g @todoforai/subagent",
49272
- internal: true
49385
+ internal: true,
49386
+ statusCmd: "todoforai-subagent whoami 2>/dev/null | grep -v 'not logged'"
49273
49387
  },
49274
49388
  "tfa-handoff": {
49275
49389
  category: "development",
49276
49390
  pkg: "@todoforai/tfa-handoff",
49277
49391
  installer: "npm",
49278
49392
  label: "Handoff",
49279
- capabilities: "Compact a conversation into a handoff document as a hidden TODO; pipe filter for `todoai --inspect … | tfa-handoff … | todoai`",
49393
+ capabilities: "Compact a conversation into a handoff document as a hidden TODO; pipe filter for `todoforai-cli --inspect … | tfa-handoff … | todoforai-cli`",
49280
49394
  description: "Compact a conversation into a handoff document for a fresh agent.",
49281
49395
  versionCmd: "tfa-handoff --version 2>/dev/null | head -1",
49282
49396
  installCmd: "bun add -g @todoforai/tfa-handoff",
49283
- internal: true
49397
+ internal: true,
49398
+ statusCmd: "tfa-handoff whoami 2>/dev/null | grep -v 'not logged'"
49284
49399
  },
49285
49400
  "tfa-vault": {
49286
49401
  category: "security",
@@ -49288,7 +49403,7 @@ var tool_catalog_default = {
49288
49403
  installer: "npm",
49289
49404
  label: "Vault",
49290
49405
  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.",
49291
- 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: when you obtain, generate, or rotate a secret and it isn't saved, store it with `tfa-vault put <path> field=value` (use `patch` to add fields without clobbering; overwrite on rotation so the vault stays source of truth). Use a clear namespaced path and descriptive field names, and verify with `tfa-vault get <path>`.",
49406
+ 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>`.",
49292
49407
  versionCmd: "tfa-vault --version 2>/dev/null | head -1",
49293
49408
  statusCmd: "tfa-vault whoami",
49294
49409
  installCmd: "bun add -g @todoforai/vault",
@@ -49572,6 +49687,9 @@ function whichWithTools(name) {
49572
49687
  }
49573
49688
  return null;
49574
49689
  }
49690
+ function binFileName(name) {
49691
+ return TOOL_CATALOG[name]?.binName ?? name;
49692
+ }
49575
49693
  function isToolInstalled(name) {
49576
49694
  const entry = TOOL_CATALOG[name];
49577
49695
  if (!entry)
@@ -49581,14 +49699,17 @@ function isToolInstalled(name) {
49581
49699
  const r = spawnSync("sh", ["-c", checkCmd], { stdio: "pipe", timeout: 5000 });
49582
49700
  return r.status === 0;
49583
49701
  }
49584
- return whichWithTools(name) !== null;
49702
+ return whichWithTools(binFileName(name)) !== null;
49585
49703
  }
49586
49704
  function findReferencedTools(content) {
49587
49705
  const stripped = content.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
49588
49706
  return Object.keys(TOOL_CATALOG).filter((name) => {
49589
- const esc = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
49590
- const re = new RegExp(String.raw`(?:^|[|;&\n]|&&|\|\||` + String.raw`\$\(|` + "`" + String.raw`|xargs\s+|sudo\s+|env\s+)\s*` + esc + String.raw`\b(?!-)`, "m");
49591
- return re.test(stripped);
49707
+ const tokens = [name, TOOL_CATALOG[name].binName].filter((t) => !!t);
49708
+ return tokens.some((tok) => {
49709
+ const esc = tok.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
49710
+ const re = new RegExp(String.raw`(?:^|[|;&\n]|&&|\|\||` + String.raw`\$\(|` + "`" + String.raw`|xargs\s+|sudo\s+|env\s+)\s*` + esc + String.raw`\b(?!-)`, "m");
49711
+ return re.test(stripped);
49712
+ });
49592
49713
  });
49593
49714
  }
49594
49715
  function findMissingTools(content) {
@@ -49603,7 +49724,8 @@ async function installBinary(name) {
49603
49724
  const dir = binDir();
49604
49725
  fs3.mkdirSync(dir, { recursive: true });
49605
49726
  const [url, isArchive] = await urlFunc();
49606
- const destName = os3.platform() === "win32" ? `${name}.exe` : name;
49727
+ const fileName = binFileName(name);
49728
+ const destName = os3.platform() === "win32" ? `${fileName}.exe` : fileName;
49607
49729
  const dest = path3.join(dir, destName);
49608
49730
  const tmpPath = dest + ".tmp";
49609
49731
  log2("info", `Downloading ${name} from ${url}`);
@@ -49613,7 +49735,7 @@ async function installBinary(name) {
49613
49735
  const data = Buffer.from(await res.arrayBuffer());
49614
49736
  fs3.writeFileSync(tmpPath, data);
49615
49737
  if (isArchive) {
49616
- const expectedNames = new Set([name, `${name}.exe`]);
49738
+ const expectedNames = new Set([name, `${name}.exe`, fileName, `${fileName}.exe`]);
49617
49739
  if (url.endsWith(".tar.gz") || url.endsWith(".tgz")) {
49618
49740
  await extractTarBinary(tmpPath, dest, expectedNames);
49619
49741
  } else if (url.endsWith(".zip")) {
@@ -51597,6 +51719,20 @@ function truncateLines(text, { maxLines = MAX_RESULT_LINES, maxLineLen = MAX_LIN
51597
51719
  ... (output truncated)`;
51598
51720
  return output;
51599
51721
  }
51722
+ function collapseCarriageReturns(text) {
51723
+ if (!text.includes("\r"))
51724
+ return text;
51725
+ return text.split(`
51726
+ `).map((line) => {
51727
+ if (!line.includes("\r"))
51728
+ return line;
51729
+ let out = "";
51730
+ for (const seg of line.split("\r"))
51731
+ out = seg + out.slice(seg.length);
51732
+ return out;
51733
+ }).join(`
51734
+ `);
51735
+ }
51600
51736
  function capLineWidth(text, lineLimit) {
51601
51737
  if (!isFinite(lineLimit))
51602
51738
  return text;
@@ -51612,6 +51748,15 @@ function formatTruncationNotice(totalLen, firstLimit, lastPart) {
51612
51748
 
51613
51749
  ${lastPart}`;
51614
51750
  }
51751
+ function applyOutputPolicy(text, policy) {
51752
+ const headLimit = Math.min(policy.firstLimit, policy.hardCap);
51753
+ if (text.length <= headLimit)
51754
+ return capLineWidth(text, policy.lineLimit);
51755
+ const tailLimit = Math.min(policy.lastLimit, policy.hardCap - headLimit);
51756
+ const head = capLineWidth(text.slice(0, headLimit), policy.lineLimit);
51757
+ const tail = tailLimit > 0 ? capLineWidth(text.slice(-tailLimit), policy.lineLimit) : "";
51758
+ return head + formatTruncationNotice(text.length, headLimit, tail);
51759
+ }
51615
51760
 
51616
51761
  // src/shell.ts
51617
51762
  var IS_WIN = os6.platform() === "win32";
@@ -51704,15 +51849,18 @@ class OutputBuffer {
51704
51849
  }
51705
51850
  getOutput() {
51706
51851
  if (!this.truncated)
51707
- return capLineWidth(this.firstPart, this.lineLimit);
51708
- return capLineWidth(this.firstPart, this.lineLimit) + `
51852
+ return this.format(this.firstPart);
51853
+ return this.format(this.firstPart) + `
51709
51854
 
51710
51855
  ... [truncated: showing first ${this.firstPart.length} and last ${this.lastPart.length} chars of ${this.totalLen} total] ...
51711
51856
 
51712
- ${capLineWidth(this.lastPart, this.lineLimit)}`;
51857
+ ${this.format(this.lastPart)}`;
51713
51858
  }
51714
51859
  getRawIfComplete() {
51715
- return this.truncated ? null : capLineWidth(this.firstPart, this.lineLimit);
51860
+ return this.truncated ? null : this.format(this.firstPart);
51861
+ }
51862
+ format(part) {
51863
+ return capLineWidth(collapseCarriageReturns(part), this.lineLimit);
51716
51864
  }
51717
51865
  }
51718
51866
  var processes = new Map;
@@ -52493,7 +52641,10 @@ register("execute_shell_command", async (args, client) => {
52493
52641
  resolve((stdout || "") + (stderr || ""));
52494
52642
  });
52495
52643
  });
52496
- return { cmd, ...detectContentType(result, cmd) };
52644
+ const detected = detectContentType(result, cmd);
52645
+ if (detected.contentType)
52646
+ return { cmd, ...detected };
52647
+ return { cmd, result: applyOutputPolicy(detected.result, resolveOutputPolicy(outputMode)) };
52497
52648
  }
52498
52649
  const { execCmd, postFilter } = extractTrailingTail(cmd);
52499
52650
  const send = (m) => client.sendResponse(m);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@todoforai/edge",
3
- "version": "0.13.21",
3
+ "version": "0.13.23",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "todoforai-edge": "dist/index.js"