@todoforai/edge 0.13.9 → 0.13.11

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 +367 -192
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -656,6 +656,8 @@ var require_receiver = __commonJS((exports, module) => {
656
656
  this._binaryType = options.binaryType || BINARY_TYPES[0];
657
657
  this._extensions = options.extensions || {};
658
658
  this._isServer = !!options.isServer;
659
+ this._maxBufferedChunks = options.maxBufferedChunks | 0;
660
+ this._maxFragments = options.maxFragments | 0;
659
661
  this._maxPayload = options.maxPayload | 0;
660
662
  this._skipUTF8Validation = !!options.skipUTF8Validation;
661
663
  this[kWebSocket] = undefined;
@@ -678,6 +680,10 @@ var require_receiver = __commonJS((exports, module) => {
678
680
  _write(chunk, encoding, cb) {
679
681
  if (this._opcode === 8 && this._state == GET_INFO)
680
682
  return cb();
683
+ if (this._maxBufferedChunks > 0 && this._buffers.length >= this._maxBufferedChunks) {
684
+ cb(this.createError(RangeError, "Too many buffered chunks", false, 1008, "WS_ERR_TOO_MANY_BUFFERED_PARTS"));
685
+ return;
686
+ }
681
687
  this._bufferedBytes += chunk.length;
682
688
  this._buffers.push(chunk);
683
689
  this.startLoop(cb);
@@ -881,6 +887,11 @@ var require_receiver = __commonJS((exports, module) => {
881
887
  return;
882
888
  }
883
889
  if (data.length) {
890
+ if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
891
+ const error = this.createError(RangeError, "Too many message fragments", false, 1008, "WS_ERR_TOO_MANY_BUFFERED_PARTS");
892
+ cb(error);
893
+ return;
894
+ }
884
895
  this._messageLength = this._totalPayloadLength;
885
896
  this._fragments.push(data);
886
897
  }
@@ -898,6 +909,11 @@ var require_receiver = __commonJS((exports, module) => {
898
909
  cb(error);
899
910
  return;
900
911
  }
912
+ if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
913
+ const error = this.createError(RangeError, "Too many message fragments", false, 1008, "WS_ERR_TOO_MANY_BUFFERED_PARTS");
914
+ cb(error);
915
+ return;
916
+ }
901
917
  this._fragments.push(buf);
902
918
  }
903
919
  this.dataMessage(cb);
@@ -1805,6 +1821,8 @@ var require_websocket = __commonJS((exports, module) => {
1805
1821
  binaryType: this.binaryType,
1806
1822
  extensions: this._extensions,
1807
1823
  isServer: this._isServer,
1824
+ maxBufferedChunks: options.maxBufferedChunks,
1825
+ maxFragments: options.maxFragments,
1808
1826
  maxPayload: options.maxPayload,
1809
1827
  skipUTF8Validation: options.skipUTF8Validation
1810
1828
  });
@@ -2047,6 +2065,8 @@ var require_websocket = __commonJS((exports, module) => {
2047
2065
  autoPong: true,
2048
2066
  closeTimeout: CLOSE_TIMEOUT,
2049
2067
  protocolVersion: protocolVersions[1],
2068
+ maxBufferedChunks: 1024 * 1024,
2069
+ maxFragments: 128 * 1024,
2050
2070
  maxPayload: 100 * 1024 * 1024,
2051
2071
  skipUTF8Validation: false,
2052
2072
  perMessageDeflate: true,
@@ -2285,6 +2305,8 @@ var require_websocket = __commonJS((exports, module) => {
2285
2305
  websocket.setSocket(socket, head, {
2286
2306
  allowSynchronousEvents: opts.allowSynchronousEvents,
2287
2307
  generateMask: opts.generateMask,
2308
+ maxBufferedChunks: opts.maxBufferedChunks,
2309
+ maxFragments: opts.maxFragments,
2288
2310
  maxPayload: opts.maxPayload,
2289
2311
  skipUTF8Validation: opts.skipUTF8Validation
2290
2312
  });
@@ -2620,6 +2642,8 @@ var require_websocket_server = __commonJS((exports, module) => {
2620
2642
  options = {
2621
2643
  allowSynchronousEvents: true,
2622
2644
  autoPong: true,
2645
+ maxBufferedChunks: 1024 * 1024,
2646
+ maxFragments: 128 * 1024,
2623
2647
  maxPayload: 100 * 1024 * 1024,
2624
2648
  skipUTF8Validation: false,
2625
2649
  perMessageDeflate: false,
@@ -2846,6 +2870,8 @@ var require_websocket_server = __commonJS((exports, module) => {
2846
2870
  socket.removeListener("error", socketOnError);
2847
2871
  ws.setSocket(socket, head, {
2848
2872
  allowSynchronousEvents: this.options.allowSynchronousEvents,
2873
+ maxBufferedChunks: this.options.maxBufferedChunks,
2874
+ maxFragments: this.options.maxFragments,
2849
2875
  maxPayload: this.options.maxPayload,
2850
2876
  skipUTF8Validation: this.options.skipUTF8Validation
2851
2877
  });
@@ -47843,6 +47869,7 @@ Options:
47843
47869
  --api-key <key> API key (env: TODOFORAI_API_KEY)
47844
47870
  --api-url <url> API URL (env: TODOFORAI_API_URL, default: https://api.todofor.ai)
47845
47871
  --add-path <path> Add workspace path to this edge
47872
+ --max-timeout <sec> Floor for shell execution timeout
47846
47873
  --kill Replace any existing edge instance for this user+server
47847
47874
  --debug Verbose logging (env: TODOFORAI_DEBUG=1)
47848
47875
  -v, --version Print version and exit
@@ -47861,6 +47888,7 @@ function loadConfig() {
47861
47888
  debug: { type: "boolean", default: false },
47862
47889
  kill: { type: "boolean", default: false },
47863
47890
  "add-path": { type: "string" },
47891
+ "max-timeout": { type: "string" },
47864
47892
  version: { type: "boolean", short: "v", default: false },
47865
47893
  help: { type: "boolean", short: "h", default: false }
47866
47894
  },
@@ -47884,17 +47912,33 @@ function loadConfig() {
47884
47912
  const p = values["add-path"];
47885
47913
  addWorkspacePath = path.resolve(p.replace(/^~/, process.env.HOME || "~"));
47886
47914
  }
47887
- return { apiUrl, apiKey, debug, kill, addWorkspacePath, subcommand };
47915
+ const maxTimeout = values["max-timeout"] ? Math.max(0, parseInt(values["max-timeout"], 10) || 0) : undefined;
47916
+ return { apiUrl, apiKey, debug, kill, addWorkspacePath, maxTimeout, subcommand };
47888
47917
  }
47889
- var CREDENTIALS_PATH = path.join(os.homedir(), ".todoforai", "credentials.json");
47890
- function readCredentials() {
47918
+ function credentialsPath() {
47919
+ const sys = os.platform();
47920
+ if (sys === "win32") {
47921
+ return path.join(os.homedir(), "AppData", "Roaming", "todoforai", "credentials.json");
47922
+ }
47923
+ if (sys === "darwin") {
47924
+ return path.join(os.homedir(), "Library", "Application Support", "todoforai", "credentials.json");
47925
+ }
47926
+ const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
47927
+ return path.join(xdg, "todoforai", "credentials.json");
47928
+ }
47929
+ var CREDENTIALS_PATH = credentialsPath();
47930
+ var LEGACY_CREDENTIALS_PATH = path.join(os.homedir(), ".todoforai", "credentials.json");
47931
+ function readFileMap(p) {
47891
47932
  try {
47892
- return JSON.parse(fs.readFileSync(CREDENTIALS_PATH, "utf-8"));
47933
+ return JSON.parse(fs.readFileSync(p, "utf-8"));
47893
47934
  } catch {
47894
47935
  return {};
47895
47936
  }
47896
47937
  }
47897
- function writeCredentials(creds) {
47938
+ function readCredentials() {
47939
+ return { ...readFileMap(LEGACY_CREDENTIALS_PATH), ...readFileMap(CREDENTIALS_PATH) };
47940
+ }
47941
+ function writeNewFile(creds) {
47898
47942
  const dir = path.dirname(CREDENTIALS_PATH);
47899
47943
  fs.mkdirSync(dir, { recursive: true, mode: 448 });
47900
47944
  fs.writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), { mode: 384 });
@@ -47906,14 +47950,23 @@ function loadSavedApiKey(apiUrl) {
47906
47950
  return readCredentials()[apiUrl] || null;
47907
47951
  }
47908
47952
  function saveApiKey(apiUrl, apiKey) {
47909
- const creds = readCredentials();
47953
+ const creds = readFileMap(CREDENTIALS_PATH);
47910
47954
  creds[apiUrl] = apiKey;
47911
- writeCredentials(creds);
47955
+ writeNewFile(creds);
47912
47956
  }
47913
47957
  function clearApiKey(apiUrl) {
47914
- const creds = readCredentials();
47915
- delete creds[apiUrl];
47916
- writeCredentials(creds);
47958
+ const creds = readFileMap(CREDENTIALS_PATH);
47959
+ if (apiUrl in creds) {
47960
+ delete creds[apiUrl];
47961
+ writeNewFile(creds);
47962
+ }
47963
+ const legacy = readFileMap(LEGACY_CREDENTIALS_PATH);
47964
+ if (apiUrl in legacy) {
47965
+ delete legacy[apiUrl];
47966
+ try {
47967
+ fs.writeFileSync(LEGACY_CREDENTIALS_PATH, JSON.stringify(legacy, null, 2), { mode: 384 });
47968
+ } catch {}
47969
+ }
47917
47970
  }
47918
47971
 
47919
47972
  // node_modules/ws/wrapper.mjs
@@ -47938,7 +47991,7 @@ function getConnectionEnv() {
47938
47991
  const { apiUrl, apiKey } = getter();
47939
47992
  if (!apiUrl || !apiKey)
47940
47993
  return {};
47941
- return { TODOFORAI_API_URL: apiUrl, TODOFORAI_API_KEY: apiKey };
47994
+ return { TODOFORAI_API_URL: apiUrl, TODOFORAI_API_TOKEN: apiKey };
47942
47995
  }
47943
47996
 
47944
47997
  // src/constants.ts
@@ -48710,6 +48763,7 @@ var tool_catalog_default = {
48710
48763
  "~/.zele/sqlite.db"
48711
48764
  ],
48712
48765
  capabilities: "Read inbox, search/send/reply email, email addresses — multi-account Gmail, OAuth browser login for setup.",
48766
+ description: "Use for anything Gmail: reading the user's inbox, searching mail, sending or replying to email. Multi-account, OAuth login via `zele login`.",
48713
48767
  versionCmd: "zele --version 2>/dev/null | head -1"
48714
48768
  },
48715
48769
  xurl: {
@@ -48723,6 +48777,7 @@ var tool_catalog_default = {
48723
48777
  "~/.xurl"
48724
48778
  ],
48725
48779
  capabilities: "Post tweets, reply & quote, read timelines, search tweets, like & repost, follow/unfollow, DMs, media uploads",
48780
+ 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>`.",
48726
48781
  versionCmd: "xurl --version 2>/dev/null | head -1"
48727
48782
  },
48728
48783
  "tiktok-uploader": {
@@ -48736,7 +48791,7 @@ var tool_catalog_default = {
48736
48791
  "~/.tiktok/cookies.txt"
48737
48792
  ],
48738
48793
  capabilities: "Upload videos, batch uploads, schedule posts, custom covers, hashtags & mentions",
48739
- description: "Cookie-based auth: Run 'npx @todoforai/tiktok-cookie-helper' to set up. Login to tiktok.com → Install 'Get cookies.txt LOCALLY' extension → Export cookies → Helper saves to ~/.tiktok/cookies.txt",
48794
+ description: "Use to upload videos to TikTok (single or batch), schedule posts, set covers/hashtags/mentions. Cookie-based auth: run `npx @todoforai/tiktok-cookie-helper` log in to tiktok.com → export cookies via 'Get cookies.txt LOCALLY' extension → saved to ~/.tiktok/cookies.txt.",
48740
48795
  versionCmd: "pip show tiktok-uploader 2>/dev/null | grep -oP 'Version: \\K.*'"
48741
48796
  },
48742
48797
  instagrapi: {
@@ -48745,6 +48800,7 @@ var tool_catalog_default = {
48745
48800
  installer: "pip",
48746
48801
  label: "Instagram",
48747
48802
  capabilities: "Upload photos & reels, post stories, send DMs, like & comment, manage followers",
48803
+ description: 'Use for Instagram automation: upload photos/reels, post stories, DMs, like/comment, follower management. Python library; call via `python3 -c "from instagrapi import Client; ..."`.',
48748
48804
  statusCmd: 'python3 -c "import instagrapi" 2>/dev/null',
48749
48805
  versionCmd: "pip show instagrapi 2>/dev/null | grep -oP 'Version: \\K.*'"
48750
48806
  },
@@ -48759,6 +48815,7 @@ var tool_catalog_default = {
48759
48815
  "~/.local/share/mudslide"
48760
48816
  ],
48761
48817
  capabilities: "Send messages, send images & files, send locations & polls, group management, QR code login",
48818
+ description: "Use to send WhatsApp messages/files/media/locations/polls from CLI. QR-code login via `mudslide login`.",
48762
48819
  versionCmd: "mudslide --version 2>/dev/null | head -1"
48763
48820
  },
48764
48821
  "telegram-send": {
@@ -48772,6 +48829,7 @@ var tool_catalog_default = {
48772
48829
  "~/.config/telegram-send/telegram-send.conf"
48773
48830
  ],
48774
48831
  capabilities: "Send messages, send files & images, send video & audio, Markdown/HTML formatting, channel & group support",
48832
+ description: "Use to send Telegram messages/files from CLI (one-way bot output). Configure once with `telegram-send --configure`. For full Telegram automation use a different tool.",
48775
48833
  versionCmd: "telegram-send --version 2>/dev/null | head -1"
48776
48834
  },
48777
48835
  "apollo-api": {
@@ -48780,6 +48838,7 @@ var tool_catalog_default = {
48780
48838
  installer: "npm",
48781
48839
  label: "Apollo",
48782
48840
  capabilities: "Lead search & enrichment, email sequences, CRM sync",
48841
+ 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.",
48783
48842
  versionCmd: "apollo-api --version 2>/dev/null | head -1"
48784
48843
  },
48785
48844
  "meta-ads": {
@@ -48798,6 +48857,7 @@ var tool_catalog_default = {
48798
48857
  installer: "npm",
48799
48858
  label: "ElevenLabs",
48800
48859
  capabilities: "Text-to-speech, voice cloning, multiple languages",
48860
+ description: "Use to generate speech audio from text, clone voices, or list voices. Needs ELEVENLABS_API_KEY.",
48801
48861
  versionCmd: "elevenlabs-api --version 2>/dev/null | head -1"
48802
48862
  },
48803
48863
  "suno-api": {
@@ -48806,12 +48866,37 @@ var tool_catalog_default = {
48806
48866
  installer: "npm",
48807
48867
  label: "Suno",
48808
48868
  capabilities: "AI music generation, custom lyrics, multiple genres",
48869
+ description: "Use to generate music/songs from a prompt or custom lyrics. Needs SUNO_API_KEY.",
48809
48870
  versionCmd: "suno-api --version 2>/dev/null | head -1"
48810
48871
  },
48872
+ ntn: {
48873
+ category: "utility",
48874
+ pkg: "ntn",
48875
+ installer: "npm",
48876
+ label: "Notion",
48877
+ statusCmd: "ntn doctor 2>&1 | grep -E 'Default workspace\\s+✔' >/dev/null",
48878
+ loginCmd: "ntn login",
48879
+ credentialPaths: [
48880
+ "~/.config/notion"
48881
+ ],
48882
+ capabilities: "Official Notion CLI: workspace-wide OAuth login, raw API calls (`ntn api <path>`), page/datasource management, file uploads, Workers deploy.",
48883
+ description: "Two auth tracks: (1) `ntn login` for Workers/internal commands (`ntn doctor`, `ntn workers`) — workspace OAuth stored in keychain. (2) Public REST API (`ntn api`, `ntn pages`, `ntn datasources`, `ntn files`) requires NOTION_API_TOKEN env var (integration token from https://www.notion.so/my-integrations) — and the target pages must be Connected to that integration via the Notion UI: page `...` → Connections. Examples: `ntn api /v1/users/me`, `ntn api /v1/search -X POST -d '{\"query\":\"...\"}'`, `ntn pages get <id>` (Markdown export), `ntn pages create --parent page:<id> --content '# Title'`, `ntn pages update <id> --content '...'`, `ntn pages trash <id> --yes`. Use `ntn datasources resolve <database-id>` then `ntn datasources query <data-source-id>`.",
48884
+ versionCmd: "ntn --version 2>/dev/null | head -1"
48885
+ },
48886
+ "perplexity-api": {
48887
+ category: "utility",
48888
+ pkg: "@todoforai/perplexity-api",
48889
+ installer: "npm",
48890
+ label: "Perplexity",
48891
+ capabilities: "AI-powered web search, chat completions, async chat, embeddings and agent runs",
48892
+ description: "Use when you need current web-grounded answers with citations (news, prices, docs released after training cutoff). Prefer over plain LLM when freshness/sources matter. Needs PERPLEXITY_API_KEY.",
48893
+ versionCmd: "perplexity-api --version 2>/dev/null | head -1"
48894
+ },
48811
48895
  gh: {
48812
48896
  category: "development",
48813
48897
  pkg: "gh",
48814
48898
  installer: "binary",
48899
+ preinstallCloud: true,
48815
48900
  label: "GitHub",
48816
48901
  statusCmd: "gh auth status",
48817
48902
  loginCmd: "gh auth login",
@@ -48819,6 +48904,7 @@ var tool_catalog_default = {
48819
48904
  "~/.config/gh/hosts.yml"
48820
48905
  ],
48821
48906
  capabilities: "Repos, PRs & issues, actions & releases, gists & SSH keys",
48907
+ 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.",
48822
48908
  versionCmd: "gh --version 2>/dev/null | head -1"
48823
48909
  },
48824
48910
  glab: {
@@ -48832,6 +48918,7 @@ var tool_catalog_default = {
48832
48918
  "~/.config/glab-cli/config.yml"
48833
48919
  ],
48834
48920
  capabilities: "Repos, MRs & issues, CI/CD pipelines, releases",
48921
+ description: "Use for any GitLab operation: MRs, issues, pipelines, releases, repo management. GitLab equivalent of `gh`.",
48835
48922
  versionCmd: "glab --version 2>/dev/null | head -1"
48836
48923
  },
48837
48924
  vercel: {
@@ -48845,6 +48932,7 @@ var tool_catalog_default = {
48845
48932
  "~/.local/share/com.vercel.cli/auth.json"
48846
48933
  ],
48847
48934
  capabilities: "Deploy & preview, environment variables, domain management",
48935
+ description: "Use to deploy a project to Vercel (`vercel deploy`), manage env vars, domains, and preview URLs.",
48848
48936
  versionCmd: "vercel --version 2>/dev/null | head -1"
48849
48937
  },
48850
48938
  netlify: {
@@ -48858,6 +48946,7 @@ var tool_catalog_default = {
48858
48946
  "~/.netlify/config.json"
48859
48947
  ],
48860
48948
  capabilities: "Deploy & preview, serverless functions, forms & identity",
48949
+ description: "Use to deploy sites/functions to Netlify, manage env vars, forms, identity. `netlify deploy --prod` for production.",
48861
48950
  versionCmd: "netlify --version 2>/dev/null | head -1"
48862
48951
  },
48863
48952
  firebase: {
@@ -48871,6 +48960,7 @@ var tool_catalog_default = {
48871
48960
  "~/.config/configstore/firebase-tools.json"
48872
48961
  ],
48873
48962
  capabilities: "Hosting & deploy, Firestore & Realtime DB, auth & functions",
48963
+ description: "Use for Firebase: deploy hosting/functions, manage Firestore/Realtime DB rules and data, auth users, emulators.",
48874
48964
  versionCmd: "firebase --version 2>/dev/null | head -1"
48875
48965
  },
48876
48966
  wrangler: {
@@ -48884,6 +48974,7 @@ var tool_catalog_default = {
48884
48974
  "~/.config/.wrangler/config/default.toml"
48885
48975
  ],
48886
48976
  capabilities: "Workers & Pages deploy, KV & D1 storage, R2 & Queues",
48977
+ description: "Use for Cloudflare developer platform: deploy Workers/Pages, manage KV/D1/R2/Queues, tail logs. Use `cloudflared` for tunnels.",
48887
48978
  versionCmd: "wrangler --version 2>/dev/null | head -1"
48888
48979
  },
48889
48980
  stripe: {
@@ -48897,6 +48988,7 @@ var tool_catalog_default = {
48897
48988
  "~/.config/stripe/config.toml"
48898
48989
  ],
48899
48990
  capabilities: "Payments & subscriptions, webhook testing, invoice management",
48991
+ description: "Use for Stripe: inspect/create customers, charges, subscriptions, invoices, webhooks. `stripe listen` to forward webhooks locally; `stripe get/post /v1/...` for raw API.",
48900
48992
  versionCmd: "stripe --version 2>/dev/null | head -1"
48901
48993
  },
48902
48994
  flyctl: {
@@ -48910,6 +49002,7 @@ var tool_catalog_default = {
48910
49002
  "~/.fly/config.yml"
48911
49003
  ],
48912
49004
  capabilities: "Deploy containers globally, Postgres & volumes, auto-scaling",
49005
+ description: "Use to deploy apps/containers to Fly.io edge, manage Postgres/volumes/machines/secrets. `flyctl deploy` from app dir.",
48913
49006
  versionCmd: "flyctl version 2>/dev/null | head -1"
48914
49007
  },
48915
49008
  supabase: {
@@ -48923,6 +49016,7 @@ var tool_catalog_default = {
48923
49016
  "~/.config/supabase/access-token"
48924
49017
  ],
48925
49018
  capabilities: "Postgres database, auth & storage, edge functions",
49019
+ description: "Use for Supabase projects: run/migrate local Postgres, deploy edge functions, manage auth/storage, link to cloud project.",
48926
49020
  versionCmd: "supabase --version 2>/dev/null | head -1"
48927
49021
  },
48928
49022
  railway: {
@@ -48936,6 +49030,7 @@ var tool_catalog_default = {
48936
49030
  "~/.railway/config.json"
48937
49031
  ],
48938
49032
  capabilities: "Deploy apps & databases, environment management, auto-scaling",
49033
+ description: "Use to deploy apps/services to Railway, provision databases, manage env vars and environments. `railway up` to deploy current dir.",
48939
49034
  versionCmd: "railway --version 2>/dev/null | head -1"
48940
49035
  },
48941
49036
  shopify: {
@@ -48949,6 +49044,7 @@ var tool_catalog_default = {
48949
49044
  "~/.config/shopify/config.json"
48950
49045
  ],
48951
49046
  capabilities: "Theme development, app scaffolding, store management",
49047
+ description: "Use for Shopify dev: scaffold/develop themes and apps, push/pull themes, run dev server against a store.",
48952
49048
  versionCmd: "shopify version 2>/dev/null | head -1"
48953
49049
  },
48954
49050
  "datadog-ci": {
@@ -48957,6 +49053,7 @@ var tool_catalog_default = {
48957
49053
  installer: "npm",
48958
49054
  label: "Datadog",
48959
49055
  capabilities: "CI test visibility, sourcemap uploads, deployment tracking",
49056
+ description: "Use in CI to upload test results/sourcemaps/coverage to Datadog and mark deployments. Needs DATADOG_API_KEY.",
48960
49057
  versionCmd: "datadog-ci version 2>/dev/null | head -1"
48961
49058
  },
48962
49059
  "sentry-cli": {
@@ -48970,18 +49067,20 @@ var tool_catalog_default = {
48970
49067
  "~/.sentryclirc"
48971
49068
  ],
48972
49069
  capabilities: "Release management, sourcemap uploads, error monitoring",
49070
+ description: "Use to create Sentry releases, upload sourcemaps/debug-symbols, associate commits, send events. `sentry-cli releases new <version>`.",
48973
49071
  versionCmd: "sentry-cli --version 2>/dev/null | head -1"
48974
49072
  },
48975
49073
  todoai: {
48976
49074
  category: "utility",
48977
49075
  pkg: "@todoforai/cli",
48978
49076
  installer: "bun",
48979
- label: "TODO CLI",
49077
+ label: "TODOforAI",
48980
49078
  capabilities: "Create & manage TODOs, run workflows, API access",
49079
+ description: "Use to programmatically create/list/update TODOs in TODOforAI, kick off workflows, call the platform API.",
48981
49080
  statusCmd: "todoai --help >/dev/null 2>&1",
48982
49081
  installCmd: "bun add -g @todoforai/cli",
48983
49082
  versionCmd: "todoai --version 2>/dev/null | head -1",
48984
- preinstall: true
49083
+ internal: true
48985
49084
  },
48986
49085
  newman: {
48987
49086
  category: "testing",
@@ -48990,17 +49089,21 @@ var tool_catalog_default = {
48990
49089
  label: "Postman",
48991
49090
  statusCmd: "newman --version",
48992
49091
  capabilities: "Run API test collections, CI/CD integration, HTML reports",
49092
+ description: "Use to run Postman collections from CLI (API smoke tests, CI checks). `newman run <collection.json> -e <env.json>`.",
48993
49093
  versionCmd: "newman --version 2>/dev/null | head -1"
48994
49094
  },
48995
49095
  curl: {
48996
49096
  category: "networking",
48997
49097
  pkg: "curl",
48998
49098
  installer: "system",
48999
- label: "curl",
49099
+ label: "Web Request",
49100
+ binName: "curl",
49000
49101
  statusCmd: "curl --version >/dev/null 2>&1",
49001
49102
  capabilities: "HTTP/HTTPS/FTP client: GET/POST/PUT/DELETE, headers, auth, file upload/download, follow redirects, cookies, TLS.",
49103
+ description: "Default tool for any HTTP request: hitting REST APIs, downloading files, testing endpoints. Common flags: `-s` silent, `-L` follow redirects, `-H 'Header: ...'`, `-X POST -d '...'`, `-o file`, `-f` fail on HTTP errors. Pipe into `jq` for JSON.",
49002
49104
  versionCmd: "curl --version 2>/dev/null | head -1",
49003
- preinstall: true
49105
+ preinstallCloud: true,
49106
+ internal: true
49004
49107
  },
49005
49108
  cloudflared: {
49006
49109
  category: "networking",
@@ -49009,25 +49112,29 @@ var tool_catalog_default = {
49009
49112
  label: "Tunnel",
49010
49113
  statusCmd: "cloudflared --version",
49011
49114
  capabilities: "Tunnel to localhost, expose local services, zero-trust access",
49115
+ 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.",
49012
49116
  versionCmd: "cloudflared --version 2>/dev/null | head -1"
49013
49117
  },
49014
49118
  vault: {
49015
49119
  category: "security",
49016
49120
  pkg: "vault",
49017
49121
  installer: "binary",
49018
- label: "Vault",
49122
+ preinstallCloud: true,
49123
+ label: "HashiCorp Vault",
49019
49124
  statusCmd: "test -f ~/.vault-token && echo 'authenticated'",
49020
49125
  loginCmd: "vault login",
49021
49126
  credentialPaths: [
49022
49127
  "~/.vault-token"
49023
49128
  ],
49024
49129
  capabilities: "Secrets management, dynamic credentials",
49130
+ description: "HashiCorp Vault CLI for Terraform/vals/ecosystem integrations. For day-to-day secret read/write in TODOforAI, prefer `tfa-vault` (zero-config). Needs VAULT_ADDR + VAULT_TOKEN.",
49025
49131
  versionCmd: "vault --version 2>/dev/null | head -1"
49026
49132
  },
49027
49133
  rclone: {
49028
49134
  category: "cloud",
49029
49135
  pkg: "rclone",
49030
- installer: "binary",
49136
+ installer: "system",
49137
+ preinstallCloud: true,
49031
49138
  label: "Cloud Sync",
49032
49139
  statusCmd: "rclone listremotes 2>&1 | grep -v 'NOTICE' | head -5",
49033
49140
  loginCmd: "rclone config",
@@ -49078,81 +49185,127 @@ var tool_catalog_default = {
49078
49185
  label: "PDF",
49079
49186
  statusCmd: "python3 -c 'import pymupdf' 2>/dev/null || /usr/bin/python3 -c 'import pymupdf' 2>/dev/null",
49080
49187
  capabilities: "PDF text editing, extraction, merge, split",
49081
- description: "Install: pip install pymupdf. Use via python3 -c with pymupdf. Replace text: doc=pymupdf.open(path); page.add_redact_annot(page.search_for('old')[0], text='new'); page.apply_redactions(); doc.save(out). Extract with positions: page.get_text('dict') returns spans with bbox/font/size.",
49082
- versionCmd: "python3 -c 'import pymupdf; print(pymupdf.__version__)' 2>/dev/null"
49188
+ description: "Use to programmatically read/edit/merge/split PDFs (text + positions). Call from `python3 -c`. Extract text: `pymupdf.open(p)[i].get_text()`; with bbox/font: `.get_text('dict')`. Replace text in place: `page.add_redact_annot(page.search_for('old')[0], text='new'); page.apply_redactions(); doc.save(out)`. For PDF markdown prefer `firecrawl parse`.",
49189
+ versionCmd: "python3 -c 'import pymupdf; print(pymupdf.__version__)' 2>/dev/null",
49190
+ preinstallCloud: true
49083
49191
  },
49084
49192
  "agent-browser": {
49085
49193
  category: "development",
49086
49194
  pkg: "agent-browser",
49087
49195
  installer: "npm",
49196
+ preinstallCloud: true,
49088
49197
  label: "Browser",
49089
49198
  statusCmd: "agent-browser --version",
49090
49199
  capabilities: "Headless browser automation, web scraping, accessibility tree snapshots, screenshots, PDF generation",
49091
- description: "Headless Chrome automation for AI agents. Commands: open <url>, click/type/fill <selector> <text>, snapshot (accessibility tree with refs), screenshot [path], pdf <path>, eval <js>, wait <selector|ms>. Use snapshot to get semantic page structure with @refs for clicking. Supports drag/drop, file uploads, form interactions.",
49200
+ description: "Use when a task needs a real browser: JS-rendered pages, logged-in flows, clicking/filling forms, screenshots, PDF export. Prefer `curl` for plain HTTP and `firecrawl` for bulk scrape/crawl. Commands: open <url>, click/type/fill <selector> <text>, snapshot (accessibility tree with @refs — use these for clicking), screenshot [path], pdf <path>, eval <js>, wait <selector|ms>.",
49092
49201
  versionCmd: "agent-browser --version 2>/dev/null | head -1"
49093
49202
  },
49203
+ firecrawl: {
49204
+ category: "development",
49205
+ pkg: "firecrawl-cli",
49206
+ installer: "npm",
49207
+ label: "Firecrawl",
49208
+ statusCmd: "firecrawl view-config 2>&1 | grep -q 'Authenticated' && echo authenticated",
49209
+ loginCmd: "firecrawl login",
49210
+ credentialPaths: [
49211
+ "~/.config/firecrawl-cli"
49212
+ ],
49213
+ capabilities: "Web scraping, crawling, search, map URLs, parse local HTML/PDF/DOCX to markdown, AI agent extraction",
49214
+ description: "Use for web → markdown at scale: single pages (`firecrawl scrape <url>`), whole sites (`firecrawl crawl <url>`), URL discovery (`firecrawl map <url>`), web search (`firecrawl search <query>`), local doc conversion (`firecrawl parse <file.pdf|docx|html|xlsx>`), AI-guided extraction (`firecrawl agent <prompt>`). Prefer over `curl` when you want markdown not HTML. Auth: `firecrawl login` or FIRECRAWL_API_KEY.",
49215
+ versionCmd: "firecrawl --version 2>/dev/null | head -1"
49216
+ },
49094
49217
  "todoforai-browser": {
49095
49218
  category: "development",
49096
49219
  pkg: "@todoforai/browser",
49097
49220
  installer: "npm",
49098
- label: "TODO for AI Browser",
49221
+ label: "Browser (Extension)",
49099
49222
  capabilities: "Browser automation via extension (non-headless), web scraping, accessibility tree snapshots, screenshots, PDF generation",
49100
49223
  description: "Browser automation via the TODO for AI browser extension (non-headless, uses your real browser). Commands: open <url>, click/type/fill <selector> <text>, snapshot (accessibility tree with refs), screenshot [path], pdf <path>, eval <js>, wait <selector|ms>. Use snapshot to get semantic page structure with @refs for clicking. Supports drag/drop, file uploads, form interactions.",
49101
- versionCmd: `node -p "require(require('child_process').execSync('which todoforai-browser',{encoding:'utf8'}).trim().replace(/\\/dist\\/index\\.js$/, '/package.json')).version" 2>/dev/null`
49224
+ versionCmd: `node -p "require(require('child_process').execSync('which todoforai-browser',{encoding:'utf8'}).trim().replace(/\\/dist\\/index\\.js$/, '/package.json')).version" 2>/dev/null`,
49225
+ internal: true
49102
49226
  },
49103
49227
  "todoforai-subagent": {
49104
49228
  category: "development",
49105
49229
  pkg: "@todoforai/subagent",
49106
49230
  installer: "npm",
49107
- label: "TODO for AI Sub-agent",
49231
+ label: "Sub-agent",
49108
49232
  capabilities: "Spawn a TODO for AI sub-agent (FluidAgent) from the CLI; pipe stdin in, get an answer out",
49109
49233
  description: 'Run a sub-agent task from a shell block. Usage: `todoforai-subagent [-m model] [-s :preset|@file|text] [--tools read,grep,bash] "<question>"`. Pipe stdin to include input context. Presets: :review, :explore, :plan, :summarize. Auth via TODOFORAI_API_KEY (auto-injected by edge).',
49110
49234
  versionCmd: "todoforai-subagent --version 2>/dev/null | head -1",
49111
49235
  installCmd: "bun add -g @todoforai/subagent",
49112
- preinstall: true
49113
- },
49114
- "todoforai-review": {
49115
- category: "development",
49116
- pkg: "@todoforai/review",
49117
- installer: "npm",
49118
- label: "TODO for AI Review",
49119
- capabilities: "Review a git diff with a sub-agent: assess goal, find issues, suggest simpler approaches",
49120
- description: 'Capture `git diff` and ask a read-only sub-agent to review it. Usage: `todoforai-review [--repo <path>] [--against <ref>] "<goal>"`. Default diffs uncommitted changes vs HEAD. Wraps `todoforai-subagent --sysmsg :review --tools read,grep,bash`.',
49121
- versionCmd: "todoforai-review --version 2>/dev/null | head -1",
49122
- installCmd: "bun add -g @todoforai/review",
49123
- preinstall: true
49124
- },
49125
- "todoforai-explore": {
49126
- category: "development",
49127
- pkg: "@todoforai/explore",
49128
- installer: "npm",
49129
- label: "TODO for AI Explore",
49130
- capabilities: "Explore a codebase with a sub-agent: map structure, find patterns, surface relevant files",
49131
- description: 'Ask a read-only sub-agent to explore a codebase. Usage: `todoforai-explore [--repo <path>] "<question>"`. Sub-agent uses read/grep/bash to investigate and reports findings with file:line citations. Wraps `todoforai-subagent --sysmsg :explore`.',
49132
- versionCmd: "todoforai-explore --version 2>/dev/null | head -1",
49133
- installCmd: "bun add -g @todoforai/explore",
49134
- preinstall: true
49236
+ internal: true
49135
49237
  },
49136
49238
  "todoforai-summary": {
49137
49239
  category: "development",
49138
49240
  pkg: "@todoforai/summary",
49139
49241
  installer: "npm",
49140
- label: "TODO for AI Summary",
49242
+ label: "Summarize (Lite)",
49141
49243
  capabilities: "Summarize files or piped input via a sub-agent",
49142
49244
  description: 'Summarize file contents or stdin. Usage: `todoforai-summary [-f <file>]... [<files...>] [<focus>]` or `cat x | todoforai-summary "focus"`. Wraps `todoforai-subagent --sysmsg :summarize`.',
49143
49245
  versionCmd: "todoforai-summary --version 2>/dev/null | head -1",
49144
49246
  installCmd: "bun add -g @todoforai/summary",
49145
- preinstall: true
49247
+ internal: true
49248
+ },
49249
+ "tfa-explore": {
49250
+ category: "development",
49251
+ pkg: "@todoforai/tfa-explore",
49252
+ installer: "npm",
49253
+ label: "Explore",
49254
+ capabilities: "Explore a codebase as a real TODO: read-only agent maps structure, surfaces relevant files, streams findings to terminal",
49255
+ description: 'Codebase exploration as a real TODO with patched permissions (read/grep/bash only) and live streaming. Usage: `tfa-explore [--repo <path>] "<question>"`. Creates a TODO visible in the UI and streams block events to stdout until DONE.',
49256
+ versionCmd: "tfa-explore --version 2>/dev/null | head -1",
49257
+ installCmd: "bun add -g @todoforai/tfa-explore",
49258
+ preinstall: true,
49259
+ preinstallCloud: true,
49260
+ internal: true
49261
+ },
49262
+ "tfa-review": {
49263
+ category: "development",
49264
+ pkg: "@todoforai/tfa-review",
49265
+ installer: "npm",
49266
+ label: "Review",
49267
+ capabilities: "Review a git diff as a real TODO: read-only agent assesses goal, finds issues, suggests simpler approaches",
49268
+ description: 'Capture `git diff` and ask a read-only sub-agent to review it as a real TODO. Usage: `tfa-review [--repo <path>] [--against <ref>] "<goal>"`. Default diffs uncommitted changes vs HEAD. Streams block events to stdout until DONE.',
49269
+ versionCmd: "tfa-review --version 2>/dev/null | head -1",
49270
+ installCmd: "bun add -g @todoforai/tfa-review",
49271
+ preinstall: true,
49272
+ preinstallCloud: true,
49273
+ internal: true
49274
+ },
49275
+ "tfa-summary": {
49276
+ category: "development",
49277
+ pkg: "@todoforai/tfa-summary",
49278
+ installer: "npm",
49279
+ label: "Summarize",
49280
+ capabilities: "Summarize files or piped input as a real TODO with no-tools sub-agent",
49281
+ description: 'Summarize file contents or stdin as a real TODO. Usage: `tfa-summary [-f <file>]... [<files...>] [<focus>]` or `cat x | tfa-summary "focus"`. No tools (content is inlined); streams block events to stdout until DONE.',
49282
+ versionCmd: "tfa-summary --version 2>/dev/null | head -1",
49283
+ installCmd: "bun add -g @todoforai/tfa-summary",
49284
+ internal: true
49285
+ },
49286
+ "tfa-vault": {
49287
+ category: "security",
49288
+ pkg: "@todoforai/vault",
49289
+ installer: "npm",
49290
+ label: "Vault",
49291
+ 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.",
49292
+ description: 'Read & write secrets in the user\'s TODOforAI vault — no VAULT_ADDR, no VAULT_TOKEN, no `vault login` needed. Usage: `tfa-vault put <path> k=v [k=v ...]`, `tfa-vault get [-f <field>] <path>`, `tfa-vault patch <path> k=v ...`, `tfa-vault list [<prefix>]`, `tfa-vault rm <path>`. Values: inline (`k=v`), file (`k=@/path`), stdin (`k=-`). `get -f <field>` writes raw value with no trailing newline — safe in `$(...)`. Exit 2 means "not found" (distinct from network errors). Prefer this over the HashiCorp `vault` CLI for daily use; `vault` remains supported for Terraform/vals/ecosystem tools.',
49293
+ versionCmd: "tfa-vault --version 2>/dev/null | head -1",
49294
+ statusCmd: "tfa-vault whoami",
49295
+ installCmd: "bun add -g @todoforai/vault",
49296
+ preinstall: true,
49297
+ preinstallCloud: true,
49298
+ internal: true
49146
49299
  },
49147
49300
  ripgrep: {
49148
49301
  category: "development",
49149
49302
  pkg: "ripgrep",
49150
49303
  installer: "binary",
49151
- label: "ripgrep (rg)",
49304
+ label: "Search",
49152
49305
  capabilities: "Fast recursive grep with regex, .gitignore-aware, parallel, unicode-correct",
49153
49306
  description: "Fast code/text search. Usage: `rg <pattern> [path]`, `-i` case-insensitive, `-l` files only, `-t <lang>` filter by language, `-g '<glob>'` filter by glob, `-C N` context. Prefer over `grep -r`.",
49154
49307
  versionCmd: "rg --version 2>/dev/null | head -1",
49155
- preinstall: true,
49308
+ preinstallCloud: true,
49156
49309
  binary: {
49157
49310
  "linux-x86_64": { url: "https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz", archive: "tar.gz", extract: "ripgrep-14.1.1-x86_64-unknown-linux-musl/rg" },
49158
49311
  "linux-aarch64": { url: "https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep-14.1.1-aarch64-unknown-linux-gnu.tar.gz", archive: "tar.gz", extract: "ripgrep-14.1.1-aarch64-unknown-linux-gnu/rg" },
@@ -49160,17 +49313,18 @@ var tool_catalog_default = {
49160
49313
  "darwin-aarch64": { url: "https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep-14.1.1-aarch64-apple-darwin.tar.gz", archive: "tar.gz", extract: "ripgrep-14.1.1-aarch64-apple-darwin/rg" },
49161
49314
  "windows-x86_64": { url: "https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep-14.1.1-x86_64-pc-windows-msvc.zip", archive: "zip", extract: "ripgrep-14.1.1-x86_64-pc-windows-msvc/rg.exe" }
49162
49315
  },
49163
- binName: "rg"
49316
+ binName: "rg",
49317
+ internal: true
49164
49318
  },
49165
49319
  fd: {
49166
49320
  category: "development",
49167
49321
  pkg: "fd-find",
49168
49322
  installer: "binary",
49169
- label: "fd",
49323
+ label: "Find Files",
49170
49324
  capabilities: "Fast user-friendly find replacement: regex by default, .gitignore-aware, parallel",
49171
49325
  description: "Find files/dirs by name. Usage: `fd <pattern> [path]`, `-e <ext>` filter by extension, `-t f|d` type, `-H` include hidden, `-x <cmd>` exec per match.",
49172
49326
  versionCmd: "fd --version 2>/dev/null | head -1",
49173
- preinstall: true,
49327
+ preinstallCloud: true,
49174
49328
  binary: {
49175
49329
  "linux-x86_64": { url: "https://github.com/sharkdp/fd/releases/download/v10.2.0/fd-v10.2.0-x86_64-unknown-linux-musl.tar.gz", archive: "tar.gz", extract: "fd-v10.2.0-x86_64-unknown-linux-musl/fd" },
49176
49330
  "linux-aarch64": { url: "https://github.com/sharkdp/fd/releases/download/v10.2.0/fd-v10.2.0-aarch64-unknown-linux-gnu.tar.gz", archive: "tar.gz", extract: "fd-v10.2.0-aarch64-unknown-linux-gnu/fd" },
@@ -49178,17 +49332,18 @@ var tool_catalog_default = {
49178
49332
  "darwin-aarch64": { url: "https://github.com/sharkdp/fd/releases/download/v10.2.0/fd-v10.2.0-aarch64-apple-darwin.tar.gz", archive: "tar.gz", extract: "fd-v10.2.0-aarch64-apple-darwin/fd" },
49179
49333
  "windows-x86_64": { url: "https://github.com/sharkdp/fd/releases/download/v10.2.0/fd-v10.2.0-x86_64-pc-windows-msvc.zip", archive: "zip", extract: "fd-v10.2.0-x86_64-pc-windows-msvc/fd.exe" }
49180
49334
  },
49181
- binName: "fd"
49335
+ binName: "fd",
49336
+ internal: true
49182
49337
  },
49183
49338
  jq: {
49184
49339
  category: "development",
49185
49340
  pkg: "jq",
49186
49341
  installer: "binary",
49187
- label: "jq",
49342
+ label: "JSON",
49188
49343
  capabilities: "Command-line JSON processor: query, filter, transform, format JSON streams",
49189
49344
  description: "JSON pipeline tool. Usage: `cat x.json | jq '.field'`, `jq -r` raw output, `jq '.[] | select(.k==\"v\")'`, `jq -s` slurp array. Pairs well with curl/rg.",
49190
49345
  versionCmd: "jq --version 2>/dev/null | head -1",
49191
- preinstall: true,
49346
+ preinstallCloud: true,
49192
49347
  binary: {
49193
49348
  "linux-x86_64": { url: "https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64", archive: "raw" },
49194
49349
  "linux-aarch64": { url: "https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-arm64", archive: "raw" },
@@ -49196,16 +49351,44 @@ var tool_catalog_default = {
49196
49351
  "darwin-aarch64": { url: "https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-macos-arm64", archive: "raw" },
49197
49352
  "windows-x86_64": { url: "https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-windows-amd64.exe", archive: "raw" }
49198
49353
  },
49199
- binName: "jq"
49354
+ binName: "jq",
49355
+ internal: true
49200
49356
  },
49201
49357
  patch: {
49202
49358
  category: "development",
49203
49359
  pkg: "patch",
49204
49360
  installer: "system",
49205
- label: "GNU patch",
49361
+ preinstallCloud: true,
49362
+ label: "Apply Diff",
49206
49363
  capabilities: "Apply unified/context diff patches to files; reverse, dry-run, fuzzy matching",
49207
49364
  description: "Apply diffs. Usage: `patch -p1 < changes.diff` (strip 1 leading path component), `--dry-run` to preview, `-R` to reverse, `-N` skip already-applied. Reads unified (`diff -u`) or context diffs.",
49208
- versionCmd: "patch --version 2>/dev/null | head -1"
49365
+ versionCmd: "patch --version 2>/dev/null | head -1",
49366
+ binName: "patch",
49367
+ internal: true
49368
+ },
49369
+ sed: {
49370
+ category: "development",
49371
+ pkg: "sed",
49372
+ installer: "system",
49373
+ preinstallCloud: true,
49374
+ label: "Replace Text",
49375
+ capabilities: "Stream editor: in-place text substitution, line filtering, scripted edits",
49376
+ description: "Non-interactive text transformation. Usage: `sed 's/old/new/g' file`, `-i` edit in place (`-i ''` on macOS), `-n '5,10p'` print line range, `-E` extended regex. Pair with `rg -l` to bulk-rewrite matched files.",
49377
+ versionCmd: "sed --version 2>/dev/null | head -1",
49378
+ binName: "sed",
49379
+ internal: true
49380
+ },
49381
+ grep: {
49382
+ category: "development",
49383
+ pkg: "grep",
49384
+ installer: "system",
49385
+ preinstallCloud: true,
49386
+ label: "Search Text",
49387
+ capabilities: "Search plain text for lines matching a regex: recursive, fixed-string, context, invert, count",
49388
+ description: "Line-based text search. Usage: `grep <pattern> file`, `-r` recursive, `-i` case-insensitive, `-n` line numbers, `-E` extended regex, `-F` fixed strings, `-v` invert, `-C N` context, `-l` files only. For large code searches prefer `rg`.",
49389
+ versionCmd: "grep --version 2>/dev/null | head -1",
49390
+ binName: "grep",
49391
+ internal: true
49209
49392
  }
49210
49393
  };
49211
49394
 
@@ -49616,11 +49799,6 @@ function execShellAsync(cmd, env, timeout) {
49616
49799
  async function scanCatalogTools() {
49617
49800
  const result = {};
49618
49801
  const env = buildEnvWithTools();
49619
- const rawPath = process.env.PATH ?? process.env.Path ?? process.env.path ?? "";
49620
- const pathDirs = rawPath.split(path3.delimiter).filter(Boolean);
49621
- log2("scan", `platform=${os3.platform()} PATH dirs (${pathDirs.length}):`);
49622
- for (const d of pathDirs)
49623
- log2("scan", ` ${d}`);
49624
49802
  const entries = Object.entries(TOOL_CATALOG);
49625
49803
  const installed = [];
49626
49804
  const missing = [];
@@ -49632,8 +49810,6 @@ async function scanCatalogTools() {
49632
49810
  installed.push([name, entry]);
49633
49811
  }
49634
49812
  }
49635
- log2("scan", `installed: [${installed.map(([n]) => n).join(", ")}]`);
49636
- log2("scan", `missing: [${missing.join(", ")}]`);
49637
49813
  await Promise.all(installed.map(async ([name, entry]) => {
49638
49814
  const state = { installed: true };
49639
49815
  if (entry.versionCmd) {
@@ -51054,7 +51230,7 @@ var mimetypes_default = {
51054
51230
  image: { icon: "mdi:file-image", color: "yellow", prefixRule: "image/", needsObjectUrl: true },
51055
51231
  audio: { icon: "mdi:file-music", color: "grey", prefixRule: "audio/", needsObjectUrl: true },
51056
51232
  video: { icon: "mdi:file-video", color: "yellow", prefixRule: "video/", needsObjectUrl: true },
51057
- pdf: { icon: "mdi:file-pdf-box", color: "red", needsObjectUrl: true },
51233
+ pdf: { icon: "mdi:pdf-box", color: "red", needsObjectUrl: true },
51058
51234
  document: { icon: "mdi:file-word", color: "lightblue" },
51059
51235
  spreadsheet: { icon: "mdi:file-excel", color: "green" },
51060
51236
  text: { icon: "mdi:file", color: "white", prefixRule: "text/" },
@@ -51145,7 +51321,7 @@ var MAX_IMAGE_FILE_SIZE = 5000000;
51145
51321
  var OFFICE_EXTENSIONS = new Set([".docx", ".xlsx", ".pdf"]);
51146
51322
  var EXT_TO_MIME = new Map(Object.entries(mimetypes_default.extensions).map(([ext, e]) => [`.${ext}`, e.mime]));
51147
51323
  var EXT_TO_TYPE = new Map(Object.entries(mimetypes_default.extensions).map(([ext, e]) => [`.${ext}`, e.type]));
51148
- async function readFileContent(filePath, rootPath, fallbackRootPaths) {
51324
+ async function readFileContent(filePath, rootPath, fallbackRootPaths, skipSizeLimit = false) {
51149
51325
  try {
51150
51326
  const fullPath = path4.resolve(resolveFilePath(filePath, rootPath, fallbackRootPaths));
51151
51327
  if (!fs7.existsSync(fullPath)) {
@@ -51167,7 +51343,7 @@ async function readFileContent(filePath, rootPath, fallbackRootPaths) {
51167
51343
  const isImage = fileType === "image";
51168
51344
  const isOffice = OFFICE_EXTENSIONS.has(ext);
51169
51345
  const sizeLimit = isImage ? MAX_IMAGE_FILE_SIZE : isOffice ? MAX_OFFICE_FILE_SIZE : MAX_FILE_SIZE;
51170
- if (stat.size > sizeLimit) {
51346
+ if (!skipSizeLimit && stat.size > sizeLimit) {
51171
51347
  return {
51172
51348
  success: false,
51173
51349
  error: `File too large: ${fullPath} (${stat.size.toLocaleString()} bytes, max ${sizeLimit.toLocaleString()})`
@@ -51660,6 +51836,13 @@ async function sendInput(blockId, text) {
51660
51836
  }
51661
51837
  return true;
51662
51838
  }
51839
+ function detachBlock(blockId) {
51840
+ const resolver = completionResolvers.get(blockId);
51841
+ if (resolver) {
51842
+ resolver();
51843
+ completionResolvers.delete(blockId);
51844
+ }
51845
+ }
51663
51846
  function interruptBlock(blockId) {
51664
51847
  const handle = processes.get(blockId);
51665
51848
  if (!handle)
@@ -52147,12 +52330,13 @@ function detectContentType(output, cmd) {
52147
52330
  return { result: output };
52148
52331
  }
52149
52332
  register("execute_shell_command", async (args, client) => {
52150
- const { cmd, timeout = 120, cwd = args.root_path ?? "", todoId = "", messageId = "", blockId = "", agentSettingsId = "", pid: resumePid = 0 } = args;
52333
+ const { cmd, cwd = args.root_path ?? "", todoId = "", messageId = "", blockId = "", agentSettingsId = "", pid: resumePid = 0 } = args;
52334
+ const timeout = Math.max(args.timeout ?? 120, client?.maxTimeout ?? 0);
52151
52335
  const canStream = !!(todoId && blockId && client);
52152
52336
  if (!canStream) {
52153
52337
  const { exec } = await import("child_process");
52154
52338
  const result = await new Promise((resolve) => {
52155
- exec(cmd, { cwd: cwd || os8.tmpdir(), encoding: "utf-8", timeout: 120000, maxBuffer: 10485760, env: { ...buildEnvWithTools(), ...getConnectionEnv(), TODOFORAI_TODO_ID: todoId, TODOFORAI_MESSAGE_ID: messageId, TODOFORAI_BLOCK_ID: blockId, TODOFORAI_AGENT_SETTINGS_ID: agentSettingsId } }, (_err, stdout, stderr) => {
52339
+ exec(cmd, { cwd: cwd || os8.tmpdir(), encoding: "utf-8", timeout: timeout * 1000, maxBuffer: 10485760, env: { ...buildEnvWithTools(), ...getConnectionEnv(), TODOFORAI_TODO_ID: todoId, TODOFORAI_MESSAGE_ID: messageId, TODOFORAI_BLOCK_ID: blockId, TODOFORAI_AGENT_SETTINGS_ID: agentSettingsId } }, (_err, stdout, stderr) => {
52156
52340
  resolve((stdout || "") + (stderr || ""));
52157
52341
  });
52158
52342
  });
@@ -52209,13 +52393,37 @@ register("execute_shell_command", async (args, client) => {
52209
52393
  }
52210
52394
  });
52211
52395
  register("read_file", async (args) => {
52212
- const { path: p10, rootPath = "", fallbackRootPaths = [] } = args;
52213
- const result = await readFileContent(p10, rootPath, fallbackRootPaths);
52396
+ const { path: p10, rootPath = "", fallbackRootPaths = [], skipSizeLimit = false } = args;
52397
+ const result = await readFileContent(p10, rootPath, fallbackRootPaths, skipSizeLimit);
52214
52398
  if (!result.success)
52215
52399
  throw new Error(result.error || "Unknown read error");
52216
52400
  const { success, ...rest } = result;
52217
52401
  return rest;
52218
52402
  });
52403
+ var LIST_DIR_MAX_ENTRIES = 1e4;
52404
+ register("list_dir", async (args) => {
52405
+ const { path: p10, rootPath = "", fallbackRootPaths = [] } = args;
52406
+ const fullPath = resolveFilePath(p10, rootPath, fallbackRootPaths);
52407
+ const st2 = fs10.statSync(fullPath);
52408
+ if (!st2.isDirectory())
52409
+ throw new Error(`Not a directory: ${fullPath}`);
52410
+ const dirents = fs10.readdirSync(fullPath, { withFileTypes: true });
52411
+ if (dirents.length > LIST_DIR_MAX_ENTRIES) {
52412
+ throw new Error(`Directory too large: ${dirents.length} entries (max ${LIST_DIR_MAX_ENTRIES})`);
52413
+ }
52414
+ const entries = dirents.map((d) => {
52415
+ let size = 0, mtime = 0, mode = 0, is_dir = d.isDirectory();
52416
+ try {
52417
+ const s = fs10.lstatSync(path7.join(fullPath, d.name));
52418
+ size = Number(s.size);
52419
+ mtime = s.mtimeMs / 1000;
52420
+ mode = s.mode & 511;
52421
+ is_dir = s.isDirectory();
52422
+ } catch {}
52423
+ return { name: d.name, is_dir, size, mtime, mode };
52424
+ });
52425
+ return { entries };
52426
+ });
52219
52427
  register("create_file", async (args) => {
52220
52428
  const { path: p10, content, rootPath = "", fallbackRootPaths = [] } = args;
52221
52429
  const fullPath = resolveFilePath(p10, rootPath, fallbackRootPaths);
@@ -52236,106 +52444,6 @@ register("read_file_base64", async (args) => {
52236
52444
  const data = fs10.readFileSync(fullPath);
52237
52445
  return { path: fullPath, base64: data.toString("base64"), bytes: data.length };
52238
52446
  });
52239
- register("search_files", async (args) => {
52240
- const { pattern, path: p10 = ".", cwd = args.root_path ?? "", head = 100, max_count = 5, glob: globPattern = "", ignore_case = true } = args;
52241
- const { execSync: execWhich } = await import("child_process");
52242
- const whichCmd = process.platform === "win32" ? "where" : "which";
52243
- const which = (bin) => {
52244
- try {
52245
- return execWhich(`${whichCmd} ${bin}`, { encoding: "utf-8" }).trim().split(`
52246
- `)[0].trim();
52247
- } catch {
52248
- return null;
52249
- }
52250
- };
52251
- let rgPath = which("rg");
52252
- if (!rgPath) {
52253
- await ensureTool("rg");
52254
- rgPath = which("rg");
52255
- }
52256
- let searchPath = p10.replace(/^~/, process.env.HOME || "~");
52257
- if (!path7.isAbsolute(searchPath) && cwd)
52258
- searchPath = path7.join(cwd, searchPath);
52259
- searchPath = path7.resolve(searchPath);
52260
- if (!fs10.existsSync(searchPath))
52261
- throw new Error(`Search path does not exist: ${searchPath}`);
52262
- let cmd;
52263
- if (rgPath) {
52264
- cmd = [rgPath, "--no-heading", "--line-number", "--color=never"];
52265
- if (ignore_case)
52266
- cmd.push("--ignore-case");
52267
- if (max_count > 0)
52268
- cmd.push(`--max-count=${max_count}`);
52269
- if (globPattern)
52270
- cmd.push("--glob", globPattern);
52271
- cmd.push(pattern, searchPath);
52272
- } else {
52273
- console.warn("[search_files] ripgrep (rg) not found, falling back to grep");
52274
- const grepPath = which("grep") || "grep";
52275
- cmd = [grepPath, "-rn", "--color=never"];
52276
- if (ignore_case)
52277
- cmd.push("-i");
52278
- if (max_count > 0)
52279
- cmd.push(`--max-count=${max_count}`);
52280
- if (globPattern) {
52281
- cmd.push(`--include=${globPattern}`);
52282
- }
52283
- cmd.push(pattern, searchPath);
52284
- }
52285
- const { spawn: spawnChild } = await import("child_process");
52286
- const { stdout, stderr, code } = await new Promise((resolve) => {
52287
- const child = spawnChild(cmd[0], cmd.slice(1));
52288
- let out = "", err2 = "";
52289
- child.stdout?.on("data", (d) => {
52290
- out += d.toString();
52291
- });
52292
- child.stderr?.on("data", (d) => {
52293
- err2 += d.toString();
52294
- });
52295
- child.on("close", (exitCode) => resolve({ stdout: out, stderr: err2, code: exitCode ?? 1 }));
52296
- });
52297
- if (code === 0) {
52298
- let output = stdout;
52299
- const lines = output.split(`
52300
- `).filter((l) => l.trim());
52301
- if (lines.length > head) {
52302
- output = lines.slice(0, head).join(`
52303
- `) + `
52304
- ... (${lines.length - head} more matches truncated)`;
52305
- }
52306
- if ((cwd || searchPath) && output) {
52307
- const searchBase = searchPath && fs10.existsSync(searchPath) && fs10.statSync(searchPath).isDirectory() ? searchPath : path7.dirname(searchPath);
52308
- const bases = Array.from(new Set([cwd, searchBase].filter(Boolean)));
52309
- const lines2 = output.split(`
52310
- `).map((line) => {
52311
- if (line.includes(":")) {
52312
- const colonIdx = line.indexOf(":");
52313
- let filePart = line.slice(0, colonIdx);
52314
- const rest = line.slice(colonIdx);
52315
- try {
52316
- const candidates = [filePart, ...bases.map((b) => path7.relative(b, filePart))].filter((p11) => (p11.match(/\.\.\//g) || []).length <= 2);
52317
- filePart = candidates.reduce((a, b) => a.length <= b.length ? a : b, filePart);
52318
- } catch {}
52319
- let fullLine = filePart + rest;
52320
- if (fullLine.length > 300) {
52321
- fullLine = fullLine.slice(0, 300) + "...";
52322
- }
52323
- return fullLine;
52324
- }
52325
- return line;
52326
- });
52327
- output = lines2.join(`
52328
- `);
52329
- }
52330
- if (output.length > 1e5)
52331
- output = output.slice(0, 1e5) + `
52332
- ... (output truncated)`;
52333
- return { result: output };
52334
- }
52335
- if (code === 1)
52336
- return { result: "No matches found." };
52337
- throw new Error(`search error (exit ${code}): ${stderr}`);
52338
- });
52339
52447
  register("download_attachment", async (args, client) => {
52340
52448
  if (!client)
52341
52449
  throw new Error("Client instance required");
@@ -52411,18 +52519,21 @@ register("register_attachment", async (args, client) => {
52411
52519
 
52412
52520
  // src/handlers.ts
52413
52521
  var log3 = (level, ...args) => console.log(`[${level}]`, ...args);
52414
- async function handleBlockExecute(payload, send, edgeId) {
52522
+ async function handleBlockExecute(payload, send, edgeId, maxTimeout = 0) {
52415
52523
  const { blockId, messageId = "", content = "", todoId = "", rootPath = "", manual = false } = payload;
52416
52524
  await send(msg.shellBlockStart(todoId, blockId, "execute", messageId));
52417
52525
  try {
52418
- const timeout = payload.timeout ?? 120;
52526
+ const timeout = Math.max(payload.timeout ?? 120, maxTimeout);
52419
52527
  await executeBlock(blockId, content, send, todoId, messageId, timeout, rootPath, manual, undefined, edgeId);
52420
52528
  } catch (e) {
52421
52529
  await send(msg.blockError(blockId, todoId, e.message));
52422
52530
  }
52423
52531
  }
52424
52532
  async function handleBlockSignal(payload) {
52425
- interruptBlock(payload.blockId);
52533
+ if (payload.detach)
52534
+ detachBlock(payload.blockId);
52535
+ else
52536
+ interruptBlock(payload.blockId);
52426
52537
  }
52427
52538
  async function handleBlockKeyboard(payload) {
52428
52539
  await sendInput(payload.blockId, payload.content || "");
@@ -52543,8 +52654,8 @@ async function handleCd(payload, send, edgeConfig, onConfigChange) {
52543
52654
  }
52544
52655
  }
52545
52656
  async function handleFileChunkRequest(payload, send, responseType = EA.FILE_CHUNK_RESULT) {
52546
- const { path: p10 = "", rootPath = "", fallbackRootPaths = [] } = payload;
52547
- const result = await readFileContent(p10, rootPath, fallbackRootPaths);
52657
+ const { path: p10 = "", rootPath = "", fallbackRootPaths = [], skipSizeLimit = false } = payload;
52658
+ const result = await readFileContent(p10, rootPath, fallbackRootPaths, skipSizeLimit);
52548
52659
  if (result.success) {
52549
52660
  await send(msg.fileChunkResult(responseType, { ...payload, full_path: result.fullPath, content: result.content, content_type: result.contentType }));
52550
52661
  } else {
@@ -52622,6 +52733,7 @@ class TODOforAIEdge {
52622
52733
  edgeId = "";
52623
52734
  userId = "";
52624
52735
  debug;
52736
+ maxTimeout;
52625
52737
  wsUrl;
52626
52738
  fingerprint = "";
52627
52739
  heartbeatTimer;
@@ -52641,6 +52753,7 @@ class TODOforAIEdge {
52641
52753
  constructor(config) {
52642
52754
  this.api = new ApiClient(normalizeApiUrl(config.apiUrl), config.apiKey);
52643
52755
  this.debug = config.debug;
52756
+ this.maxTimeout = config.maxTimeout ?? 0;
52644
52757
  this.wsUrl = getWsUrl(this.api.apiUrl);
52645
52758
  this.addWorkspacePath = config.addWorkspacePath;
52646
52759
  this.browserExtensionBridge = new BrowserExtensionBridge(this.debug);
@@ -52699,10 +52812,16 @@ class TODOforAIEdge {
52699
52812
  try {
52700
52813
  console.log("\x1B[36mStarting device login...\x1B[0m");
52701
52814
  const { code, url, expiresIn } = await this.api.initDeviceLogin("edge");
52815
+ const userCode = new URL(url).searchParams.get("user_code") || "";
52816
+ const formattedCode = userCode.length === 8 ? `${userCode.slice(0, 4)}-${userCode.slice(4)}` : userCode;
52702
52817
  console.log(`
52703
52818
  \x1B[1m\uD83D\uDD11 Open this URL to authorize:\x1B[0m`);
52704
52819
  console.log(`\x1B[36m${url}\x1B[0m
52705
52820
  `);
52821
+ if (formattedCode) {
52822
+ console.log(`\x1B[1m Your code: \x1B[33m${formattedCode}\x1B[0m
52823
+ `);
52824
+ }
52706
52825
  try {
52707
52826
  const { exec } = __require("child_process");
52708
52827
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
@@ -52852,7 +52971,7 @@ class TODOforAIEdge {
52852
52971
  run(() => handleCd(payload, send, this.edgeConfig, (u) => this.updateConfig(u)));
52853
52972
  break;
52854
52973
  case FE.BLOCK_EXECUTE:
52855
- run(() => handleBlockExecute(payload, send, this.edgeId));
52974
+ run(() => handleBlockExecute(payload, send, this.edgeId, this.maxTimeout));
52856
52975
  break;
52857
52976
  case FE.BLOCK_SAVE:
52858
52977
  run(() => handleBlockSave(payload, send));
@@ -53067,22 +53186,75 @@ class ServerError extends Error {
53067
53186
  }
53068
53187
  }
53069
53188
 
53189
+ // src/update-notifier.ts
53190
+ import fs12 from "node:fs";
53191
+ import path9 from "node:path";
53192
+ import os9 from "node:os";
53193
+ var TTL_MS = 24 * 60 * 60 * 1000;
53194
+ var CACHE_DIR = path9.join(os9.homedir(), ".config", "todoforai");
53195
+ function cmpVer(a, b) {
53196
+ const pa2 = a.split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
53197
+ const pb2 = b.split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
53198
+ for (let i10 = 0;i10 < Math.max(pa2.length, pb2.length); i10++) {
53199
+ const d = (pa2[i10] ?? 0) - (pb2[i10] ?? 0);
53200
+ if (d)
53201
+ return d;
53202
+ }
53203
+ return 0;
53204
+ }
53205
+ function checkForUpdates(pkg) {
53206
+ if (!process.stderr.isTTY || process.env.CI || process.env.NO_UPDATE_NOTIFIER)
53207
+ return;
53208
+ const cacheFile = path9.join(CACHE_DIR, `notifier-${encodeURIComponent(pkg.name)}.json`);
53209
+ let cache = {};
53210
+ try {
53211
+ cache = JSON.parse(fs12.readFileSync(cacheFile, "utf8"));
53212
+ } catch {}
53213
+ if (cache.latest && cmpVer(cache.latest, pkg.version) > 0) {
53214
+ process.stderr.write(`
53215
+ \x1B[33m Update available: \x1B[2m${pkg.version}\x1B[22m → \x1B[1m${cache.latest}\x1B[0m
53216
+ ` + `\x1B[33m Run:\x1B[0m npm i -g ${pkg.name}
53217
+
53218
+ `);
53219
+ }
53220
+ if (Date.now() - (cache.ts ?? 0) > TTL_MS) {
53221
+ try {
53222
+ fs12.mkdirSync(CACHE_DIR, { recursive: true });
53223
+ fs12.writeFileSync(cacheFile, JSON.stringify({ ...cache, ts: Date.now() }));
53224
+ } catch {}
53225
+ fetch(`https://registry.npmjs.org/${pkg.name}/latest`, { signal: AbortSignal.timeout(3000) }).then((r) => r.ok ? r.json() : null).then((j) => {
53226
+ if (!j?.version)
53227
+ return;
53228
+ fs12.writeFileSync(cacheFile, JSON.stringify({ ts: Date.now(), latest: j.version }));
53229
+ }).catch(() => {});
53230
+ }
53231
+ }
53232
+
53070
53233
  // src/index.ts
53071
- import fs12 from "fs";
53072
- import path9 from "path";
53073
- import os9 from "os";
53234
+ import { fileURLToPath as fileURLToPath2 } from "url";
53235
+ import fs13 from "fs";
53236
+ import path10 from "path";
53237
+ import os10 from "os";
53074
53238
  import crypto3 from "crypto";
53239
+ function readOwnPackage() {
53240
+ try {
53241
+ const pkgPath = path10.resolve(fileURLToPath2(import.meta.url), "../../package.json");
53242
+ return JSON.parse(fs13.readFileSync(pkgPath, "utf-8"));
53243
+ } catch {
53244
+ return null;
53245
+ }
53246
+ }
53075
53247
  function lockPath(apiUrl, userId) {
53076
- const dir = path9.join(os9.homedir(), ".todoforai");
53077
- fs12.mkdirSync(dir, { recursive: true });
53248
+ const dir = path10.join(os10.homedir(), ".todoforai");
53249
+ fs13.mkdirSync(dir, { recursive: true });
53078
53250
  const hash = crypto3.createHash("sha256").update(`${apiUrl}
53079
53251
  ${userId}`).digest("hex").slice(0, 12);
53080
- return path9.join(dir, `edge-${hash}.lock`);
53252
+ return path10.join(dir, `edge-${hash}.lock`);
53081
53253
  }
53082
53254
  function isEdgeProcess(pid) {
53083
53255
  try {
53084
53256
  process.kill(pid, 0);
53085
- const cmdline = fs12.readFileSync(`/proc/${pid}/cmdline`, "utf-8");
53257
+ const cmdline = fs13.readFileSync(`/proc/${pid}/cmdline`, "utf-8");
53086
53258
  return cmdline.includes("index.ts") || cmdline.includes("todoforai-edge");
53087
53259
  } catch {
53088
53260
  return false;
@@ -53090,7 +53262,7 @@ function isEdgeProcess(pid) {
53090
53262
  }
53091
53263
  function killExistingEdge(lp2) {
53092
53264
  try {
53093
- const pid = parseInt(fs12.readFileSync(lp2, "utf-8").trim(), 10);
53265
+ const pid = parseInt(fs13.readFileSync(lp2, "utf-8").trim(), 10);
53094
53266
  if (!isNaN(pid) && isEdgeProcess(pid)) {
53095
53267
  console.log(`\x1B[33mKilling existing edge process (pid ${pid})...\x1B[0m`);
53096
53268
  process.kill(pid, "SIGTERM");
@@ -53113,7 +53285,7 @@ function killExistingEdge(lp2) {
53113
53285
  }
53114
53286
  function acquireLock(lp2, kill = false) {
53115
53287
  try {
53116
- const pid = parseInt(fs12.readFileSync(lp2, "utf-8").trim(), 10);
53288
+ const pid = parseInt(fs13.readFileSync(lp2, "utf-8").trim(), 10);
53117
53289
  if (!isNaN(pid) && isEdgeProcess(pid)) {
53118
53290
  if (kill) {
53119
53291
  killExistingEdge(lp2);
@@ -53121,16 +53293,19 @@ function acquireLock(lp2, kill = false) {
53121
53293
  return false;
53122
53294
  }
53123
53295
  } catch {}
53124
- fs12.writeFileSync(lp2, String(process.pid));
53296
+ fs13.writeFileSync(lp2, String(process.pid));
53125
53297
  return true;
53126
53298
  }
53127
53299
  function releaseLock(lp2) {
53128
53300
  try {
53129
- if (fs12.readFileSync(lp2, "utf-8").trim() === String(process.pid))
53130
- fs12.unlinkSync(lp2);
53301
+ if (fs13.readFileSync(lp2, "utf-8").trim() === String(process.pid))
53302
+ fs13.unlinkSync(lp2);
53131
53303
  } catch {}
53132
53304
  }
53133
53305
  async function main() {
53306
+ const ownPkg = readOwnPackage();
53307
+ if (ownPkg)
53308
+ checkForUpdates(ownPkg);
53134
53309
  const config = await loadConfig();
53135
53310
  if (config.debug) {
53136
53311
  console.log("[config]", { apiUrl: config.apiUrl, debug: config.debug, addWorkspacePath: config.addWorkspacePath });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@todoforai/edge",
3
- "version": "0.13.9",
3
+ "version": "0.13.11",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "todoforai-edge": "dist/index.js"
@@ -22,8 +22,8 @@
22
22
  "ws": "^8.18.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@types/ws": "^8.5.12",
26
25
  "@types/node": "^22.0.0",
26
+ "@types/ws": "^8.5.12",
27
27
  "typescript": "^5.6.0"
28
28
  }
29
29
  }