@todoforai/edge 0.13.19 → 0.13.20

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 +113 -162
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -48145,7 +48145,7 @@ class ApiClient {
48145
48145
  }
48146
48146
  async request(method, endpoint, body) {
48147
48147
  const url = `${this.apiUrl}${endpoint}`;
48148
- const opts = { method, headers: this.headers };
48148
+ const opts = { method, headers: this.headers, signal: AbortSignal.timeout(30000) };
48149
48149
  if (body)
48150
48150
  opts.body = JSON.stringify(body);
48151
48151
  const res = await fetch(url, opts);
@@ -48772,7 +48772,6 @@ var tool_catalog_default = {
48772
48772
  "~/.zele/sqlite.db"
48773
48773
  ],
48774
48774
  capabilities: "Read inbox, search/send/reply email, email addresses — multi-account Gmail, OAuth browser login for setup.",
48775
- description: "Use for anything Gmail: reading the user's inbox, searching mail, sending or replying to email. Multi-account, OAuth login via `zele login`.",
48776
48775
  versionCmd: "zele --version 2>/dev/null | head -1"
48777
48776
  },
48778
48777
  xurl: {
@@ -48786,7 +48785,7 @@ var tool_catalog_default = {
48786
48785
  "~/.xurl"
48787
48786
  ],
48788
48787
  capabilities: "Post tweets, reply & quote, read timelines, search tweets, like & repost, follow/unfollow, DMs, media uploads",
48789
- description: "Use to interact with X/Twitter: post/reply/quote tweets, search, read timelines, like/repost, follow, DMs, media upload. Authenticated raw API access via `xurl <method> <path>`.",
48788
+ description: "Authenticated raw X API access via `xurl <method> <path>`; login `xurl auth oauth2`.",
48790
48789
  versionCmd: "xurl --version 2>/dev/null | head -1"
48791
48790
  },
48792
48791
  "tiktok-uploader": {
@@ -48799,7 +48798,6 @@ var tool_catalog_default = {
48799
48798
  "~/.tiktok/cookies.txt"
48800
48799
  ],
48801
48800
  capabilities: "Upload videos, batch uploads, schedule posts, custom covers, hashtags & mentions",
48802
- description: "Upload videos to TikTok, schedule posts, set covers and hashtags.",
48803
48801
  versionCmd: "pip show tiktok-uploader 2>/dev/null | grep -oP 'Version: \\K.*'"
48804
48802
  },
48805
48803
  instagrapi: {
@@ -48808,7 +48806,6 @@ var tool_catalog_default = {
48808
48806
  installer: "pip",
48809
48807
  label: "Instagram",
48810
48808
  capabilities: "Upload photos & reels, post stories, send DMs, like & comment, manage followers",
48811
- description: "Instagram automation: upload photos/reels, stories, DMs, likes, follower management.",
48812
48809
  versionCmd: "pip show instagrapi 2>/dev/null | grep -oP 'Version: \\K.*'"
48813
48810
  },
48814
48811
  mudslide: {
@@ -48822,7 +48819,6 @@ var tool_catalog_default = {
48822
48819
  "~/.local/share/mudslide"
48823
48820
  ],
48824
48821
  capabilities: "Send messages, send images & files, send locations & polls, group management, QR code login",
48825
- description: "Use to send WhatsApp messages/files/media/locations/polls from CLI. QR-code login via `mudslide login`.",
48826
48822
  versionCmd: "mudslide --version 2>/dev/null | head -1"
48827
48823
  },
48828
48824
  slack: {
@@ -48836,7 +48832,7 @@ var tool_catalog_default = {
48836
48832
  "~/.slack/credentials.json"
48837
48833
  ],
48838
48834
  capabilities: "Build & deploy Slack apps, manage workspaces & triggers, run datastore queries, tail logs — official Slack CLI.",
48839
- description: "Use for Slack platform development: scaffold/run/deploy Slack apps, manage triggers and datastores, manage workspace auth. `slack-cli login` to authenticate. Invoked as `slack-cli` to avoid colliding with the Slack desktop app's `slack` binary.",
48835
+ description: "`slack-cli login` to auth. Invoked as `slack-cli` to avoid colliding with the Slack desktop app's `slack` binary.",
48840
48836
  versionCmd: "slack-cli version 2>/dev/null | head -1",
48841
48837
  binName: "slack-cli",
48842
48838
  binary: {
@@ -48877,18 +48873,8 @@ var tool_catalog_default = {
48877
48873
  "~/.config/telegram-send/telegram-send.conf"
48878
48874
  ],
48879
48875
  capabilities: "Send messages, send files & images, send video & audio, Markdown/HTML formatting, channel & group support",
48880
- description: "Send Telegram messages and files from CLI via a bot.",
48881
48876
  versionCmd: "telegram-send --version 2>/dev/null | head -1"
48882
48877
  },
48883
- "apollo-api": {
48884
- category: "marketing",
48885
- pkg: "@todoforai/apollo-api",
48886
- installer: "npm",
48887
- label: "Apollo",
48888
- capabilities: "Lead search & enrichment, email sequences, CRM sync",
48889
- description: "Use for B2B prospecting: search Apollo's people/company DB, enrich leads with emails/titles/companies, manage sequences and CRM data. Needs APOLLO_API_KEY.",
48890
- versionCmd: "apollo-api --version 2>/dev/null | head -1"
48891
- },
48892
48878
  "meta-ads": {
48893
48879
  category: "marketing",
48894
48880
  pkg: "meta-ads",
@@ -48896,7 +48882,6 @@ var tool_catalog_default = {
48896
48882
  label: "Meta Ads",
48897
48883
  statusCmd: "meta auth status 2>&1",
48898
48884
  capabilities: "Meta/Facebook & Instagram ad campaigns: campaigns/adsets/ads CRUD, insights & reporting, catalog/product-feed/product-set/product-item, ad creatives, datasets (pixels), pages — Marketing API via the official `meta` CLI.",
48899
- description: "Manage Meta/Facebook & Instagram ad campaigns, insights, creatives, and catalogs.",
48900
48885
  versionCmd: "meta --version 2>/dev/null | head -1"
48901
48886
  },
48902
48887
  "elevenlabs-api": {
@@ -48904,8 +48889,7 @@ var tool_catalog_default = {
48904
48889
  pkg: "@todoforai/elevenlabs-api",
48905
48890
  installer: "npm",
48906
48891
  label: "ElevenLabs",
48907
- capabilities: "Text-to-speech, voice cloning, multiple languages",
48908
- description: "Use to generate speech audio from text, clone voices, or list voices. Needs ELEVENLABS_API_KEY.",
48892
+ capabilities: "Text-to-speech, voice cloning, multiple languages. Needs ELEVENLABS_API_KEY.",
48909
48893
  versionCmd: "elevenlabs-api --version 2>/dev/null | head -1"
48910
48894
  },
48911
48895
  "codex-imagegen-api": {
@@ -48914,7 +48898,6 @@ var tool_catalog_default = {
48914
48898
  installer: "npm",
48915
48899
  label: "Image Gen",
48916
48900
  capabilities: "AI image generation & editing via TODOFORAI backend (gpt-image)",
48917
- description: "Generate or edit images with AI.",
48918
48901
  versionCmd: "codex-imagegen-api --version 2>/dev/null | head -1"
48919
48902
  },
48920
48903
  "suno-api": {
@@ -48922,8 +48905,7 @@ var tool_catalog_default = {
48922
48905
  pkg: "@todoforai/suno-api",
48923
48906
  installer: "npm",
48924
48907
  label: "Suno",
48925
- capabilities: "AI music generation, custom lyrics, multiple genres",
48926
- description: "Use to generate music/songs from a prompt or custom lyrics. Needs SUNO_API_KEY.",
48908
+ capabilities: "AI music generation, custom lyrics, multiple genres. Needs SUNO_API_KEY.",
48927
48909
  versionCmd: "suno-api --version 2>/dev/null | head -1"
48928
48910
  },
48929
48911
  ntn: {
@@ -48937,7 +48919,6 @@ var tool_catalog_default = {
48937
48919
  "~/.config/notion"
48938
48920
  ],
48939
48921
  capabilities: "Official Notion CLI: workspace-wide OAuth login, raw API calls (`ntn api <path>`), page/datasource management, file uploads, Workers deploy.",
48940
- description: "Read, create, and update Notion pages and databases.",
48941
48922
  versionCmd: "ntn --version 2>/dev/null | head -1"
48942
48923
  },
48943
48924
  "perplexity-api": {
@@ -48946,7 +48927,6 @@ var tool_catalog_default = {
48946
48927
  installer: "npm",
48947
48928
  label: "Perplexity",
48948
48929
  capabilities: "AI-powered web search, chat completions, async chat, embeddings and agent runs",
48949
- description: "Web-grounded AI search with citations. Use when you need fresh information beyond training cutoff.",
48950
48930
  versionCmd: "perplexity-api --version 2>/dev/null | head -1"
48951
48931
  },
48952
48932
  gh: {
@@ -48961,7 +48941,7 @@ var tool_catalog_default = {
48961
48941
  "~/.config/gh/hosts.yml"
48962
48942
  ],
48963
48943
  capabilities: "Repos, PRs & issues, actions & releases, gists & SSH keys",
48964
- description: "Use for any GitHub operation: create/list/merge PRs, file issues, manage releases, trigger/inspect Actions, clone/fork repos, gists. Prefer `gh` over raw GitHub API calls.",
48944
+ description: "Prefer `gh` over raw GitHub API calls.",
48965
48945
  versionCmd: "gh --version 2>/dev/null | head -1"
48966
48946
  },
48967
48947
  glab: {
@@ -48975,7 +48955,7 @@ var tool_catalog_default = {
48975
48955
  "~/.config/glab-cli/config.yml"
48976
48956
  ],
48977
48957
  capabilities: "Repos, MRs & issues, CI/CD pipelines, releases",
48978
- description: "Use for any GitLab operation: MRs, issues, pipelines, releases, repo management. GitLab equivalent of `gh`.",
48958
+ description: "GitLab equivalent of `gh`.",
48979
48959
  versionCmd: "glab --version 2>/dev/null | head -1"
48980
48960
  },
48981
48961
  vercel: {
@@ -48989,7 +48969,7 @@ var tool_catalog_default = {
48989
48969
  "~/.local/share/com.vercel.cli/auth.json"
48990
48970
  ],
48991
48971
  capabilities: "Deploy & preview, environment variables, domain management",
48992
- description: "Use to deploy a project to Vercel (`vercel deploy`), manage env vars, domains, and preview URLs.",
48972
+ description: "`vercel deploy`; manage env vars, domains, preview URLs.",
48993
48973
  versionCmd: "vercel --version 2>/dev/null | head -1"
48994
48974
  },
48995
48975
  netlify: {
@@ -49003,7 +48983,7 @@ var tool_catalog_default = {
49003
48983
  "~/.netlify/config.json"
49004
48984
  ],
49005
48985
  capabilities: "Deploy & preview, serverless functions, forms & identity",
49006
- description: "Use to deploy sites/functions to Netlify, manage env vars, forms, identity. `netlify deploy --prod` for production.",
48986
+ description: "`netlify deploy --prod` for production; manage env vars, forms, identity.",
49007
48987
  versionCmd: "netlify --version 2>/dev/null | head -1"
49008
48988
  },
49009
48989
  firebase: {
@@ -49017,7 +48997,7 @@ var tool_catalog_default = {
49017
48997
  "~/.config/configstore/firebase-tools.json"
49018
48998
  ],
49019
48999
  capabilities: "Hosting & deploy, Firestore & Realtime DB, auth & functions",
49020
- description: "Use for Firebase: deploy hosting/functions, manage Firestore/Realtime DB rules and data, auth users, emulators.",
49000
+ description: "Deploy hosting/functions, manage Firestore/RTDB rules and data, auth users, emulators.",
49021
49001
  versionCmd: "firebase --version 2>/dev/null | head -1"
49022
49002
  },
49023
49003
  wrangler: {
@@ -49031,7 +49011,7 @@ var tool_catalog_default = {
49031
49011
  "~/.config/.wrangler/config/default.toml"
49032
49012
  ],
49033
49013
  capabilities: "Workers & Pages deploy, KV & D1 storage, R2 & Queues",
49034
- description: "Use for Cloudflare developer platform: deploy Workers/Pages, manage KV/D1/R2/Queues, tail logs. Use `cloudflared` for tunnels.",
49014
+ description: "Tail logs with `wrangler tail`. Use `cloudflared` for tunnels.",
49035
49015
  versionCmd: "wrangler --version 2>/dev/null | head -1"
49036
49016
  },
49037
49017
  stripe: {
@@ -49045,7 +49025,7 @@ var tool_catalog_default = {
49045
49025
  "~/.config/stripe/config.toml"
49046
49026
  ],
49047
49027
  capabilities: "Payments & subscriptions, webhook testing, invoice management",
49048
- description: "Use for Stripe: inspect/create customers, charges, subscriptions, invoices, webhooks. `stripe listen` to forward webhooks locally; `stripe get/post /v1/...` for raw API.",
49028
+ description: "`stripe listen` forwards webhooks locally; `stripe get/post /v1/...` for raw API.",
49049
49029
  versionCmd: "stripe --version 2>/dev/null | head -1"
49050
49030
  },
49051
49031
  flyctl: {
@@ -49059,7 +49039,7 @@ var tool_catalog_default = {
49059
49039
  "~/.fly/config.yml"
49060
49040
  ],
49061
49041
  capabilities: "Deploy containers globally, Postgres & volumes, auto-scaling",
49062
- description: "Use to deploy apps/containers to Fly.io edge, manage Postgres/volumes/machines/secrets. `flyctl deploy` from app dir.",
49042
+ description: "`flyctl deploy` from app dir; manage Postgres/volumes/machines/secrets.",
49063
49043
  versionCmd: "flyctl version 2>/dev/null | head -1"
49064
49044
  },
49065
49045
  supabase: {
@@ -49073,7 +49053,7 @@ var tool_catalog_default = {
49073
49053
  "~/.config/supabase/access-token"
49074
49054
  ],
49075
49055
  capabilities: "Postgres database, auth & storage, edge functions",
49076
- description: "Use for Supabase projects: run/migrate local Postgres, deploy edge functions, manage auth/storage, link to cloud project.",
49056
+ description: "Link to cloud project; runs/migrates local Postgres.",
49077
49057
  versionCmd: "supabase --version 2>/dev/null | head -1"
49078
49058
  },
49079
49059
  railway: {
@@ -49087,7 +49067,7 @@ var tool_catalog_default = {
49087
49067
  "~/.railway/config.json"
49088
49068
  ],
49089
49069
  capabilities: "Deploy apps & databases, environment management, auto-scaling",
49090
- description: "Use to deploy apps/services to Railway, provision databases, manage env vars and environments. `railway up` to deploy current dir.",
49070
+ description: "`railway up` to deploy current dir.",
49091
49071
  versionCmd: "railway --version 2>/dev/null | head -1"
49092
49072
  },
49093
49073
  shopify: {
@@ -49100,16 +49080,23 @@ var tool_catalog_default = {
49100
49080
  "~/.config/shopify/config.json"
49101
49081
  ],
49102
49082
  capabilities: "Theme development, app scaffolding, store management",
49103
- description: "Use for Shopify dev: scaffold/develop themes and apps, push/pull themes, run dev server against a store.",
49104
49083
  versionCmd: "shopify version 2>/dev/null | head -1"
49105
49084
  },
49085
+ "shop-app": {
49086
+ category: "ecommerce",
49087
+ pkg: "@todoforai/shop-app",
49088
+ installer: "npm",
49089
+ label: "Shop",
49090
+ capabilities: "Product search & price comparison across Shopify stores",
49091
+ description: "Shop.app product search across Shopify stores (no auth). `shop-app search '<query>' --ships-to HU -n 10` returns products with price, rating, URL, variants. `shop-app similar <variantId>` for lookalikes.",
49092
+ versionCmd: "shop-app --version 2>/dev/null | head -1"
49093
+ },
49106
49094
  "datadog-ci": {
49107
49095
  category: "monitoring",
49108
49096
  pkg: "@datadog/datadog-ci",
49109
49097
  installer: "npm",
49110
49098
  label: "Datadog",
49111
- capabilities: "CI test visibility, sourcemap uploads, deployment tracking",
49112
- description: "Use in CI to upload test results/sourcemaps/coverage to Datadog and mark deployments. Needs DATADOG_API_KEY.",
49099
+ capabilities: "CI test visibility, sourcemap uploads, deployment tracking. Needs DATADOG_API_KEY.",
49113
49100
  versionCmd: "datadog-ci version 2>/dev/null | head -1"
49114
49101
  },
49115
49102
  "sentry-cli": {
@@ -49123,7 +49110,7 @@ var tool_catalog_default = {
49123
49110
  "~/.sentryclirc"
49124
49111
  ],
49125
49112
  capabilities: "Release management, sourcemap uploads, error monitoring",
49126
- description: "Use to create Sentry releases, upload sourcemaps/debug-symbols, associate commits, send events. `sentry-cli releases new <version>`.",
49113
+ description: "`sentry-cli releases new <version>`.",
49127
49114
  versionCmd: "sentry-cli --version 2>/dev/null | head -1"
49128
49115
  },
49129
49116
  todoai: {
@@ -49132,7 +49119,7 @@ var tool_catalog_default = {
49132
49119
  installer: "bun",
49133
49120
  label: "TODOforAI",
49134
49121
  capabilities: "Create/list/inspect/update TODOs, run templates & workflows, platform API access",
49135
- description: "Use for the TODOforAI platform — this is how you reach the user's OWN TODOs: `todoai list` (filter with `--status open`) to browse them, `todoai \"prompt\"` to create one, `todoai --inspect <id>` to read a TODO's full chat log, `todoai status/addmessage/delete` to manage them, `--template` to run a registry workflow. Never claim you lack access to platform TODOs — reach them here; run `--help` for details.",
49122
+ 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.',
49136
49123
  installCmd: "bun add -g @todoforai/cli",
49137
49124
  versionCmd: "todoai --version 2>/dev/null | head -1",
49138
49125
  internal: true
@@ -49143,7 +49130,7 @@ var tool_catalog_default = {
49143
49130
  installer: "npm",
49144
49131
  label: "Postman",
49145
49132
  capabilities: "Run API test collections, CI/CD integration, HTML reports",
49146
- description: "Use to run Postman collections from CLI (API smoke tests, CI checks). `newman run <collection.json> -e <env.json>`.",
49133
+ description: "`newman run <collection.json> -e <env.json>`.",
49147
49134
  versionCmd: "newman --version 2>/dev/null | head -1"
49148
49135
  },
49149
49136
  curl: {
@@ -49164,22 +49151,20 @@ var tool_catalog_default = {
49164
49151
  installer: "binary",
49165
49152
  label: "Tunnel",
49166
49153
  capabilities: "Tunnel to localhost, expose local services, zero-trust access",
49167
- 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.",
49154
+ description: "`cloudflared tunnel --url http://localhost:<port>` prints a public https URL.",
49168
49155
  versionCmd: "cloudflared --version 2>/dev/null | head -1"
49169
49156
  },
49170
49157
  vault: {
49171
49158
  category: "security",
49172
49159
  pkg: "vault",
49173
49160
  installer: "binary",
49174
- preinstallCloud: true,
49175
49161
  label: "HashiCorp Vault",
49176
49162
  statusCmd: "vault token lookup >/dev/null 2>&1 && echo authenticated",
49177
49163
  loginCmd: "vault login",
49178
49164
  credentialPaths: [
49179
49165
  "~/.vault-token"
49180
49166
  ],
49181
- capabilities: "Secrets management, dynamic credentials",
49182
- 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.",
49167
+ capabilities: "Secrets management, dynamic credentials. Needs VAULT_ADDR + VAULT_TOKEN; for credential read/write use `tfa-vault` instead.",
49183
49168
  versionCmd: "vault --version 2>/dev/null | head -1"
49184
49169
  },
49185
49170
  rclone: {
@@ -49194,7 +49179,6 @@ var tool_catalog_default = {
49194
49179
  "~/.config/rclone/rclone.conf"
49195
49180
  ],
49196
49181
  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).",
49197
- description: "Access cloud storage (Google Drive, OneDrive, Dropbox, S3, 40+ providers). List, copy, sync, or mount files.",
49198
49182
  subProviders: {
49199
49183
  gdrive: {
49200
49184
  label: "Google Drive",
@@ -49239,8 +49223,7 @@ var tool_catalog_default = {
49239
49223
  pkg: "pymupdf",
49240
49224
  installer: "pip",
49241
49225
  label: "PDF",
49242
- capabilities: "PDF text editing, extraction, merge, split",
49243
- description: "Read, edit, merge, or split PDFs programmatically. Call via `python3 -c`.",
49226
+ capabilities: "PDF text editing, extraction, merge, split. Call via `python3 -c`.",
49244
49227
  versionCmd: "python3 -c 'import pymupdf; print(pymupdf.__version__)' 2>/dev/null",
49245
49228
  preinstallCloud: true
49246
49229
  },
@@ -49251,16 +49234,16 @@ var tool_catalog_default = {
49251
49234
  preinstallCloud: true,
49252
49235
  label: "Browser",
49253
49236
  capabilities: "Headless browser automation, web scraping, accessibility tree snapshots, screenshots, PDF generation",
49254
- description: "Headless browser. Use for JS-rendered pages, scraping, screenshots, PDFs, and automated flows. Default browser — prefer over `todoforai-browser` unless the user's own session is needed. On a PC with a display, prefer a visible window the user can watch and 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 running Chrome untouched), then attach with `agent-browser --cdp 9222 <command>`. Use headless only on the cloud or when no display is available.",
49237
+ 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.",
49255
49238
  versionCmd: "agent-browser --version 2>/dev/null | head -1"
49256
49239
  },
49257
49240
  "todoforai-browser": {
49258
49241
  category: "development",
49259
49242
  pkg: "@todoforai/browser",
49260
49243
  installer: "npm",
49244
+ preinstallCloud: true,
49261
49245
  label: "Browser (Extension)",
49262
49246
  capabilities: "Browser automation via extension (non-headless), web scraping, accessibility tree snapshots, screenshots, PDF generation",
49263
- description: "Drives the user's real browser via the extension. Use when the task needs the user's actual logged-in session, cookies, or MFA.",
49264
49247
  versionCmd: `node -p "require(require('child_process').execSync('which todoforai-browser',{encoding:'utf8'}).trim().replace(/\\/dist\\/index\\.js$/, '/package.json')).version" 2>/dev/null`,
49265
49248
  internal: true
49266
49249
  },
@@ -49270,55 +49253,19 @@ var tool_catalog_default = {
49270
49253
  installer: "npm",
49271
49254
  label: "Sub-agent",
49272
49255
  capabilities: "Spawn a TODO for AI sub-agent (FluidAgent) from the CLI; pipe stdin in, get an answer out",
49273
- description: "Spawn a sub-agent for a focused task. Pipe context via stdin.",
49274
49256
  versionCmd: "todoforai-subagent --version 2>/dev/null | head -1",
49275
49257
  installCmd: "bun add -g @todoforai/subagent",
49276
49258
  internal: true
49277
49259
  },
49278
- "todoforai-summary": {
49279
- category: "development",
49280
- pkg: "@todoforai/summary",
49281
- installer: "npm",
49282
- label: "Summarize (Lite)",
49283
- capabilities: "Summarize files or piped input via a sub-agent",
49284
- description: "Summarize files or piped content via a sub-agent.",
49285
- versionCmd: "todoforai-summary --version 2>/dev/null | head -1",
49286
- installCmd: "bun add -g @todoforai/summary",
49287
- internal: true
49288
- },
49289
- "tfa-explore": {
49260
+ "tfa-handoff": {
49290
49261
  category: "development",
49291
- pkg: "@todoforai/tfa-explore",
49262
+ pkg: "@todoforai/tfa-handoff",
49292
49263
  installer: "npm",
49293
- label: "Explore",
49294
- capabilities: "Explore a codebase as a real TODO: read-only agent maps structure, surfaces relevant files, streams findings to terminal",
49295
- description: "Explore a codebase with a read-only sub-agent. Streams findings live.",
49296
- versionCmd: "tfa-explore --version 2>/dev/null | head -1",
49297
- installCmd: "bun add -g @todoforai/tfa-explore",
49298
- preinstallCloud: true,
49299
- internal: true
49300
- },
49301
- "tfa-review": {
49302
- category: "development",
49303
- pkg: "@todoforai/tfa-review",
49304
- installer: "npm",
49305
- label: "Review",
49306
- capabilities: "Review a git diff as a real TODO: read-only agent assesses goal, finds issues, suggests simpler approaches",
49307
- description: "Review a git diff with a read-only sub-agent.",
49308
- versionCmd: "tfa-review --version 2>/dev/null | head -1",
49309
- installCmd: "bun add -g @todoforai/tfa-review",
49310
- preinstallCloud: true,
49311
- internal: true
49312
- },
49313
- "tfa-summary": {
49314
- category: "development",
49315
- pkg: "@todoforai/tfa-summary",
49316
- installer: "npm",
49317
- label: "Summarize",
49318
- capabilities: "Summarize files or piped input as a real TODO with no-tools sub-agent",
49319
- description: "Summarize files or piped content as a visible TODO.",
49320
- versionCmd: "tfa-summary --version 2>/dev/null | head -1",
49321
- installCmd: "bun add -g @todoforai/tfa-summary",
49264
+ label: "Handoff",
49265
+ capabilities: "Compact a conversation into a handoff document as a hidden TODO; pipe filter for `todoai --inspect | tfa-handoff | todoai`",
49266
+ description: "Compact a conversation into a handoff document for a fresh agent.",
49267
+ versionCmd: "tfa-handoff --version 2>/dev/null | head -1",
49268
+ installCmd: "bun add -g @todoforai/tfa-handoff",
49322
49269
  internal: true
49323
49270
  },
49324
49271
  "tfa-vault": {
@@ -49326,8 +49273,8 @@ var tool_catalog_default = {
49326
49273
  pkg: "@todoforai/vault",
49327
49274
  installer: "npm",
49328
49275
  label: "Vault",
49329
- 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.",
49330
- description: "The user's secret store. Always check here first for API keys, tokens, credentials, bank cards, and private/secret account details before looking elsewhere.",
49276
+ 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: 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>`.",
49331
49278
  versionCmd: "tfa-vault --version 2>/dev/null | head -1",
49332
49279
  statusCmd: "tfa-vault whoami",
49333
49280
  installCmd: "bun add -g @todoforai/vault",
@@ -51610,9 +51557,10 @@ var STREAM_FIRST = 1e4;
51610
51557
  var STREAM_LAST = 1e4;
51611
51558
  var RUN_OUTPUT_CAP = 256 * 1024;
51612
51559
  var OUTPUT_POLICIES = {
51613
- safe: { firstLimit: STREAM_FIRST, lastLimit: STREAM_LAST, hardCap: STREAM_FIRST + STREAM_LAST },
51614
- full: { firstLimit: Infinity, lastLimit: 0, hardCap: RUN_OUTPUT_CAP },
51615
- raw: { firstLimit: Infinity, lastLimit: 0, hardCap: Infinity }
51560
+ safe: { firstLimit: STREAM_FIRST, lastLimit: STREAM_LAST, hardCap: STREAM_FIRST + STREAM_LAST, lineLimit: MAX_LINE_LEN },
51561
+ wide: { firstLimit: STREAM_FIRST, lastLimit: STREAM_LAST, hardCap: STREAM_FIRST + STREAM_LAST, lineLimit: Infinity },
51562
+ full: { firstLimit: Infinity, lastLimit: 0, hardCap: RUN_OUTPUT_CAP, lineLimit: MAX_LINE_LEN },
51563
+ raw: { firstLimit: Infinity, lastLimit: 0, hardCap: Infinity, lineLimit: Infinity }
51616
51564
  };
51617
51565
  var DEFAULT_OUTPUT_MODE = "safe";
51618
51566
  function resolveOutputPolicy(mode) {
@@ -51635,6 +51583,13 @@ function truncateLines(text, { maxLines = MAX_RESULT_LINES, maxLineLen = MAX_LIN
51635
51583
  ... (output truncated)`;
51636
51584
  return output;
51637
51585
  }
51586
+ function capLineWidth(text, lineLimit) {
51587
+ if (!isFinite(lineLimit))
51588
+ return text;
51589
+ return text.split(`
51590
+ `).map((line) => line.length > lineLimit ? line.slice(0, lineLimit) + ` ...[+${line.length - lineLimit} chars]` : line).join(`
51591
+ `);
51592
+ }
51638
51593
  function formatTruncationNotice(totalLen, firstLimit, lastPart) {
51639
51594
  const dropped = totalLen - firstLimit - lastPart.length;
51640
51595
  return `
@@ -51697,9 +51652,11 @@ class OutputBuffer {
51697
51652
  truncMsgSent = false;
51698
51653
  headLimit;
51699
51654
  lastLimit;
51655
+ lineLimit;
51700
51656
  constructor(policy = OUTPUT_POLICIES[DEFAULT_OUTPUT_MODE]) {
51701
51657
  this.headLimit = Math.min(policy.firstLimit, policy.hardCap);
51702
51658
  this.lastLimit = Math.min(policy.lastLimit, policy.hardCap - this.headLimit);
51659
+ this.lineLimit = policy.lineLimit;
51703
51660
  }
51704
51661
  append(text) {
51705
51662
  this.totalLen += text.length;
@@ -51733,15 +51690,15 @@ class OutputBuffer {
51733
51690
  }
51734
51691
  getOutput() {
51735
51692
  if (!this.truncated)
51736
- return this.firstPart;
51737
- return this.firstPart + `
51693
+ return capLineWidth(this.firstPart, this.lineLimit);
51694
+ return capLineWidth(this.firstPart, this.lineLimit) + `
51738
51695
 
51739
51696
  ... [truncated: showing first ${this.firstPart.length} and last ${this.lastPart.length} chars of ${this.totalLen} total] ...
51740
51697
 
51741
- ${this.lastPart}`;
51698
+ ${capLineWidth(this.lastPart, this.lineLimit)}`;
51742
51699
  }
51743
51700
  getRawIfComplete() {
51744
- return this.truncated ? null : this.firstPart;
51701
+ return this.truncated ? null : capLineWidth(this.firstPart, this.lineLimit);
51745
51702
  }
51746
51703
  }
51747
51704
  var processes = new Map;
@@ -52624,7 +52581,7 @@ register("read_file_base64", async (args) => {
52624
52581
  return { path: fullPath, base64: data.toString("base64"), bytes: data.length };
52625
52582
  });
52626
52583
  register("search_files", async (args) => {
52627
- const { pattern, path: p10 = ".", cwd = args.root_path ?? "", head = 100, max_count = 5, glob: globPattern = "", ignore_case = true } = args;
52584
+ const { pattern, path: p10 = ".", cwd = args.root_path ?? "", head = 100, max_count = 5, glob: globPattern = "", ignore_case = true, output: outputMode = DEFAULT_OUTPUT_MODE } = args;
52628
52585
  const { execSync: execWhich } = await import("child_process");
52629
52586
  const whichCmd = process.platform === "win32" ? "where" : "which";
52630
52587
  const which = (bin) => {
@@ -52703,7 +52660,7 @@ register("search_files", async (args) => {
52703
52660
  output = lines.join(`
52704
52661
  `);
52705
52662
  }
52706
- return { result: truncateLines(output, { maxLines: head }) };
52663
+ return { result: truncateLines(output, { maxLines: head, maxLineLen: resolveOutputPolicy(outputMode).lineLimit }) };
52707
52664
  }
52708
52665
  if (code === 1)
52709
52666
  return { result: "No matches found." };
@@ -53006,6 +52963,8 @@ class TODOforAIEdge {
53006
52963
  browserExtensionBridge;
53007
52964
  stopping = false;
53008
52965
  reconnectTimer;
52966
+ wakeReconnect;
52967
+ connectedAt = 0;
53009
52968
  edgeConfig = {
53010
52969
  id: "",
53011
52970
  name: "Name uninitialized",
@@ -53288,23 +53247,22 @@ class TODOforAIEdge {
53288
53247
  console.log(`[warn] Unknown message type: ${msgType}`);
53289
53248
  }
53290
53249
  }
53291
- startHeartbeat(onStale) {
53250
+ startHeartbeat(ws2, onStale) {
53292
53251
  this.stopHeartbeat();
53293
53252
  let pongReceived = true;
53294
- this.ws?.on("pong", () => {
53253
+ ws2.on("pong", () => {
53295
53254
  pongReceived = true;
53296
53255
  });
53297
53256
  this.heartbeatTimer = setInterval(() => {
53298
53257
  if (!pongReceived) {
53299
53258
  console.log("[warn] No pong received, terminating stale connection");
53300
- this.stopHeartbeat();
53301
- this.ws?.terminate();
53259
+ ws2.terminate();
53302
53260
  onStale();
53303
53261
  return;
53304
53262
  }
53305
53263
  pongReceived = false;
53306
53264
  try {
53307
- this.ws?.ping();
53265
+ ws2.ping();
53308
53266
  } catch {}
53309
53267
  }, 30000);
53310
53268
  }
@@ -53319,20 +53277,30 @@ class TODOforAIEdge {
53319
53277
  const url = `${this.wsUrl}?fingerprint=${encodeURIComponent(this.fingerprint)}`;
53320
53278
  if (this.debug)
53321
53279
  console.log(`[info] Connecting to ${url}`);
53322
- this.ws = new wrapper_default(url, [this.api.apiKey], {
53280
+ const ws2 = new wrapper_default(url, [this.api.apiKey], {
53323
53281
  maxPayload: 5 * 1024 * 1024,
53324
53282
  rejectUnauthorized: false
53325
53283
  });
53326
- this.ws.on("open", () => {
53327
- this.connected = true;
53328
- console.log("[info] WebSocket connected");
53329
- this.startHeartbeat(() => {
53284
+ this.ws = ws2;
53285
+ let settled = false;
53286
+ const settle = (fn2) => {
53287
+ if (settled)
53288
+ return;
53289
+ settled = true;
53290
+ if (this.ws === ws2) {
53291
+ this.stopHeartbeat();
53330
53292
  this.connected = false;
53331
53293
  this.ws = null;
53332
- resolve();
53333
- });
53294
+ }
53295
+ fn2();
53296
+ };
53297
+ ws2.on("open", () => {
53298
+ this.connected = true;
53299
+ this.connectedAt = Date.now();
53300
+ console.log("[info] WebSocket connected");
53301
+ this.startHeartbeat(ws2, () => settle(() => resolve(0)));
53334
53302
  });
53335
- this.ws.on("message", (data, isBinary) => {
53303
+ ws2.on("message", (data, isBinary) => {
53336
53304
  if (isBinary) {
53337
53305
  const frame = data instanceof Buffer ? new Uint8Array(data) : new Uint8Array(data);
53338
53306
  this.storeBinaryFrame(frame);
@@ -53340,36 +53308,30 @@ class TODOforAIEdge {
53340
53308
  }
53341
53309
  this.handleMessage(data.toString()).catch((e) => {
53342
53310
  if (e instanceof AuthenticationError || e instanceof ServerError) {
53343
- this.ws?.close();
53344
- reject(e);
53311
+ ws2.close();
53312
+ settle(() => reject(e));
53345
53313
  } else {
53346
53314
  console.error("[handler error]", e);
53347
53315
  }
53348
53316
  });
53349
53317
  });
53350
- this.ws.on("close", (code, reason) => {
53351
- this.stopHeartbeat();
53352
- this.connected = false;
53353
- this.ws = null;
53318
+ ws2.on("close", (code, reason) => {
53354
53319
  const reasonText = reason?.toString() || "<empty>";
53355
- const clean = code === 1000;
53356
- console.log(`[info] WebSocket closed code=${code} clean=${clean} reason=${reasonText}`);
53320
+ console.log(`[info] WebSocket closed code=${code} clean=${code === 1000} reason=${reasonText}`);
53357
53321
  if (code === 4001) {
53358
53322
  console.log(`\x1B[33m[info] ${reasonText}. Not reconnecting.\x1B[0m`);
53359
53323
  console.log(`\x1B[33m[info] To replace the existing connection, restart with: todoforai-edge --kill\x1B[0m`);
53360
- reject(new ServerError(reasonText));
53324
+ settle(() => reject(new ServerError(reasonText)));
53361
53325
  } else if (code === 4002) {
53362
53326
  console.log(`\x1B[33m[info] ${reasonText}. This instance was replaced by a new connection.\x1B[0m`);
53363
- reject(new ServerError(reasonText));
53327
+ settle(() => reject(new ServerError(reasonText)));
53364
53328
  } else {
53365
- resolve();
53329
+ settle(() => resolve(code));
53366
53330
  }
53367
53331
  });
53368
- this.ws.on("error", (err2) => {
53369
- this.stopHeartbeat();
53370
- this.connected = false;
53371
- this.ws = null;
53372
- reject(err2);
53332
+ ws2.on("error", (err2) => {
53333
+ console.error(`[error] WebSocket error: ${err2.message}`);
53334
+ settle(() => resolve(0));
53373
53335
  });
53374
53336
  });
53375
53337
  }
@@ -53379,45 +53341,34 @@ class TODOforAIEdge {
53379
53341
  } catch {}
53380
53342
  this.fingerprint = generateFingerprint();
53381
53343
  console.log(`\x1B[36m\x1B[1m\uD83D\uDC46 Fingerprint:\x1B[0m ${this.fingerprint}`);
53382
- const maxAttempts = 20;
53383
53344
  let attempt = 0;
53384
- while (attempt < maxAttempts && !this.stopping) {
53385
- console.log(`[info] Connecting (attempt ${attempt + 1}/${maxAttempts})`);
53345
+ while (!this.stopping) {
53346
+ console.log(`[info] Connecting${attempt > 0 ? ` (retry ${attempt})` : ""}`);
53347
+ this.connectedAt = 0;
53386
53348
  try {
53387
53349
  await this.connect();
53388
- if (this.stopping)
53389
- break;
53390
- attempt = 0;
53391
53350
  } catch (e) {
53392
- if (e instanceof AuthenticationError) {
53393
- console.error(`\x1B[31mAuthentication failed: ${e.message}\x1B[0m`);
53394
- break;
53395
- }
53396
- if (e instanceof ServerError) {
53397
- console.error(`\x1B[31mServer error: ${e.message}\x1B[0m`);
53398
- break;
53399
- }
53400
- attempt++;
53401
- console.error(`[error] Connection error: ${e.message}`);
53402
- } finally {
53403
- this.connected = false;
53404
- this.ws = null;
53405
- }
53406
- if (attempt > 0 && attempt < maxAttempts && !this.stopping) {
53407
- const delay = Math.min(4 + attempt, 20);
53408
- console.log(`[info] Reconnecting in ${delay}s...`);
53409
- await new Promise((r) => {
53410
- this.reconnectTimer = setTimeout(r, delay * 1000);
53411
- });
53351
+ const label = e instanceof AuthenticationError ? "Authentication failed" : "Server error";
53352
+ console.error(`\x1B[31m${label}: ${e.message}\x1B[0m`);
53353
+ break;
53412
53354
  }
53413
- }
53414
- if (attempt >= maxAttempts) {
53415
- console.error("\x1B[31mMax reconnection attempts reached.\x1B[0m");
53355
+ if (this.stopping)
53356
+ break;
53357
+ if (this.connectedAt && Date.now() - this.connectedAt > 60000)
53358
+ attempt = 0;
53359
+ attempt++;
53360
+ const delay = Math.min(2 * 2 ** Math.min(attempt - 1, 4), 30);
53361
+ console.log(`[info] Reconnecting in ${delay}s...`);
53362
+ await new Promise((r) => {
53363
+ this.wakeReconnect = r;
53364
+ this.reconnectTimer = setTimeout(r, delay * 1000);
53365
+ });
53416
53366
  }
53417
53367
  }
53418
53368
  stop() {
53419
53369
  this.stopping = true;
53420
53370
  clearTimeout(this.reconnectTimer);
53371
+ this.wakeReconnect?.();
53421
53372
  this.stopHeartbeat();
53422
53373
  this.browserExtensionBridge.stop();
53423
53374
  this.frontendWs?.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@todoforai/edge",
3
- "version": "0.13.19",
3
+ "version": "0.13.20",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "todoforai-edge": "dist/index.js"