@todoforai/edge 0.13.19 → 0.13.21

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 +144 -163
  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,9 +48785,23 @@ 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
  },
48791
+ "linkedin-api": {
48792
+ category: "social",
48793
+ pkg: "@todoforai/linkedin-api",
48794
+ installer: "npm",
48795
+ label: "LinkedIn",
48796
+ statusCmd: `linkedin-api whoami 2>/dev/null | grep -oP '"publicId":\\s*"\\K[^"]+' | head -1`,
48797
+ loginCmd: "linkedin-api auth",
48798
+ credentialPaths: [
48799
+ "~/.config/linkedin-api/credentials.json"
48800
+ ],
48801
+ capabilities: "Read profiles, list & read conversations, send DMs, send connection requests — raw LinkedIn Voyager API via cookie auth",
48802
+ description: "Authenticated LinkedIn Voyager API CLI. Cookie auth: `linkedin-api auth <li_at> <jsessionid>` (from browser DevTools).",
48803
+ versionCmd: "linkedin-api --version 2>/dev/null | head -1"
48804
+ },
48792
48805
  "tiktok-uploader": {
48793
48806
  category: "social",
48794
48807
  pkg: "tiktok-uploader",
@@ -48799,7 +48812,6 @@ var tool_catalog_default = {
48799
48812
  "~/.tiktok/cookies.txt"
48800
48813
  ],
48801
48814
  capabilities: "Upload videos, batch uploads, schedule posts, custom covers, hashtags & mentions",
48802
- description: "Upload videos to TikTok, schedule posts, set covers and hashtags.",
48803
48815
  versionCmd: "pip show tiktok-uploader 2>/dev/null | grep -oP 'Version: \\K.*'"
48804
48816
  },
48805
48817
  instagrapi: {
@@ -48808,7 +48820,6 @@ var tool_catalog_default = {
48808
48820
  installer: "pip",
48809
48821
  label: "Instagram",
48810
48822
  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
48823
  versionCmd: "pip show instagrapi 2>/dev/null | grep -oP 'Version: \\K.*'"
48813
48824
  },
48814
48825
  mudslide: {
@@ -48822,7 +48833,6 @@ var tool_catalog_default = {
48822
48833
  "~/.local/share/mudslide"
48823
48834
  ],
48824
48835
  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
48836
  versionCmd: "mudslide --version 2>/dev/null | head -1"
48827
48837
  },
48828
48838
  slack: {
@@ -48836,7 +48846,7 @@ var tool_catalog_default = {
48836
48846
  "~/.slack/credentials.json"
48837
48847
  ],
48838
48848
  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.",
48849
+ description: "`slack-cli login` to auth. Invoked as `slack-cli` to avoid colliding with the Slack desktop app's `slack` binary.",
48840
48850
  versionCmd: "slack-cli version 2>/dev/null | head -1",
48841
48851
  binName: "slack-cli",
48842
48852
  binary: {
@@ -48877,18 +48887,8 @@ var tool_catalog_default = {
48877
48887
  "~/.config/telegram-send/telegram-send.conf"
48878
48888
  ],
48879
48889
  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
48890
  versionCmd: "telegram-send --version 2>/dev/null | head -1"
48882
48891
  },
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
48892
  "meta-ads": {
48893
48893
  category: "marketing",
48894
48894
  pkg: "meta-ads",
@@ -48896,7 +48896,6 @@ var tool_catalog_default = {
48896
48896
  label: "Meta Ads",
48897
48897
  statusCmd: "meta auth status 2>&1",
48898
48898
  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
48899
  versionCmd: "meta --version 2>/dev/null | head -1"
48901
48900
  },
48902
48901
  "elevenlabs-api": {
@@ -48904,8 +48903,7 @@ var tool_catalog_default = {
48904
48903
  pkg: "@todoforai/elevenlabs-api",
48905
48904
  installer: "npm",
48906
48905
  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.",
48906
+ capabilities: "Text-to-speech, voice cloning, multiple languages. Needs ELEVENLABS_API_KEY.",
48909
48907
  versionCmd: "elevenlabs-api --version 2>/dev/null | head -1"
48910
48908
  },
48911
48909
  "codex-imagegen-api": {
@@ -48914,7 +48912,6 @@ var tool_catalog_default = {
48914
48912
  installer: "npm",
48915
48913
  label: "Image Gen",
48916
48914
  capabilities: "AI image generation & editing via TODOFORAI backend (gpt-image)",
48917
- description: "Generate or edit images with AI.",
48918
48915
  versionCmd: "codex-imagegen-api --version 2>/dev/null | head -1"
48919
48916
  },
48920
48917
  "suno-api": {
@@ -48922,8 +48919,7 @@ var tool_catalog_default = {
48922
48919
  pkg: "@todoforai/suno-api",
48923
48920
  installer: "npm",
48924
48921
  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.",
48922
+ capabilities: "AI music generation, custom lyrics, multiple genres. Needs SUNO_API_KEY.",
48927
48923
  versionCmd: "suno-api --version 2>/dev/null | head -1"
48928
48924
  },
48929
48925
  ntn: {
@@ -48937,7 +48933,6 @@ var tool_catalog_default = {
48937
48933
  "~/.config/notion"
48938
48934
  ],
48939
48935
  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
48936
  versionCmd: "ntn --version 2>/dev/null | head -1"
48942
48937
  },
48943
48938
  "perplexity-api": {
@@ -48946,7 +48941,6 @@ var tool_catalog_default = {
48946
48941
  installer: "npm",
48947
48942
  label: "Perplexity",
48948
48943
  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
48944
  versionCmd: "perplexity-api --version 2>/dev/null | head -1"
48951
48945
  },
48952
48946
  gh: {
@@ -48961,7 +48955,7 @@ var tool_catalog_default = {
48961
48955
  "~/.config/gh/hosts.yml"
48962
48956
  ],
48963
48957
  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.",
48958
+ description: "Prefer `gh` over raw GitHub API calls.",
48965
48959
  versionCmd: "gh --version 2>/dev/null | head -1"
48966
48960
  },
48967
48961
  glab: {
@@ -48975,7 +48969,7 @@ var tool_catalog_default = {
48975
48969
  "~/.config/glab-cli/config.yml"
48976
48970
  ],
48977
48971
  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`.",
48972
+ description: "GitLab equivalent of `gh`.",
48979
48973
  versionCmd: "glab --version 2>/dev/null | head -1"
48980
48974
  },
48981
48975
  vercel: {
@@ -48989,7 +48983,7 @@ var tool_catalog_default = {
48989
48983
  "~/.local/share/com.vercel.cli/auth.json"
48990
48984
  ],
48991
48985
  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.",
48986
+ description: "`vercel deploy`; manage env vars, domains, preview URLs.",
48993
48987
  versionCmd: "vercel --version 2>/dev/null | head -1"
48994
48988
  },
48995
48989
  netlify: {
@@ -49003,7 +48997,7 @@ var tool_catalog_default = {
49003
48997
  "~/.netlify/config.json"
49004
48998
  ],
49005
48999
  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.",
49000
+ description: "`netlify deploy --prod` for production; manage env vars, forms, identity.",
49007
49001
  versionCmd: "netlify --version 2>/dev/null | head -1"
49008
49002
  },
49009
49003
  firebase: {
@@ -49017,7 +49011,7 @@ var tool_catalog_default = {
49017
49011
  "~/.config/configstore/firebase-tools.json"
49018
49012
  ],
49019
49013
  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.",
49014
+ description: "Deploy hosting/functions, manage Firestore/RTDB rules and data, auth users, emulators.",
49021
49015
  versionCmd: "firebase --version 2>/dev/null | head -1"
49022
49016
  },
49023
49017
  wrangler: {
@@ -49031,7 +49025,7 @@ var tool_catalog_default = {
49031
49025
  "~/.config/.wrangler/config/default.toml"
49032
49026
  ],
49033
49027
  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.",
49028
+ description: "Tail logs with `wrangler tail`. Use `cloudflared` for tunnels.",
49035
49029
  versionCmd: "wrangler --version 2>/dev/null | head -1"
49036
49030
  },
49037
49031
  stripe: {
@@ -49045,7 +49039,7 @@ var tool_catalog_default = {
49045
49039
  "~/.config/stripe/config.toml"
49046
49040
  ],
49047
49041
  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.",
49042
+ description: "`stripe listen` forwards webhooks locally; `stripe get/post /v1/...` for raw API.",
49049
49043
  versionCmd: "stripe --version 2>/dev/null | head -1"
49050
49044
  },
49051
49045
  flyctl: {
@@ -49059,7 +49053,7 @@ var tool_catalog_default = {
49059
49053
  "~/.fly/config.yml"
49060
49054
  ],
49061
49055
  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.",
49056
+ description: "`flyctl deploy` from app dir; manage Postgres/volumes/machines/secrets.",
49063
49057
  versionCmd: "flyctl version 2>/dev/null | head -1"
49064
49058
  },
49065
49059
  supabase: {
@@ -49073,7 +49067,7 @@ var tool_catalog_default = {
49073
49067
  "~/.config/supabase/access-token"
49074
49068
  ],
49075
49069
  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.",
49070
+ description: "Link to cloud project; runs/migrates local Postgres.",
49077
49071
  versionCmd: "supabase --version 2>/dev/null | head -1"
49078
49072
  },
49079
49073
  railway: {
@@ -49087,7 +49081,7 @@ var tool_catalog_default = {
49087
49081
  "~/.railway/config.json"
49088
49082
  ],
49089
49083
  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.",
49084
+ description: "`railway up` to deploy current dir.",
49091
49085
  versionCmd: "railway --version 2>/dev/null | head -1"
49092
49086
  },
49093
49087
  shopify: {
@@ -49100,16 +49094,23 @@ var tool_catalog_default = {
49100
49094
  "~/.config/shopify/config.json"
49101
49095
  ],
49102
49096
  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
49097
  versionCmd: "shopify version 2>/dev/null | head -1"
49105
49098
  },
49099
+ "shop-app": {
49100
+ category: "ecommerce",
49101
+ pkg: "@todoforai/shop-app",
49102
+ installer: "npm",
49103
+ label: "Shop",
49104
+ capabilities: "Product search & price comparison across Shopify stores",
49105
+ 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.",
49106
+ versionCmd: "shop-app --version 2>/dev/null | head -1"
49107
+ },
49106
49108
  "datadog-ci": {
49107
49109
  category: "monitoring",
49108
49110
  pkg: "@datadog/datadog-ci",
49109
49111
  installer: "npm",
49110
49112
  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.",
49113
+ capabilities: "CI test visibility, sourcemap uploads, deployment tracking. Needs DATADOG_API_KEY.",
49113
49114
  versionCmd: "datadog-ci version 2>/dev/null | head -1"
49114
49115
  },
49115
49116
  "sentry-cli": {
@@ -49123,7 +49124,7 @@ var tool_catalog_default = {
49123
49124
  "~/.sentryclirc"
49124
49125
  ],
49125
49126
  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>`.",
49127
+ description: "`sentry-cli releases new <version>`.",
49127
49128
  versionCmd: "sentry-cli --version 2>/dev/null | head -1"
49128
49129
  },
49129
49130
  todoai: {
@@ -49132,7 +49133,7 @@ var tool_catalog_default = {
49132
49133
  installer: "bun",
49133
49134
  label: "TODOforAI",
49134
49135
  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.",
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.',
49136
49137
  installCmd: "bun add -g @todoforai/cli",
49137
49138
  versionCmd: "todoai --version 2>/dev/null | head -1",
49138
49139
  internal: true
@@ -49143,7 +49144,7 @@ var tool_catalog_default = {
49143
49144
  installer: "npm",
49144
49145
  label: "Postman",
49145
49146
  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>`.",
49147
+ description: "`newman run <collection.json> -e <env.json>`.",
49147
49148
  versionCmd: "newman --version 2>/dev/null | head -1"
49148
49149
  },
49149
49150
  curl: {
@@ -49164,22 +49165,20 @@ var tool_catalog_default = {
49164
49165
  installer: "binary",
49165
49166
  label: "Tunnel",
49166
49167
  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.",
49168
+ description: "`cloudflared tunnel --url http://localhost:<port>` prints a public https URL.",
49168
49169
  versionCmd: "cloudflared --version 2>/dev/null | head -1"
49169
49170
  },
49170
49171
  vault: {
49171
49172
  category: "security",
49172
49173
  pkg: "vault",
49173
49174
  installer: "binary",
49174
- preinstallCloud: true,
49175
49175
  label: "HashiCorp Vault",
49176
49176
  statusCmd: "vault token lookup >/dev/null 2>&1 && echo authenticated",
49177
49177
  loginCmd: "vault login",
49178
49178
  credentialPaths: [
49179
49179
  "~/.vault-token"
49180
49180
  ],
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.",
49181
+ capabilities: "Secrets management, dynamic credentials. Needs VAULT_ADDR + VAULT_TOKEN; for credential read/write use `tfa-vault` instead.",
49183
49182
  versionCmd: "vault --version 2>/dev/null | head -1"
49184
49183
  },
49185
49184
  rclone: {
@@ -49194,7 +49193,6 @@ var tool_catalog_default = {
49194
49193
  "~/.config/rclone/rclone.conf"
49195
49194
  ],
49196
49195
  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
49196
  subProviders: {
49199
49197
  gdrive: {
49200
49198
  label: "Google Drive",
@@ -49239,8 +49237,7 @@ var tool_catalog_default = {
49239
49237
  pkg: "pymupdf",
49240
49238
  installer: "pip",
49241
49239
  label: "PDF",
49242
- capabilities: "PDF text editing, extraction, merge, split",
49243
- description: "Read, edit, merge, or split PDFs programmatically. Call via `python3 -c`.",
49240
+ capabilities: "PDF text editing, extraction, merge, split. Call via `python3 -c`.",
49244
49241
  versionCmd: "python3 -c 'import pymupdf; print(pymupdf.__version__)' 2>/dev/null",
49245
49242
  preinstallCloud: true
49246
49243
  },
@@ -49251,16 +49248,16 @@ var tool_catalog_default = {
49251
49248
  preinstallCloud: true,
49252
49249
  label: "Browser",
49253
49250
  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.",
49251
+ 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
49252
  versionCmd: "agent-browser --version 2>/dev/null | head -1"
49256
49253
  },
49257
49254
  "todoforai-browser": {
49258
49255
  category: "development",
49259
49256
  pkg: "@todoforai/browser",
49260
49257
  installer: "npm",
49258
+ preinstallCloud: true,
49261
49259
  label: "Browser (Extension)",
49262
49260
  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
49261
  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
49262
  internal: true
49266
49263
  },
@@ -49270,55 +49267,19 @@ var tool_catalog_default = {
49270
49267
  installer: "npm",
49271
49268
  label: "Sub-agent",
49272
49269
  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
49270
  versionCmd: "todoforai-subagent --version 2>/dev/null | head -1",
49275
49271
  installCmd: "bun add -g @todoforai/subagent",
49276
49272
  internal: true
49277
49273
  },
49278
- "todoforai-summary": {
49274
+ "tfa-handoff": {
49279
49275
  category: "development",
49280
- pkg: "@todoforai/summary",
49276
+ pkg: "@todoforai/tfa-handoff",
49281
49277
  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": {
49290
- category: "development",
49291
- pkg: "@todoforai/tfa-explore",
49292
- 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",
49278
+ label: "Handoff",
49279
+ capabilities: "Compact a conversation into a handoff document as a hidden TODO; pipe filter for `todoai --inspect … | tfa-handoff … | todoai`",
49280
+ description: "Compact a conversation into a handoff document for a fresh agent.",
49281
+ versionCmd: "tfa-handoff --version 2>/dev/null | head -1",
49282
+ installCmd: "bun add -g @todoforai/tfa-handoff",
49322
49283
  internal: true
49323
49284
  },
49324
49285
  "tfa-vault": {
@@ -49326,8 +49287,8 @@ var tool_catalog_default = {
49326
49287
  pkg: "@todoforai/vault",
49327
49288
  installer: "npm",
49328
49289
  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.",
49290
+ 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>`.",
49331
49292
  versionCmd: "tfa-vault --version 2>/dev/null | head -1",
49332
49293
  statusCmd: "tfa-vault whoami",
49333
49294
  installCmd: "bun add -g @todoforai/vault",
@@ -51610,9 +51571,10 @@ var STREAM_FIRST = 1e4;
51610
51571
  var STREAM_LAST = 1e4;
51611
51572
  var RUN_OUTPUT_CAP = 256 * 1024;
51612
51573
  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 }
51574
+ safe: { firstLimit: STREAM_FIRST, lastLimit: STREAM_LAST, hardCap: STREAM_FIRST + STREAM_LAST, lineLimit: MAX_LINE_LEN },
51575
+ wide: { firstLimit: STREAM_FIRST, lastLimit: STREAM_LAST, hardCap: STREAM_FIRST + STREAM_LAST, lineLimit: Infinity },
51576
+ full: { firstLimit: Infinity, lastLimit: 0, hardCap: RUN_OUTPUT_CAP, lineLimit: MAX_LINE_LEN },
51577
+ raw: { firstLimit: Infinity, lastLimit: 0, hardCap: Infinity, lineLimit: Infinity }
51616
51578
  };
51617
51579
  var DEFAULT_OUTPUT_MODE = "safe";
51618
51580
  function resolveOutputPolicy(mode) {
@@ -51635,6 +51597,13 @@ function truncateLines(text, { maxLines = MAX_RESULT_LINES, maxLineLen = MAX_LIN
51635
51597
  ... (output truncated)`;
51636
51598
  return output;
51637
51599
  }
51600
+ function capLineWidth(text, lineLimit) {
51601
+ if (!isFinite(lineLimit))
51602
+ return text;
51603
+ return text.split(`
51604
+ `).map((line) => line.length > lineLimit ? line.slice(0, lineLimit) + ` ...[+${line.length - lineLimit} chars]` : line).join(`
51605
+ `);
51606
+ }
51638
51607
  function formatTruncationNotice(totalLen, firstLimit, lastPart) {
51639
51608
  const dropped = totalLen - firstLimit - lastPart.length;
51640
51609
  return `
@@ -51697,9 +51666,11 @@ class OutputBuffer {
51697
51666
  truncMsgSent = false;
51698
51667
  headLimit;
51699
51668
  lastLimit;
51669
+ lineLimit;
51700
51670
  constructor(policy = OUTPUT_POLICIES[DEFAULT_OUTPUT_MODE]) {
51701
51671
  this.headLimit = Math.min(policy.firstLimit, policy.hardCap);
51702
51672
  this.lastLimit = Math.min(policy.lastLimit, policy.hardCap - this.headLimit);
51673
+ this.lineLimit = policy.lineLimit;
51703
51674
  }
51704
51675
  append(text) {
51705
51676
  this.totalLen += text.length;
@@ -51733,15 +51704,15 @@ class OutputBuffer {
51733
51704
  }
51734
51705
  getOutput() {
51735
51706
  if (!this.truncated)
51736
- return this.firstPart;
51737
- return this.firstPart + `
51707
+ return capLineWidth(this.firstPart, this.lineLimit);
51708
+ return capLineWidth(this.firstPart, this.lineLimit) + `
51738
51709
 
51739
51710
  ... [truncated: showing first ${this.firstPart.length} and last ${this.lastPart.length} chars of ${this.totalLen} total] ...
51740
51711
 
51741
- ${this.lastPart}`;
51712
+ ${capLineWidth(this.lastPart, this.lineLimit)}`;
51742
51713
  }
51743
51714
  getRawIfComplete() {
51744
- return this.truncated ? null : this.firstPart;
51715
+ return this.truncated ? null : capLineWidth(this.firstPart, this.lineLimit);
51745
51716
  }
51746
51717
  }
51747
51718
  var processes = new Map;
@@ -52624,7 +52595,7 @@ register("read_file_base64", async (args) => {
52624
52595
  return { path: fullPath, base64: data.toString("base64"), bytes: data.length };
52625
52596
  });
52626
52597
  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;
52598
+ 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
52599
  const { execSync: execWhich } = await import("child_process");
52629
52600
  const whichCmd = process.platform === "win32" ? "where" : "which";
52630
52601
  const which = (bin) => {
@@ -52703,7 +52674,7 @@ register("search_files", async (args) => {
52703
52674
  output = lines.join(`
52704
52675
  `);
52705
52676
  }
52706
- return { result: truncateLines(output, { maxLines: head }) };
52677
+ return { result: truncateLines(output, { maxLines: head, maxLineLen: resolveOutputPolicy(outputMode).lineLimit }) };
52707
52678
  }
52708
52679
  if (code === 1)
52709
52680
  return { result: "No matches found." };
@@ -53006,6 +52977,8 @@ class TODOforAIEdge {
53006
52977
  browserExtensionBridge;
53007
52978
  stopping = false;
53008
52979
  reconnectTimer;
52980
+ wakeReconnect;
52981
+ connectedAt = 0;
53009
52982
  edgeConfig = {
53010
52983
  id: "",
53011
52984
  name: "Name uninitialized",
@@ -53288,25 +53261,40 @@ class TODOforAIEdge {
53288
53261
  console.log(`[warn] Unknown message type: ${msgType}`);
53289
53262
  }
53290
53263
  }
53291
- startHeartbeat(onStale) {
53264
+ startHeartbeat(ws2, onStale) {
53292
53265
  this.stopHeartbeat();
53266
+ const intervalMs = 30000;
53267
+ const resumeGapMs = 60000;
53268
+ let lastTick = Date.now();
53293
53269
  let pongReceived = true;
53294
- this.ws?.on("pong", () => {
53270
+ ws2.on("pong", () => {
53295
53271
  pongReceived = true;
53296
53272
  });
53297
53273
  this.heartbeatTimer = setInterval(() => {
53274
+ const now = Date.now();
53275
+ const elapsed = now - lastTick;
53276
+ lastTick = now;
53277
+ if (elapsed > resumeGapMs) {
53278
+ console.log(`[warn] Heartbeat delayed ${Math.round(elapsed / 1000)}s (sleep/resume likely), reconnecting`);
53279
+ try {
53280
+ ws2.terminate();
53281
+ } catch {}
53282
+ onStale();
53283
+ return;
53284
+ }
53298
53285
  if (!pongReceived) {
53299
53286
  console.log("[warn] No pong received, terminating stale connection");
53300
- this.stopHeartbeat();
53301
- this.ws?.terminate();
53287
+ try {
53288
+ ws2.terminate();
53289
+ } catch {}
53302
53290
  onStale();
53303
53291
  return;
53304
53292
  }
53305
53293
  pongReceived = false;
53306
53294
  try {
53307
- this.ws?.ping();
53295
+ ws2.ping();
53308
53296
  } catch {}
53309
- }, 30000);
53297
+ }, intervalMs);
53310
53298
  }
53311
53299
  stopHeartbeat() {
53312
53300
  if (this.heartbeatTimer) {
@@ -53319,20 +53307,30 @@ class TODOforAIEdge {
53319
53307
  const url = `${this.wsUrl}?fingerprint=${encodeURIComponent(this.fingerprint)}`;
53320
53308
  if (this.debug)
53321
53309
  console.log(`[info] Connecting to ${url}`);
53322
- this.ws = new wrapper_default(url, [this.api.apiKey], {
53310
+ const ws2 = new wrapper_default(url, [this.api.apiKey], {
53323
53311
  maxPayload: 5 * 1024 * 1024,
53324
53312
  rejectUnauthorized: false
53325
53313
  });
53326
- this.ws.on("open", () => {
53327
- this.connected = true;
53328
- console.log("[info] WebSocket connected");
53329
- this.startHeartbeat(() => {
53314
+ this.ws = ws2;
53315
+ let settled = false;
53316
+ const settle = (fn2) => {
53317
+ if (settled)
53318
+ return;
53319
+ settled = true;
53320
+ if (this.ws === ws2) {
53321
+ this.stopHeartbeat();
53330
53322
  this.connected = false;
53331
53323
  this.ws = null;
53332
- resolve();
53333
- });
53324
+ }
53325
+ fn2();
53326
+ };
53327
+ ws2.on("open", () => {
53328
+ this.connected = true;
53329
+ this.connectedAt = Date.now();
53330
+ console.log("[info] WebSocket connected");
53331
+ this.startHeartbeat(ws2, () => settle(() => resolve(0)));
53334
53332
  });
53335
- this.ws.on("message", (data, isBinary) => {
53333
+ ws2.on("message", (data, isBinary) => {
53336
53334
  if (isBinary) {
53337
53335
  const frame = data instanceof Buffer ? new Uint8Array(data) : new Uint8Array(data);
53338
53336
  this.storeBinaryFrame(frame);
@@ -53340,36 +53338,30 @@ class TODOforAIEdge {
53340
53338
  }
53341
53339
  this.handleMessage(data.toString()).catch((e) => {
53342
53340
  if (e instanceof AuthenticationError || e instanceof ServerError) {
53343
- this.ws?.close();
53344
- reject(e);
53341
+ ws2.close();
53342
+ settle(() => reject(e));
53345
53343
  } else {
53346
53344
  console.error("[handler error]", e);
53347
53345
  }
53348
53346
  });
53349
53347
  });
53350
- this.ws.on("close", (code, reason) => {
53351
- this.stopHeartbeat();
53352
- this.connected = false;
53353
- this.ws = null;
53348
+ ws2.on("close", (code, reason) => {
53354
53349
  const reasonText = reason?.toString() || "<empty>";
53355
- const clean = code === 1000;
53356
- console.log(`[info] WebSocket closed code=${code} clean=${clean} reason=${reasonText}`);
53350
+ console.log(`[info] WebSocket closed code=${code} clean=${code === 1000} reason=${reasonText}`);
53357
53351
  if (code === 4001) {
53358
53352
  console.log(`\x1B[33m[info] ${reasonText}. Not reconnecting.\x1B[0m`);
53359
53353
  console.log(`\x1B[33m[info] To replace the existing connection, restart with: todoforai-edge --kill\x1B[0m`);
53360
- reject(new ServerError(reasonText));
53354
+ settle(() => reject(new ServerError(reasonText)));
53361
53355
  } else if (code === 4002) {
53362
53356
  console.log(`\x1B[33m[info] ${reasonText}. This instance was replaced by a new connection.\x1B[0m`);
53363
- reject(new ServerError(reasonText));
53357
+ settle(() => reject(new ServerError(reasonText)));
53364
53358
  } else {
53365
- resolve();
53359
+ settle(() => resolve(code));
53366
53360
  }
53367
53361
  });
53368
- this.ws.on("error", (err2) => {
53369
- this.stopHeartbeat();
53370
- this.connected = false;
53371
- this.ws = null;
53372
- reject(err2);
53362
+ ws2.on("error", (err2) => {
53363
+ console.error(`[error] WebSocket error: ${err2.message}`);
53364
+ settle(() => resolve(0));
53373
53365
  });
53374
53366
  });
53375
53367
  }
@@ -53379,45 +53371,34 @@ class TODOforAIEdge {
53379
53371
  } catch {}
53380
53372
  this.fingerprint = generateFingerprint();
53381
53373
  console.log(`\x1B[36m\x1B[1m\uD83D\uDC46 Fingerprint:\x1B[0m ${this.fingerprint}`);
53382
- const maxAttempts = 20;
53383
53374
  let attempt = 0;
53384
- while (attempt < maxAttempts && !this.stopping) {
53385
- console.log(`[info] Connecting (attempt ${attempt + 1}/${maxAttempts})`);
53375
+ while (!this.stopping) {
53376
+ console.log(`[info] Connecting${attempt > 0 ? ` (retry ${attempt})` : ""}`);
53377
+ this.connectedAt = 0;
53386
53378
  try {
53387
53379
  await this.connect();
53388
- if (this.stopping)
53389
- break;
53390
- attempt = 0;
53391
53380
  } 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
- });
53381
+ const label = e instanceof AuthenticationError ? "Authentication failed" : "Server error";
53382
+ console.error(`\x1B[31m${label}: ${e.message}\x1B[0m`);
53383
+ break;
53412
53384
  }
53413
- }
53414
- if (attempt >= maxAttempts) {
53415
- console.error("\x1B[31mMax reconnection attempts reached.\x1B[0m");
53385
+ if (this.stopping)
53386
+ break;
53387
+ if (this.connectedAt && Date.now() - this.connectedAt > 60000)
53388
+ attempt = 0;
53389
+ attempt++;
53390
+ const delay = Math.min(2 * 2 ** Math.min(attempt - 1, 4), 30);
53391
+ console.log(`[info] Reconnecting in ${delay}s...`);
53392
+ await new Promise((r) => {
53393
+ this.wakeReconnect = r;
53394
+ this.reconnectTimer = setTimeout(r, delay * 1000);
53395
+ });
53416
53396
  }
53417
53397
  }
53418
53398
  stop() {
53419
53399
  this.stopping = true;
53420
53400
  clearTimeout(this.reconnectTimer);
53401
+ this.wakeReconnect?.();
53421
53402
  this.stopHeartbeat();
53422
53403
  this.browserExtensionBridge.stop();
53423
53404
  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.21",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "todoforai-edge": "dist/index.js"