@todoforai/edge 0.13.18 → 0.13.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +275 -210
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -47977,10 +47977,10 @@ function setConnectionContext(get) {
|
|
|
47977
47977
|
function getConnectionEnv() {
|
|
47978
47978
|
if (!getter)
|
|
47979
47979
|
return {};
|
|
47980
|
-
const { apiUrl,
|
|
47981
|
-
if (!apiUrl || !
|
|
47980
|
+
const { apiUrl, sessionToken } = getter();
|
|
47981
|
+
if (!apiUrl || !sessionToken)
|
|
47982
47982
|
return {};
|
|
47983
|
-
return { TODOFORAI_API_URL: apiUrl, TODOFORAI_API_TOKEN:
|
|
47983
|
+
return { TODOFORAI_API_URL: apiUrl, TODOFORAI_API_TOKEN: sessionToken };
|
|
47984
47984
|
}
|
|
47985
47985
|
|
|
47986
47986
|
// src/constants.ts
|
|
@@ -48033,7 +48033,8 @@ var EF = {
|
|
|
48033
48033
|
FRONTEND_FILE_CHUNK_RESULT: "frontend:file_chunk_result"
|
|
48034
48034
|
};
|
|
48035
48035
|
var S2E = {
|
|
48036
|
-
EDGE_CONFIG_UPDATE: "edge:config_update"
|
|
48036
|
+
EDGE_CONFIG_UPDATE: "edge:config_update",
|
|
48037
|
+
SESSION_TOKEN: "edge:session_token"
|
|
48037
48038
|
};
|
|
48038
48039
|
var msg = {
|
|
48039
48040
|
edgeStatus(edgeId, status) {
|
|
@@ -48144,7 +48145,7 @@ class ApiClient {
|
|
|
48144
48145
|
}
|
|
48145
48146
|
async request(method, endpoint, body) {
|
|
48146
48147
|
const url = `${this.apiUrl}${endpoint}`;
|
|
48147
|
-
const opts = { method, headers: this.headers };
|
|
48148
|
+
const opts = { method, headers: this.headers, signal: AbortSignal.timeout(30000) };
|
|
48148
48149
|
if (body)
|
|
48149
48150
|
opts.body = JSON.stringify(body);
|
|
48150
48151
|
const res = await fetch(url, opts);
|
|
@@ -48771,7 +48772,6 @@ var tool_catalog_default = {
|
|
|
48771
48772
|
"~/.zele/sqlite.db"
|
|
48772
48773
|
],
|
|
48773
48774
|
capabilities: "Read inbox, search/send/reply email, email addresses — multi-account Gmail, OAuth browser login for setup.",
|
|
48774
|
-
description: "Use for anything Gmail: reading the user's inbox, searching mail, sending or replying to email. Multi-account, OAuth login via `zele login`.",
|
|
48775
48775
|
versionCmd: "zele --version 2>/dev/null | head -1"
|
|
48776
48776
|
},
|
|
48777
48777
|
xurl: {
|
|
@@ -48785,7 +48785,7 @@ var tool_catalog_default = {
|
|
|
48785
48785
|
"~/.xurl"
|
|
48786
48786
|
],
|
|
48787
48787
|
capabilities: "Post tweets, reply & quote, read timelines, search tweets, like & repost, follow/unfollow, DMs, media uploads",
|
|
48788
|
-
description: "
|
|
48788
|
+
description: "Authenticated raw X API access via `xurl <method> <path>`; login `xurl auth oauth2`.",
|
|
48789
48789
|
versionCmd: "xurl --version 2>/dev/null | head -1"
|
|
48790
48790
|
},
|
|
48791
48791
|
"tiktok-uploader": {
|
|
@@ -48798,7 +48798,6 @@ var tool_catalog_default = {
|
|
|
48798
48798
|
"~/.tiktok/cookies.txt"
|
|
48799
48799
|
],
|
|
48800
48800
|
capabilities: "Upload videos, batch uploads, schedule posts, custom covers, hashtags & mentions",
|
|
48801
|
-
description: "Upload videos to TikTok, schedule posts, set covers and hashtags.",
|
|
48802
48801
|
versionCmd: "pip show tiktok-uploader 2>/dev/null | grep -oP 'Version: \\K.*'"
|
|
48803
48802
|
},
|
|
48804
48803
|
instagrapi: {
|
|
@@ -48807,7 +48806,6 @@ var tool_catalog_default = {
|
|
|
48807
48806
|
installer: "pip",
|
|
48808
48807
|
label: "Instagram",
|
|
48809
48808
|
capabilities: "Upload photos & reels, post stories, send DMs, like & comment, manage followers",
|
|
48810
|
-
description: "Instagram automation: upload photos/reels, stories, DMs, likes, follower management.",
|
|
48811
48809
|
versionCmd: "pip show instagrapi 2>/dev/null | grep -oP 'Version: \\K.*'"
|
|
48812
48810
|
},
|
|
48813
48811
|
mudslide: {
|
|
@@ -48821,9 +48819,50 @@ var tool_catalog_default = {
|
|
|
48821
48819
|
"~/.local/share/mudslide"
|
|
48822
48820
|
],
|
|
48823
48821
|
capabilities: "Send messages, send images & files, send locations & polls, group management, QR code login",
|
|
48824
|
-
description: "Use to send WhatsApp messages/files/media/locations/polls from CLI. QR-code login via `mudslide login`.",
|
|
48825
48822
|
versionCmd: "mudslide --version 2>/dev/null | head -1"
|
|
48826
48823
|
},
|
|
48824
|
+
slack: {
|
|
48825
|
+
category: "development",
|
|
48826
|
+
pkg: "slack-cli",
|
|
48827
|
+
installer: "binary",
|
|
48828
|
+
label: "Slack",
|
|
48829
|
+
statusCmd: "slack-cli auth list 2>&1 | grep -q 'Team' && echo authenticated",
|
|
48830
|
+
loginCmd: "slack-cli login",
|
|
48831
|
+
credentialPaths: [
|
|
48832
|
+
"~/.slack/credentials.json"
|
|
48833
|
+
],
|
|
48834
|
+
capabilities: "Build & deploy Slack apps, manage workspaces & triggers, run datastore queries, tail logs — official Slack CLI.",
|
|
48835
|
+
description: "`slack-cli login` to auth. Invoked as `slack-cli` to avoid colliding with the Slack desktop app's `slack` binary.",
|
|
48836
|
+
versionCmd: "slack-cli version 2>/dev/null | head -1",
|
|
48837
|
+
binName: "slack-cli",
|
|
48838
|
+
binary: {
|
|
48839
|
+
"linux-x86_64": {
|
|
48840
|
+
url: "https://github.com/slackapi/slack-cli/releases/download/v4.2.0/slack_cli_4.2.0_linux_amd64.tar.gz",
|
|
48841
|
+
archive: "tar.gz",
|
|
48842
|
+
extract: "bin/slack"
|
|
48843
|
+
},
|
|
48844
|
+
"linux-aarch64": {
|
|
48845
|
+
url: "https://github.com/slackapi/slack-cli/releases/download/v4.2.0/slack_cli_4.2.0_linux_arm64.tar.gz",
|
|
48846
|
+
archive: "tar.gz",
|
|
48847
|
+
extract: "bin/slack"
|
|
48848
|
+
},
|
|
48849
|
+
"darwin-x86_64": {
|
|
48850
|
+
url: "https://github.com/slackapi/slack-cli/releases/download/v4.2.0/slack_cli_4.2.0_macOS_amd64.tar.gz",
|
|
48851
|
+
archive: "tar.gz",
|
|
48852
|
+
extract: "bin/slack"
|
|
48853
|
+
},
|
|
48854
|
+
"darwin-aarch64": {
|
|
48855
|
+
url: "https://github.com/slackapi/slack-cli/releases/download/v4.2.0/slack_cli_4.2.0_macOS_arm64.tar.gz",
|
|
48856
|
+
archive: "tar.gz",
|
|
48857
|
+
extract: "bin/slack"
|
|
48858
|
+
},
|
|
48859
|
+
"windows-x86_64": {
|
|
48860
|
+
url: "https://github.com/slackapi/slack-cli/releases/download/v4.2.0/slack_cli_4.2.0_windows_64-bit.zip",
|
|
48861
|
+
archive: "zip",
|
|
48862
|
+
extract: "bin/slack.exe"
|
|
48863
|
+
}
|
|
48864
|
+
}
|
|
48865
|
+
},
|
|
48827
48866
|
"telegram-send": {
|
|
48828
48867
|
category: "messaging",
|
|
48829
48868
|
pkg: "telegram-send",
|
|
@@ -48834,18 +48873,8 @@ var tool_catalog_default = {
|
|
|
48834
48873
|
"~/.config/telegram-send/telegram-send.conf"
|
|
48835
48874
|
],
|
|
48836
48875
|
capabilities: "Send messages, send files & images, send video & audio, Markdown/HTML formatting, channel & group support",
|
|
48837
|
-
description: "Send Telegram messages and files from CLI via a bot.",
|
|
48838
48876
|
versionCmd: "telegram-send --version 2>/dev/null | head -1"
|
|
48839
48877
|
},
|
|
48840
|
-
"apollo-api": {
|
|
48841
|
-
category: "marketing",
|
|
48842
|
-
pkg: "@todoforai/apollo-api",
|
|
48843
|
-
installer: "npm",
|
|
48844
|
-
label: "Apollo",
|
|
48845
|
-
capabilities: "Lead search & enrichment, email sequences, CRM sync",
|
|
48846
|
-
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.",
|
|
48847
|
-
versionCmd: "apollo-api --version 2>/dev/null | head -1"
|
|
48848
|
-
},
|
|
48849
48878
|
"meta-ads": {
|
|
48850
48879
|
category: "marketing",
|
|
48851
48880
|
pkg: "meta-ads",
|
|
@@ -48853,7 +48882,6 @@ var tool_catalog_default = {
|
|
|
48853
48882
|
label: "Meta Ads",
|
|
48854
48883
|
statusCmd: "meta auth status 2>&1",
|
|
48855
48884
|
capabilities: "Meta/Facebook & Instagram ad campaigns: campaigns/adsets/ads CRUD, insights & reporting, catalog/product-feed/product-set/product-item, ad creatives, datasets (pixels), pages — Marketing API via the official `meta` CLI.",
|
|
48856
|
-
description: "Manage Meta/Facebook & Instagram ad campaigns, insights, creatives, and catalogs.",
|
|
48857
48885
|
versionCmd: "meta --version 2>/dev/null | head -1"
|
|
48858
48886
|
},
|
|
48859
48887
|
"elevenlabs-api": {
|
|
@@ -48861,8 +48889,7 @@ var tool_catalog_default = {
|
|
|
48861
48889
|
pkg: "@todoforai/elevenlabs-api",
|
|
48862
48890
|
installer: "npm",
|
|
48863
48891
|
label: "ElevenLabs",
|
|
48864
|
-
capabilities: "Text-to-speech, voice cloning, multiple languages",
|
|
48865
|
-
description: "Use to generate speech audio from text, clone voices, or list voices. Needs ELEVENLABS_API_KEY.",
|
|
48892
|
+
capabilities: "Text-to-speech, voice cloning, multiple languages. Needs ELEVENLABS_API_KEY.",
|
|
48866
48893
|
versionCmd: "elevenlabs-api --version 2>/dev/null | head -1"
|
|
48867
48894
|
},
|
|
48868
48895
|
"codex-imagegen-api": {
|
|
@@ -48871,7 +48898,6 @@ var tool_catalog_default = {
|
|
|
48871
48898
|
installer: "npm",
|
|
48872
48899
|
label: "Image Gen",
|
|
48873
48900
|
capabilities: "AI image generation & editing via TODOFORAI backend (gpt-image)",
|
|
48874
|
-
description: "Generate or edit images with AI.",
|
|
48875
48901
|
versionCmd: "codex-imagegen-api --version 2>/dev/null | head -1"
|
|
48876
48902
|
},
|
|
48877
48903
|
"suno-api": {
|
|
@@ -48879,8 +48905,7 @@ var tool_catalog_default = {
|
|
|
48879
48905
|
pkg: "@todoforai/suno-api",
|
|
48880
48906
|
installer: "npm",
|
|
48881
48907
|
label: "Suno",
|
|
48882
|
-
capabilities: "AI music generation, custom lyrics, multiple genres",
|
|
48883
|
-
description: "Use to generate music/songs from a prompt or custom lyrics. Needs SUNO_API_KEY.",
|
|
48908
|
+
capabilities: "AI music generation, custom lyrics, multiple genres. Needs SUNO_API_KEY.",
|
|
48884
48909
|
versionCmd: "suno-api --version 2>/dev/null | head -1"
|
|
48885
48910
|
},
|
|
48886
48911
|
ntn: {
|
|
@@ -48894,7 +48919,6 @@ var tool_catalog_default = {
|
|
|
48894
48919
|
"~/.config/notion"
|
|
48895
48920
|
],
|
|
48896
48921
|
capabilities: "Official Notion CLI: workspace-wide OAuth login, raw API calls (`ntn api <path>`), page/datasource management, file uploads, Workers deploy.",
|
|
48897
|
-
description: "Read, create, and update Notion pages and databases.",
|
|
48898
48922
|
versionCmd: "ntn --version 2>/dev/null | head -1"
|
|
48899
48923
|
},
|
|
48900
48924
|
"perplexity-api": {
|
|
@@ -48903,7 +48927,6 @@ var tool_catalog_default = {
|
|
|
48903
48927
|
installer: "npm",
|
|
48904
48928
|
label: "Perplexity",
|
|
48905
48929
|
capabilities: "AI-powered web search, chat completions, async chat, embeddings and agent runs",
|
|
48906
|
-
description: "Web-grounded AI search with citations. Use when you need fresh information beyond training cutoff.",
|
|
48907
48930
|
versionCmd: "perplexity-api --version 2>/dev/null | head -1"
|
|
48908
48931
|
},
|
|
48909
48932
|
gh: {
|
|
@@ -48918,7 +48941,7 @@ var tool_catalog_default = {
|
|
|
48918
48941
|
"~/.config/gh/hosts.yml"
|
|
48919
48942
|
],
|
|
48920
48943
|
capabilities: "Repos, PRs & issues, actions & releases, gists & SSH keys",
|
|
48921
|
-
description: "
|
|
48944
|
+
description: "Prefer `gh` over raw GitHub API calls.",
|
|
48922
48945
|
versionCmd: "gh --version 2>/dev/null | head -1"
|
|
48923
48946
|
},
|
|
48924
48947
|
glab: {
|
|
@@ -48932,7 +48955,7 @@ var tool_catalog_default = {
|
|
|
48932
48955
|
"~/.config/glab-cli/config.yml"
|
|
48933
48956
|
],
|
|
48934
48957
|
capabilities: "Repos, MRs & issues, CI/CD pipelines, releases",
|
|
48935
|
-
description: "
|
|
48958
|
+
description: "GitLab equivalent of `gh`.",
|
|
48936
48959
|
versionCmd: "glab --version 2>/dev/null | head -1"
|
|
48937
48960
|
},
|
|
48938
48961
|
vercel: {
|
|
@@ -48946,7 +48969,7 @@ var tool_catalog_default = {
|
|
|
48946
48969
|
"~/.local/share/com.vercel.cli/auth.json"
|
|
48947
48970
|
],
|
|
48948
48971
|
capabilities: "Deploy & preview, environment variables, domain management",
|
|
48949
|
-
description: "
|
|
48972
|
+
description: "`vercel deploy`; manage env vars, domains, preview URLs.",
|
|
48950
48973
|
versionCmd: "vercel --version 2>/dev/null | head -1"
|
|
48951
48974
|
},
|
|
48952
48975
|
netlify: {
|
|
@@ -48960,7 +48983,7 @@ var tool_catalog_default = {
|
|
|
48960
48983
|
"~/.netlify/config.json"
|
|
48961
48984
|
],
|
|
48962
48985
|
capabilities: "Deploy & preview, serverless functions, forms & identity",
|
|
48963
|
-
description: "
|
|
48986
|
+
description: "`netlify deploy --prod` for production; manage env vars, forms, identity.",
|
|
48964
48987
|
versionCmd: "netlify --version 2>/dev/null | head -1"
|
|
48965
48988
|
},
|
|
48966
48989
|
firebase: {
|
|
@@ -48974,7 +48997,7 @@ var tool_catalog_default = {
|
|
|
48974
48997
|
"~/.config/configstore/firebase-tools.json"
|
|
48975
48998
|
],
|
|
48976
48999
|
capabilities: "Hosting & deploy, Firestore & Realtime DB, auth & functions",
|
|
48977
|
-
description: "
|
|
49000
|
+
description: "Deploy hosting/functions, manage Firestore/RTDB rules and data, auth users, emulators.",
|
|
48978
49001
|
versionCmd: "firebase --version 2>/dev/null | head -1"
|
|
48979
49002
|
},
|
|
48980
49003
|
wrangler: {
|
|
@@ -48988,7 +49011,7 @@ var tool_catalog_default = {
|
|
|
48988
49011
|
"~/.config/.wrangler/config/default.toml"
|
|
48989
49012
|
],
|
|
48990
49013
|
capabilities: "Workers & Pages deploy, KV & D1 storage, R2 & Queues",
|
|
48991
|
-
description: "
|
|
49014
|
+
description: "Tail logs with `wrangler tail`. Use `cloudflared` for tunnels.",
|
|
48992
49015
|
versionCmd: "wrangler --version 2>/dev/null | head -1"
|
|
48993
49016
|
},
|
|
48994
49017
|
stripe: {
|
|
@@ -49002,7 +49025,7 @@ var tool_catalog_default = {
|
|
|
49002
49025
|
"~/.config/stripe/config.toml"
|
|
49003
49026
|
],
|
|
49004
49027
|
capabilities: "Payments & subscriptions, webhook testing, invoice management",
|
|
49005
|
-
description: "
|
|
49028
|
+
description: "`stripe listen` forwards webhooks locally; `stripe get/post /v1/...` for raw API.",
|
|
49006
49029
|
versionCmd: "stripe --version 2>/dev/null | head -1"
|
|
49007
49030
|
},
|
|
49008
49031
|
flyctl: {
|
|
@@ -49016,7 +49039,7 @@ var tool_catalog_default = {
|
|
|
49016
49039
|
"~/.fly/config.yml"
|
|
49017
49040
|
],
|
|
49018
49041
|
capabilities: "Deploy containers globally, Postgres & volumes, auto-scaling",
|
|
49019
|
-
description: "
|
|
49042
|
+
description: "`flyctl deploy` from app dir; manage Postgres/volumes/machines/secrets.",
|
|
49020
49043
|
versionCmd: "flyctl version 2>/dev/null | head -1"
|
|
49021
49044
|
},
|
|
49022
49045
|
supabase: {
|
|
@@ -49030,7 +49053,7 @@ var tool_catalog_default = {
|
|
|
49030
49053
|
"~/.config/supabase/access-token"
|
|
49031
49054
|
],
|
|
49032
49055
|
capabilities: "Postgres database, auth & storage, edge functions",
|
|
49033
|
-
description: "
|
|
49056
|
+
description: "Link to cloud project; runs/migrates local Postgres.",
|
|
49034
49057
|
versionCmd: "supabase --version 2>/dev/null | head -1"
|
|
49035
49058
|
},
|
|
49036
49059
|
railway: {
|
|
@@ -49044,7 +49067,7 @@ var tool_catalog_default = {
|
|
|
49044
49067
|
"~/.railway/config.json"
|
|
49045
49068
|
],
|
|
49046
49069
|
capabilities: "Deploy apps & databases, environment management, auto-scaling",
|
|
49047
|
-
description: "
|
|
49070
|
+
description: "`railway up` to deploy current dir.",
|
|
49048
49071
|
versionCmd: "railway --version 2>/dev/null | head -1"
|
|
49049
49072
|
},
|
|
49050
49073
|
shopify: {
|
|
@@ -49057,16 +49080,23 @@ var tool_catalog_default = {
|
|
|
49057
49080
|
"~/.config/shopify/config.json"
|
|
49058
49081
|
],
|
|
49059
49082
|
capabilities: "Theme development, app scaffolding, store management",
|
|
49060
|
-
description: "Use for Shopify dev: scaffold/develop themes and apps, push/pull themes, run dev server against a store.",
|
|
49061
49083
|
versionCmd: "shopify version 2>/dev/null | head -1"
|
|
49062
49084
|
},
|
|
49085
|
+
"shop-app": {
|
|
49086
|
+
category: "ecommerce",
|
|
49087
|
+
pkg: "@todoforai/shop-app",
|
|
49088
|
+
installer: "npm",
|
|
49089
|
+
label: "Shop",
|
|
49090
|
+
capabilities: "Product search & price comparison across Shopify stores",
|
|
49091
|
+
description: "Shop.app product search across Shopify stores (no auth). `shop-app search '<query>' --ships-to HU -n 10` returns products with price, rating, URL, variants. `shop-app similar <variantId>` for lookalikes.",
|
|
49092
|
+
versionCmd: "shop-app --version 2>/dev/null | head -1"
|
|
49093
|
+
},
|
|
49063
49094
|
"datadog-ci": {
|
|
49064
49095
|
category: "monitoring",
|
|
49065
49096
|
pkg: "@datadog/datadog-ci",
|
|
49066
49097
|
installer: "npm",
|
|
49067
49098
|
label: "Datadog",
|
|
49068
|
-
capabilities: "CI test visibility, sourcemap uploads, deployment tracking",
|
|
49069
|
-
description: "Use in CI to upload test results/sourcemaps/coverage to Datadog and mark deployments. Needs DATADOG_API_KEY.",
|
|
49099
|
+
capabilities: "CI test visibility, sourcemap uploads, deployment tracking. Needs DATADOG_API_KEY.",
|
|
49070
49100
|
versionCmd: "datadog-ci version 2>/dev/null | head -1"
|
|
49071
49101
|
},
|
|
49072
49102
|
"sentry-cli": {
|
|
@@ -49080,7 +49110,7 @@ var tool_catalog_default = {
|
|
|
49080
49110
|
"~/.sentryclirc"
|
|
49081
49111
|
],
|
|
49082
49112
|
capabilities: "Release management, sourcemap uploads, error monitoring",
|
|
49083
|
-
description: "
|
|
49113
|
+
description: "`sentry-cli releases new <version>`.",
|
|
49084
49114
|
versionCmd: "sentry-cli --version 2>/dev/null | head -1"
|
|
49085
49115
|
},
|
|
49086
49116
|
todoai: {
|
|
@@ -49089,7 +49119,7 @@ var tool_catalog_default = {
|
|
|
49089
49119
|
installer: "bun",
|
|
49090
49120
|
label: "TODOforAI",
|
|
49091
49121
|
capabilities: "Create/list/inspect/update TODOs, run templates & workflows, platform API access",
|
|
49092
|
-
description:
|
|
49122
|
+
description: '`todoai list` (`--status open`) to browse, `todoai "prompt"` to create, `--inspect <id>` to read a chat log, `status/addmessage/delete` to manage, `--template` for registry workflows.',
|
|
49093
49123
|
installCmd: "bun add -g @todoforai/cli",
|
|
49094
49124
|
versionCmd: "todoai --version 2>/dev/null | head -1",
|
|
49095
49125
|
internal: true
|
|
@@ -49100,7 +49130,7 @@ var tool_catalog_default = {
|
|
|
49100
49130
|
installer: "npm",
|
|
49101
49131
|
label: "Postman",
|
|
49102
49132
|
capabilities: "Run API test collections, CI/CD integration, HTML reports",
|
|
49103
|
-
description: "
|
|
49133
|
+
description: "`newman run <collection.json> -e <env.json>`.",
|
|
49104
49134
|
versionCmd: "newman --version 2>/dev/null | head -1"
|
|
49105
49135
|
},
|
|
49106
49136
|
curl: {
|
|
@@ -49121,22 +49151,20 @@ var tool_catalog_default = {
|
|
|
49121
49151
|
installer: "binary",
|
|
49122
49152
|
label: "Tunnel",
|
|
49123
49153
|
capabilities: "Tunnel to localhost, expose local services, zero-trust access",
|
|
49124
|
-
description: "
|
|
49154
|
+
description: "`cloudflared tunnel --url http://localhost:<port>` prints a public https URL.",
|
|
49125
49155
|
versionCmd: "cloudflared --version 2>/dev/null | head -1"
|
|
49126
49156
|
},
|
|
49127
49157
|
vault: {
|
|
49128
49158
|
category: "security",
|
|
49129
49159
|
pkg: "vault",
|
|
49130
49160
|
installer: "binary",
|
|
49131
|
-
preinstallCloud: true,
|
|
49132
49161
|
label: "HashiCorp Vault",
|
|
49133
49162
|
statusCmd: "vault token lookup >/dev/null 2>&1 && echo authenticated",
|
|
49134
49163
|
loginCmd: "vault login",
|
|
49135
49164
|
credentialPaths: [
|
|
49136
49165
|
"~/.vault-token"
|
|
49137
49166
|
],
|
|
49138
|
-
capabilities: "Secrets management, dynamic credentials",
|
|
49139
|
-
description: "HashiCorp Vault CLI — only for Terraform/vals/ecosystem integrations that expect it. For all credential read/write tasks use `tfa-vault` instead. Needs VAULT_ADDR + VAULT_TOKEN.",
|
|
49167
|
+
capabilities: "Secrets management, dynamic credentials. Needs VAULT_ADDR + VAULT_TOKEN; for credential read/write use `tfa-vault` instead.",
|
|
49140
49168
|
versionCmd: "vault --version 2>/dev/null | head -1"
|
|
49141
49169
|
},
|
|
49142
49170
|
rclone: {
|
|
@@ -49151,7 +49179,6 @@ var tool_catalog_default = {
|
|
|
49151
49179
|
"~/.config/rclone/rclone.conf"
|
|
49152
49180
|
],
|
|
49153
49181
|
capabilities: "Access Google Drive, OneDrive, Dropbox, S3 and 40+ cloud providers. List, copy, sync, move, mount files as virtual filesystem (FUSE). Use 'rclone config create <name> <provider>' to connect (opens browser for OAuth).",
|
|
49154
|
-
description: "Access cloud storage (Google Drive, OneDrive, Dropbox, S3, 40+ providers). List, copy, sync, or mount files.",
|
|
49155
49182
|
subProviders: {
|
|
49156
49183
|
gdrive: {
|
|
49157
49184
|
label: "Google Drive",
|
|
@@ -49196,8 +49223,7 @@ var tool_catalog_default = {
|
|
|
49196
49223
|
pkg: "pymupdf",
|
|
49197
49224
|
installer: "pip",
|
|
49198
49225
|
label: "PDF",
|
|
49199
|
-
capabilities: "PDF text editing, extraction, merge, split",
|
|
49200
|
-
description: "Read, edit, merge, or split PDFs programmatically. Call via `python3 -c`.",
|
|
49226
|
+
capabilities: "PDF text editing, extraction, merge, split. Call via `python3 -c`.",
|
|
49201
49227
|
versionCmd: "python3 -c 'import pymupdf; print(pymupdf.__version__)' 2>/dev/null",
|
|
49202
49228
|
preinstallCloud: true
|
|
49203
49229
|
},
|
|
@@ -49208,16 +49234,16 @@ var tool_catalog_default = {
|
|
|
49208
49234
|
preinstallCloud: true,
|
|
49209
49235
|
label: "Browser",
|
|
49210
49236
|
capabilities: "Headless browser automation, web scraping, accessibility tree snapshots, screenshots, PDF generation",
|
|
49211
|
-
description: "
|
|
49237
|
+
description: "Default browser. On a PC with a display prefer a visible window the user can watch/interact with (needed for CAPTCHA/MFA/login): launch a separate Chrome with `google-chrome --remote-debugging-port=9222 --user-data-dir=$HOME/.config/google-chrome-cdp >/tmp/chrome-cdp.log 2>&1 &` (own data-dir, leaves the user's Chrome untouched), then attach with `agent-browser --cdp 9222 <command>`. Use headless only on the cloud or when no display is available.",
|
|
49212
49238
|
versionCmd: "agent-browser --version 2>/dev/null | head -1"
|
|
49213
49239
|
},
|
|
49214
49240
|
"todoforai-browser": {
|
|
49215
49241
|
category: "development",
|
|
49216
49242
|
pkg: "@todoforai/browser",
|
|
49217
49243
|
installer: "npm",
|
|
49244
|
+
preinstallCloud: true,
|
|
49218
49245
|
label: "Browser (Extension)",
|
|
49219
49246
|
capabilities: "Browser automation via extension (non-headless), web scraping, accessibility tree snapshots, screenshots, PDF generation",
|
|
49220
|
-
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.",
|
|
49221
49247
|
versionCmd: `node -p "require(require('child_process').execSync('which todoforai-browser',{encoding:'utf8'}).trim().replace(/\\/dist\\/index\\.js$/, '/package.json')).version" 2>/dev/null`,
|
|
49222
49248
|
internal: true
|
|
49223
49249
|
},
|
|
@@ -49227,57 +49253,19 @@ var tool_catalog_default = {
|
|
|
49227
49253
|
installer: "npm",
|
|
49228
49254
|
label: "Sub-agent",
|
|
49229
49255
|
capabilities: "Spawn a TODO for AI sub-agent (FluidAgent) from the CLI; pipe stdin in, get an answer out",
|
|
49230
|
-
description: "Spawn a sub-agent for a focused task. Pipe context via stdin.",
|
|
49231
49256
|
versionCmd: "todoforai-subagent --version 2>/dev/null | head -1",
|
|
49232
49257
|
installCmd: "bun add -g @todoforai/subagent",
|
|
49233
49258
|
internal: true
|
|
49234
49259
|
},
|
|
49235
|
-
"
|
|
49260
|
+
"tfa-handoff": {
|
|
49236
49261
|
category: "development",
|
|
49237
|
-
pkg: "@todoforai/
|
|
49262
|
+
pkg: "@todoforai/tfa-handoff",
|
|
49238
49263
|
installer: "npm",
|
|
49239
|
-
label: "
|
|
49240
|
-
capabilities: "
|
|
49241
|
-
description: "
|
|
49242
|
-
versionCmd: "
|
|
49243
|
-
installCmd: "bun add -g @todoforai/
|
|
49244
|
-
internal: true
|
|
49245
|
-
},
|
|
49246
|
-
"tfa-explore": {
|
|
49247
|
-
category: "development",
|
|
49248
|
-
pkg: "@todoforai/tfa-explore",
|
|
49249
|
-
installer: "npm",
|
|
49250
|
-
label: "Explore",
|
|
49251
|
-
capabilities: "Explore a codebase as a real TODO: read-only agent maps structure, surfaces relevant files, streams findings to terminal",
|
|
49252
|
-
description: "Explore a codebase with a read-only sub-agent. Streams findings live.",
|
|
49253
|
-
versionCmd: "tfa-explore --version 2>/dev/null | head -1",
|
|
49254
|
-
installCmd: "bun add -g @todoforai/tfa-explore",
|
|
49255
|
-
preinstall: true,
|
|
49256
|
-
preinstallCloud: true,
|
|
49257
|
-
internal: true
|
|
49258
|
-
},
|
|
49259
|
-
"tfa-review": {
|
|
49260
|
-
category: "development",
|
|
49261
|
-
pkg: "@todoforai/tfa-review",
|
|
49262
|
-
installer: "npm",
|
|
49263
|
-
label: "Review",
|
|
49264
|
-
capabilities: "Review a git diff as a real TODO: read-only agent assesses goal, finds issues, suggests simpler approaches",
|
|
49265
|
-
description: "Review a git diff with a read-only sub-agent.",
|
|
49266
|
-
versionCmd: "tfa-review --version 2>/dev/null | head -1",
|
|
49267
|
-
installCmd: "bun add -g @todoforai/tfa-review",
|
|
49268
|
-
preinstall: true,
|
|
49269
|
-
preinstallCloud: true,
|
|
49270
|
-
internal: true
|
|
49271
|
-
},
|
|
49272
|
-
"tfa-summary": {
|
|
49273
|
-
category: "development",
|
|
49274
|
-
pkg: "@todoforai/tfa-summary",
|
|
49275
|
-
installer: "npm",
|
|
49276
|
-
label: "Summarize",
|
|
49277
|
-
capabilities: "Summarize files or piped input as a real TODO with no-tools sub-agent",
|
|
49278
|
-
description: "Summarize files or piped content as a visible TODO.",
|
|
49279
|
-
versionCmd: "tfa-summary --version 2>/dev/null | head -1",
|
|
49280
|
-
installCmd: "bun add -g @todoforai/tfa-summary",
|
|
49264
|
+
label: "Handoff",
|
|
49265
|
+
capabilities: "Compact a conversation into a handoff document as a hidden TODO; pipe filter for `todoai --inspect … | tfa-handoff … | todoai`",
|
|
49266
|
+
description: "Compact a conversation into a handoff document for a fresh agent.",
|
|
49267
|
+
versionCmd: "tfa-handoff --version 2>/dev/null | head -1",
|
|
49268
|
+
installCmd: "bun add -g @todoforai/tfa-handoff",
|
|
49281
49269
|
internal: true
|
|
49282
49270
|
},
|
|
49283
49271
|
"tfa-vault": {
|
|
@@ -49285,12 +49273,11 @@ var tool_catalog_default = {
|
|
|
49285
49273
|
pkg: "@todoforai/vault",
|
|
49286
49274
|
installer: "npm",
|
|
49287
49275
|
label: "Vault",
|
|
49288
|
-
capabilities: "
|
|
49289
|
-
description: "
|
|
49276
|
+
capabilities: "The user's dedicated password manager and secure secret store — a zero-config TODOforAI vault CLI: put/get/patch/list/rm passwords & secrets in the user's KV v2 vault. Reuses bridge credentials (TODOFORAI_API_KEY / ~/.config/todoforai/credentials.json); derives vault URL from backend URL.",
|
|
49277
|
+
description: "This is the user's own dedicated password manager and secure secret store (encrypted KV vault), and the `whoami` transcript shows you're authorized to it, so reading and using the secrets it holds is the intended workflow. Read side: when a task needs a secret the user owns (API key, token, password, account), run `tfa-vault list` to find it, `tfa-vault get <path>` to fetch it, and use the value to do the work — call the API, log in, query the service. There's no need to ask the user for a value the vault already holds. Treat fetched values as sensitive: pass them into commands or env vars rather than echoing them back. Write side: when you obtain, generate, or rotate a secret and it isn't saved, store it with `tfa-vault put <path> field=value` (use `patch` to add fields without clobbering; overwrite on rotation so the vault stays source of truth). Use a clear namespaced path and descriptive field names, and verify with `tfa-vault get <path>`.",
|
|
49290
49278
|
versionCmd: "tfa-vault --version 2>/dev/null | head -1",
|
|
49291
49279
|
statusCmd: "tfa-vault whoami",
|
|
49292
49280
|
installCmd: "bun add -g @todoforai/vault",
|
|
49293
|
-
preinstall: true,
|
|
49294
49281
|
preinstallCloud: true
|
|
49295
49282
|
},
|
|
49296
49283
|
ripgrep: {
|
|
@@ -49582,6 +49569,17 @@ function isToolInstalled(name) {
|
|
|
49582
49569
|
}
|
|
49583
49570
|
return whichWithTools(name) !== null;
|
|
49584
49571
|
}
|
|
49572
|
+
function findReferencedTools(content) {
|
|
49573
|
+
const stripped = content.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
|
|
49574
|
+
return Object.keys(TOOL_CATALOG).filter((name) => {
|
|
49575
|
+
const esc = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
49576
|
+
const re = new RegExp(String.raw`(?:^|[|;&\n]|&&|\|\||` + String.raw`\$\(|` + "`" + String.raw`|xargs\s+|sudo\s+|env\s+)\s*` + esc + String.raw`\b(?!-)`, "m");
|
|
49577
|
+
return re.test(stripped);
|
|
49578
|
+
});
|
|
49579
|
+
}
|
|
49580
|
+
function findMissingTools(content) {
|
|
49581
|
+
return findReferencedTools(content).filter((name) => TOOL_CATALOG[name].installer !== "system" && !isToolInstalled(name));
|
|
49582
|
+
}
|
|
49585
49583
|
async function installBinary(name) {
|
|
49586
49584
|
const urlFunc = BINARY_URL_FUNCS[name];
|
|
49587
49585
|
if (!urlFunc) {
|
|
@@ -49655,6 +49653,15 @@ function findFileRecursive(dir, names) {
|
|
|
49655
49653
|
}
|
|
49656
49654
|
return null;
|
|
49657
49655
|
}
|
|
49656
|
+
function getInstallCommand(name) {
|
|
49657
|
+
const e = TOOL_CATALOG[name];
|
|
49658
|
+
return e.installCmd || {
|
|
49659
|
+
npm: `npm install --prefix ~/.todoforai/tools ${e.pkg}`,
|
|
49660
|
+
bun: `bun add --cwd ~/.todoforai/tools ${e.pkg}`,
|
|
49661
|
+
pip: `pip install ${e.pkg}`,
|
|
49662
|
+
binary: `download ${e.pkg}`
|
|
49663
|
+
}[e.installer] || `install ${e.pkg}`;
|
|
49664
|
+
}
|
|
49658
49665
|
function installWithNpm(name, pkg) {
|
|
49659
49666
|
const TIMEOUT_MS = 120000;
|
|
49660
49667
|
log2("info", `Installing ${name} via npm (${pkg})`);
|
|
@@ -49756,11 +49763,11 @@ function installWithPip(name, pkg) {
|
|
|
49756
49763
|
log2("info", `Installing ${name} via pip (${pkg})`);
|
|
49757
49764
|
const args = useVenv ? ["-m", "pip", "install", pkg] : ["-m", "pip", "install", "--user", pkg];
|
|
49758
49765
|
const result = spawnSync(python, args, { stdio: "pipe", timeout: 120000 });
|
|
49759
|
-
|
|
49760
|
-
|
|
49761
|
-
|
|
49762
|
-
|
|
49763
|
-
|
|
49766
|
+
const stderr = result.stderr?.toString().trim() || "";
|
|
49767
|
+
if (result.signal)
|
|
49768
|
+
throw new Error(`pip install killed by ${result.signal}${result.signal === "SIGTERM" ? " (likely timed out after 120s)" : ""}`);
|
|
49769
|
+
if (result.status !== 0)
|
|
49770
|
+
throw new Error(`pip install failed (exit ${result.status}): ${stderr || result.stdout?.toString().trim() || "(empty)"}`);
|
|
49764
49771
|
}
|
|
49765
49772
|
var INSTALLERS = {
|
|
49766
49773
|
npm: installWithNpm,
|
|
@@ -49829,6 +49836,17 @@ function uninstallTool(name) {
|
|
|
49829
49836
|
return false;
|
|
49830
49837
|
}
|
|
49831
49838
|
}
|
|
49839
|
+
async function autoInstallMissingTools(content) {
|
|
49840
|
+
const lines = [];
|
|
49841
|
+
for (const name of findMissingTools(content)) {
|
|
49842
|
+
const ok = await ensureTool(name) && isToolInstalled(name);
|
|
49843
|
+
lines.push(`$ ${getInstallCommand(name)}
|
|
49844
|
+
[${ok ? "installed" : "install failed"}: ${name}]`);
|
|
49845
|
+
}
|
|
49846
|
+
return lines.length ? lines.join(`
|
|
49847
|
+
`) + `
|
|
49848
|
+
` : "";
|
|
49849
|
+
}
|
|
49832
49850
|
function execShellAsync(cmd, env, timeout) {
|
|
49833
49851
|
return new Promise((resolve) => {
|
|
49834
49852
|
execFile("sh", ["-c", cmd], { env, timeout, encoding: "utf-8", maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
|
|
@@ -51531,6 +51549,56 @@ async function readFdTarget(pid, fd3) {
|
|
|
51531
51549
|
}
|
|
51532
51550
|
var pauseDetector = new PtyPauseDetector;
|
|
51533
51551
|
|
|
51552
|
+
// ../../packages/shared-fbe/src/outputLimits.ts
|
|
51553
|
+
var MAX_RESULT_LINES = 100;
|
|
51554
|
+
var MAX_LINE_LEN = 300;
|
|
51555
|
+
var MAX_TOTAL_LEN = 1e5;
|
|
51556
|
+
var STREAM_FIRST = 1e4;
|
|
51557
|
+
var STREAM_LAST = 1e4;
|
|
51558
|
+
var RUN_OUTPUT_CAP = 256 * 1024;
|
|
51559
|
+
var OUTPUT_POLICIES = {
|
|
51560
|
+
safe: { firstLimit: STREAM_FIRST, lastLimit: STREAM_LAST, hardCap: STREAM_FIRST + STREAM_LAST, lineLimit: MAX_LINE_LEN },
|
|
51561
|
+
wide: { firstLimit: STREAM_FIRST, lastLimit: STREAM_LAST, hardCap: STREAM_FIRST + STREAM_LAST, lineLimit: Infinity },
|
|
51562
|
+
full: { firstLimit: Infinity, lastLimit: 0, hardCap: RUN_OUTPUT_CAP, lineLimit: MAX_LINE_LEN },
|
|
51563
|
+
raw: { firstLimit: Infinity, lastLimit: 0, hardCap: Infinity, lineLimit: Infinity }
|
|
51564
|
+
};
|
|
51565
|
+
var DEFAULT_OUTPUT_MODE = "safe";
|
|
51566
|
+
function resolveOutputPolicy(mode) {
|
|
51567
|
+
return OUTPUT_POLICIES[mode] ?? OUTPUT_POLICIES[DEFAULT_OUTPUT_MODE];
|
|
51568
|
+
}
|
|
51569
|
+
function truncateLines(text, { maxLines = MAX_RESULT_LINES, maxLineLen = MAX_LINE_LEN, maxTotalLen = MAX_TOTAL_LEN } = {}, unit = "matches") {
|
|
51570
|
+
let lines = text.split(`
|
|
51571
|
+
`).filter((l) => l.trim());
|
|
51572
|
+
const overflow = lines.length - maxLines;
|
|
51573
|
+
if (overflow > 0)
|
|
51574
|
+
lines = lines.slice(0, maxLines);
|
|
51575
|
+
lines = lines.map((line) => line.length > maxLineLen ? line.slice(0, maxLineLen) + ` ...[+${line.length - maxLineLen} chars]` : line);
|
|
51576
|
+
let output = lines.join(`
|
|
51577
|
+
`);
|
|
51578
|
+
if (overflow > 0)
|
|
51579
|
+
output += `
|
|
51580
|
+
... (${overflow} more ${unit} truncated)`;
|
|
51581
|
+
if (output.length > maxTotalLen)
|
|
51582
|
+
output = output.slice(0, maxTotalLen) + `
|
|
51583
|
+
... (output truncated)`;
|
|
51584
|
+
return output;
|
|
51585
|
+
}
|
|
51586
|
+
function capLineWidth(text, lineLimit) {
|
|
51587
|
+
if (!isFinite(lineLimit))
|
|
51588
|
+
return text;
|
|
51589
|
+
return text.split(`
|
|
51590
|
+
`).map((line) => line.length > lineLimit ? line.slice(0, lineLimit) + ` ...[+${line.length - lineLimit} chars]` : line).join(`
|
|
51591
|
+
`);
|
|
51592
|
+
}
|
|
51593
|
+
function formatTruncationNotice(totalLen, firstLimit, lastPart) {
|
|
51594
|
+
const dropped = totalLen - firstLimit - lastPart.length;
|
|
51595
|
+
return `
|
|
51596
|
+
|
|
51597
|
+
... [truncated ${dropped} chars] ...
|
|
51598
|
+
|
|
51599
|
+
${lastPart}`;
|
|
51600
|
+
}
|
|
51601
|
+
|
|
51534
51602
|
// src/shell.ts
|
|
51535
51603
|
var IS_WIN = os6.platform() === "win32";
|
|
51536
51604
|
var HAS_BUN = typeof globalThis.Bun !== "undefined";
|
|
@@ -51550,6 +51618,14 @@ function whichSync(name) {
|
|
|
51550
51618
|
}
|
|
51551
51619
|
return null;
|
|
51552
51620
|
}
|
|
51621
|
+
function isAccessibleDir(p10) {
|
|
51622
|
+
try {
|
|
51623
|
+
fs8.accessSync(p10, fs8.constants.X_OK);
|
|
51624
|
+
return fs8.statSync(p10).isDirectory();
|
|
51625
|
+
} catch {
|
|
51626
|
+
return false;
|
|
51627
|
+
}
|
|
51628
|
+
}
|
|
51553
51629
|
function getShellCommand(content) {
|
|
51554
51630
|
if (!IS_WIN)
|
|
51555
51631
|
return { shell: "/bin/bash", args: ["-c", content] };
|
|
@@ -51567,26 +51643,26 @@ function getShellCommand(content) {
|
|
|
51567
51643
|
}
|
|
51568
51644
|
return { shell: "cmd.exe", args: ["/c", "chcp 65001>nul && " + content] };
|
|
51569
51645
|
}
|
|
51570
|
-
var STREAM_FIRST = 1e4;
|
|
51571
|
-
var STREAM_LAST = 1e4;
|
|
51572
51646
|
|
|
51573
51647
|
class OutputBuffer {
|
|
51574
|
-
firstLimit;
|
|
51575
|
-
lastLimit;
|
|
51576
51648
|
firstPart = "";
|
|
51577
51649
|
lastPart = "";
|
|
51578
51650
|
totalLen = 0;
|
|
51579
51651
|
truncated = false;
|
|
51580
51652
|
truncMsgSent = false;
|
|
51581
|
-
|
|
51582
|
-
|
|
51583
|
-
|
|
51653
|
+
headLimit;
|
|
51654
|
+
lastLimit;
|
|
51655
|
+
lineLimit;
|
|
51656
|
+
constructor(policy = OUTPUT_POLICIES[DEFAULT_OUTPUT_MODE]) {
|
|
51657
|
+
this.headLimit = Math.min(policy.firstLimit, policy.hardCap);
|
|
51658
|
+
this.lastLimit = Math.min(policy.lastLimit, policy.hardCap - this.headLimit);
|
|
51659
|
+
this.lineLimit = policy.lineLimit;
|
|
51584
51660
|
}
|
|
51585
51661
|
append(text) {
|
|
51586
51662
|
this.totalLen += text.length;
|
|
51587
51663
|
let toStream = "";
|
|
51588
|
-
if (this.firstPart.length < this.
|
|
51589
|
-
const remaining = this.
|
|
51664
|
+
if (this.firstPart.length < this.headLimit) {
|
|
51665
|
+
const remaining = this.headLimit - this.firstPart.length;
|
|
51590
51666
|
toStream = text.slice(0, remaining);
|
|
51591
51667
|
this.firstPart += toStream;
|
|
51592
51668
|
text = text.slice(remaining);
|
|
@@ -51594,19 +51670,14 @@ class OutputBuffer {
|
|
|
51594
51670
|
if (text) {
|
|
51595
51671
|
if (!this.truncated)
|
|
51596
51672
|
this.truncated = true;
|
|
51597
|
-
this.lastPart = (this.lastPart + text).slice(-this.lastLimit);
|
|
51673
|
+
this.lastPart = this.lastLimit > 0 ? (this.lastPart + text).slice(-this.lastLimit) : "";
|
|
51598
51674
|
}
|
|
51599
51675
|
return toStream;
|
|
51600
51676
|
}
|
|
51601
51677
|
getTruncationNotice() {
|
|
51602
51678
|
if (this.truncated && !this.truncMsgSent) {
|
|
51603
51679
|
this.truncMsgSent = true;
|
|
51604
|
-
|
|
51605
|
-
return `
|
|
51606
|
-
|
|
51607
|
-
... [truncated ${dropped} chars] ...
|
|
51608
|
-
|
|
51609
|
-
${this.lastPart}`;
|
|
51680
|
+
return formatTruncationNotice(this.totalLen, this.firstPart.length, this.lastPart);
|
|
51610
51681
|
}
|
|
51611
51682
|
return "";
|
|
51612
51683
|
}
|
|
@@ -51619,27 +51690,27 @@ ${this.lastPart}`;
|
|
|
51619
51690
|
}
|
|
51620
51691
|
getOutput() {
|
|
51621
51692
|
if (!this.truncated)
|
|
51622
|
-
return this.firstPart;
|
|
51623
|
-
return this.firstPart + `
|
|
51693
|
+
return capLineWidth(this.firstPart, this.lineLimit);
|
|
51694
|
+
return capLineWidth(this.firstPart, this.lineLimit) + `
|
|
51624
51695
|
|
|
51625
51696
|
... [truncated: showing first ${this.firstPart.length} and last ${this.lastPart.length} chars of ${this.totalLen} total] ...
|
|
51626
51697
|
|
|
51627
|
-
${this.lastPart}`;
|
|
51698
|
+
${capLineWidth(this.lastPart, this.lineLimit)}`;
|
|
51628
51699
|
}
|
|
51629
51700
|
getRawIfComplete() {
|
|
51630
|
-
return this.truncated ? null : this.firstPart;
|
|
51701
|
+
return this.truncated ? null : capLineWidth(this.firstPart, this.lineLimit);
|
|
51631
51702
|
}
|
|
51632
51703
|
}
|
|
51633
51704
|
var processes = new Map;
|
|
51634
51705
|
var outputBuffers = new Map;
|
|
51635
51706
|
var completionResolvers = new Map;
|
|
51636
51707
|
var exitedOutputByPid = new Map;
|
|
51637
|
-
async function executeBlock(blockId, content, send, todoId, messageId, timeout, cwd, manual = false, runMode, edgeId, agentSettingsId = "", keepAliveOnTimeout = false) {
|
|
51708
|
+
async function executeBlock(blockId, content, send, todoId, messageId, timeout, cwd, manual = false, runMode, edgeId, agentSettingsId = "", keepAliveOnTimeout = false, outputMode = DEFAULT_OUTPUT_MODE) {
|
|
51638
51709
|
if (processes.has(blockId)) {
|
|
51639
51710
|
console.log(`[shell] killing existing process for blockId=${blockId}`);
|
|
51640
51711
|
interruptBlock(blockId);
|
|
51641
51712
|
}
|
|
51642
|
-
const buf = new OutputBuffer;
|
|
51713
|
+
const buf = new OutputBuffer(resolveOutputPolicy(outputMode));
|
|
51643
51714
|
outputBuffers.set(blockId, buf);
|
|
51644
51715
|
try {
|
|
51645
51716
|
const tmpDir = path5.join(os6.tmpdir(), "todoforai");
|
|
@@ -51647,10 +51718,16 @@ async function executeBlock(blockId, content, send, todoId, messageId, timeout,
|
|
|
51647
51718
|
fs8.mkdirSync(tmpDir, { recursive: true });
|
|
51648
51719
|
if (cwd) {
|
|
51649
51720
|
const expanded = cwd.replace(/^~/, process.env.HOME || "~");
|
|
51650
|
-
cwd =
|
|
51721
|
+
cwd = isAccessibleDir(expanded) ? expanded : tmpDir;
|
|
51651
51722
|
} else {
|
|
51652
51723
|
cwd = tmpDir;
|
|
51653
51724
|
}
|
|
51725
|
+
const installNotice = await autoInstallMissingTools(content);
|
|
51726
|
+
if (installNotice) {
|
|
51727
|
+
const toStream = buf.append(installNotice);
|
|
51728
|
+
if (toStream)
|
|
51729
|
+
await send(msg.shellBlockResult(todoId, blockId, toStream, messageId));
|
|
51730
|
+
}
|
|
51654
51731
|
await send({
|
|
51655
51732
|
type: "BLOCK_UPDATE",
|
|
51656
51733
|
payload: { todoId, blockId, messageId, updates: { status: "RUNNING" } }
|
|
@@ -51815,7 +51892,7 @@ async function executeBlock(blockId, content, send, todoId, messageId, timeout,
|
|
|
51815
51892
|
spawnWithPipes();
|
|
51816
51893
|
}
|
|
51817
51894
|
} catch (e) {
|
|
51818
|
-
await send(msg.shellBlockResult(todoId, blockId, `Error creating process: ${e.message}`, messageId));
|
|
51895
|
+
await send(msg.shellBlockResult(todoId, blockId, `Error creating process: ${e.message} (cwd: ${cwd})`, messageId));
|
|
51819
51896
|
const resolver = completionResolvers.get(blockId);
|
|
51820
51897
|
if (resolver) {
|
|
51821
51898
|
resolver();
|
|
@@ -52392,7 +52469,7 @@ function detectContentType(output, cmd) {
|
|
|
52392
52469
|
return { result: output };
|
|
52393
52470
|
}
|
|
52394
52471
|
register("execute_shell_command", async (args, client) => {
|
|
52395
|
-
const { cmd, cwd = args.root_path ?? "", todoId = "", messageId = "", blockId = "", agentSettingsId = "", pid: resumePid = 0 } = args;
|
|
52472
|
+
const { cmd, cwd = args.root_path ?? "", todoId = "", messageId = "", blockId = "", agentSettingsId = "", pid: resumePid = 0, output: outputMode = DEFAULT_OUTPUT_MODE } = args;
|
|
52396
52473
|
const timeout = Math.max(args.timeout ?? 120, client?.maxTimeout ?? 0);
|
|
52397
52474
|
const canStream = !!(todoId && blockId && client);
|
|
52398
52475
|
if (!canStream) {
|
|
@@ -52433,7 +52510,7 @@ register("execute_shell_command", async (args, client) => {
|
|
|
52433
52510
|
}
|
|
52434
52511
|
try {
|
|
52435
52512
|
await send(msg.shellBlockStart(todoId, blockId, "execute", messageId));
|
|
52436
|
-
await executeBlock(blockId, execCmd, send, todoId, messageId, timeout, cwd, false, "internal", undefined, agentSettingsId, true);
|
|
52513
|
+
await executeBlock(blockId, execCmd, send, todoId, messageId, timeout, cwd, false, "internal", undefined, agentSettingsId, true, outputMode);
|
|
52437
52514
|
await waitForCompletion(blockId, (timeout + 5) * 1000);
|
|
52438
52515
|
const rawOutput = getBlockRawOutput(blockId);
|
|
52439
52516
|
let output = rawOutput ?? getBlockOutput(blockId);
|
|
@@ -52504,7 +52581,7 @@ register("read_file_base64", async (args) => {
|
|
|
52504
52581
|
return { path: fullPath, base64: data.toString("base64"), bytes: data.length };
|
|
52505
52582
|
});
|
|
52506
52583
|
register("search_files", async (args) => {
|
|
52507
|
-
const { pattern, path: p10 = ".", cwd = args.root_path ?? "", head = 100, max_count = 5, glob: globPattern = "", ignore_case = true } = args;
|
|
52584
|
+
const { pattern, path: p10 = ".", cwd = args.root_path ?? "", head = 100, max_count = 5, glob: globPattern = "", ignore_case = true, output: outputMode = DEFAULT_OUTPUT_MODE } = args;
|
|
52508
52585
|
const { execSync: execWhich } = await import("child_process");
|
|
52509
52586
|
const whichCmd = process.platform === "win32" ? "where" : "which";
|
|
52510
52587
|
const which = (bin) => {
|
|
@@ -52563,17 +52640,10 @@ register("search_files", async (args) => {
|
|
|
52563
52640
|
});
|
|
52564
52641
|
if (code === 0) {
|
|
52565
52642
|
let output = stdout;
|
|
52566
|
-
const lines = output.split(`
|
|
52567
|
-
`).filter((l) => l.trim());
|
|
52568
|
-
if (lines.length > head) {
|
|
52569
|
-
output = lines.slice(0, head).join(`
|
|
52570
|
-
`) + `
|
|
52571
|
-
... (${lines.length - head} more matches truncated)`;
|
|
52572
|
-
}
|
|
52573
52643
|
if ((cwd || searchPath) && output) {
|
|
52574
52644
|
const searchBase = searchPath && fs11.existsSync(searchPath) && fs11.statSync(searchPath).isDirectory() ? searchPath : path8.dirname(searchPath);
|
|
52575
52645
|
const bases = Array.from(new Set([cwd, searchBase].filter(Boolean)));
|
|
52576
|
-
const
|
|
52646
|
+
const lines = output.split(`
|
|
52577
52647
|
`).map((line) => {
|
|
52578
52648
|
if (line.includes(":")) {
|
|
52579
52649
|
const colonIdx = line.indexOf(":");
|
|
@@ -52583,21 +52653,14 @@ register("search_files", async (args) => {
|
|
|
52583
52653
|
const candidates = [filePart, ...bases.map((b) => path8.relative(b, filePart))].filter((p11) => (p11.match(/\.\.\//g) || []).length <= 2);
|
|
52584
52654
|
filePart = candidates.reduce((a, b) => a.length <= b.length ? a : b, filePart);
|
|
52585
52655
|
} catch {}
|
|
52586
|
-
|
|
52587
|
-
if (fullLine.length > 300) {
|
|
52588
|
-
fullLine = fullLine.slice(0, 300) + "...";
|
|
52589
|
-
}
|
|
52590
|
-
return fullLine;
|
|
52656
|
+
return filePart + rest;
|
|
52591
52657
|
}
|
|
52592
52658
|
return line;
|
|
52593
52659
|
});
|
|
52594
|
-
output =
|
|
52660
|
+
output = lines.join(`
|
|
52595
52661
|
`);
|
|
52596
52662
|
}
|
|
52597
|
-
|
|
52598
|
-
output = output.slice(0, 1e5) + `
|
|
52599
|
-
... (output truncated)`;
|
|
52600
|
-
return { result: output };
|
|
52663
|
+
return { result: truncateLines(output, { maxLines: head, maxLineLen: resolveOutputPolicy(outputMode).lineLimit }) };
|
|
52601
52664
|
}
|
|
52602
52665
|
if (code === 1)
|
|
52603
52666
|
return { result: "No matches found." };
|
|
@@ -52889,6 +52952,7 @@ class TODOforAIEdge {
|
|
|
52889
52952
|
connected = false;
|
|
52890
52953
|
edgeId = "";
|
|
52891
52954
|
userId = "";
|
|
52955
|
+
sessionToken = "";
|
|
52892
52956
|
debug;
|
|
52893
52957
|
maxTimeout;
|
|
52894
52958
|
wsUrl;
|
|
@@ -52899,6 +52963,8 @@ class TODOforAIEdge {
|
|
|
52899
52963
|
browserExtensionBridge;
|
|
52900
52964
|
stopping = false;
|
|
52901
52965
|
reconnectTimer;
|
|
52966
|
+
wakeReconnect;
|
|
52967
|
+
connectedAt = 0;
|
|
52902
52968
|
edgeConfig = {
|
|
52903
52969
|
id: "",
|
|
52904
52970
|
name: "Name uninitialized",
|
|
@@ -52914,7 +52980,7 @@ class TODOforAIEdge {
|
|
|
52914
52980
|
this.wsUrl = getWsUrl(this.api.apiUrl);
|
|
52915
52981
|
this.addWorkspacePath = config.addWorkspacePath;
|
|
52916
52982
|
this.browserExtensionBridge = new BrowserExtensionBridge(this.debug);
|
|
52917
|
-
setConnectionContext(() => ({ apiUrl: this.api.apiUrl,
|
|
52983
|
+
setConnectionContext(() => ({ apiUrl: this.api.apiUrl, sessionToken: this.sessionToken }));
|
|
52918
52984
|
}
|
|
52919
52985
|
get apiUrl() {
|
|
52920
52986
|
return this.api.apiUrl;
|
|
@@ -53124,6 +53190,13 @@ class TODOforAIEdge {
|
|
|
53124
53190
|
case S2E.EDGE_CONFIG_UPDATE:
|
|
53125
53191
|
run(async () => this.handleEdgeConfigUpdate(payload));
|
|
53126
53192
|
break;
|
|
53193
|
+
case S2E.SESSION_TOKEN:
|
|
53194
|
+
if (typeof payload.token === "string" && payload.token.startsWith("dst_")) {
|
|
53195
|
+
this.sessionToken = payload.token;
|
|
53196
|
+
if (this.debug)
|
|
53197
|
+
console.log(`[recv] session token (expires in ${payload.expiresIn}s)`);
|
|
53198
|
+
}
|
|
53199
|
+
break;
|
|
53127
53200
|
case FE.EDGE_CD:
|
|
53128
53201
|
run(() => handleCd(payload, send, this.edgeConfig, (u) => this.updateConfig(u)));
|
|
53129
53202
|
break;
|
|
@@ -53174,23 +53247,22 @@ class TODOforAIEdge {
|
|
|
53174
53247
|
console.log(`[warn] Unknown message type: ${msgType}`);
|
|
53175
53248
|
}
|
|
53176
53249
|
}
|
|
53177
|
-
startHeartbeat(onStale) {
|
|
53250
|
+
startHeartbeat(ws2, onStale) {
|
|
53178
53251
|
this.stopHeartbeat();
|
|
53179
53252
|
let pongReceived = true;
|
|
53180
|
-
|
|
53253
|
+
ws2.on("pong", () => {
|
|
53181
53254
|
pongReceived = true;
|
|
53182
53255
|
});
|
|
53183
53256
|
this.heartbeatTimer = setInterval(() => {
|
|
53184
53257
|
if (!pongReceived) {
|
|
53185
53258
|
console.log("[warn] No pong received, terminating stale connection");
|
|
53186
|
-
|
|
53187
|
-
this.ws?.terminate();
|
|
53259
|
+
ws2.terminate();
|
|
53188
53260
|
onStale();
|
|
53189
53261
|
return;
|
|
53190
53262
|
}
|
|
53191
53263
|
pongReceived = false;
|
|
53192
53264
|
try {
|
|
53193
|
-
|
|
53265
|
+
ws2.ping();
|
|
53194
53266
|
} catch {}
|
|
53195
53267
|
}, 30000);
|
|
53196
53268
|
}
|
|
@@ -53205,20 +53277,30 @@ class TODOforAIEdge {
|
|
|
53205
53277
|
const url = `${this.wsUrl}?fingerprint=${encodeURIComponent(this.fingerprint)}`;
|
|
53206
53278
|
if (this.debug)
|
|
53207
53279
|
console.log(`[info] Connecting to ${url}`);
|
|
53208
|
-
|
|
53280
|
+
const ws2 = new wrapper_default(url, [this.api.apiKey], {
|
|
53209
53281
|
maxPayload: 5 * 1024 * 1024,
|
|
53210
53282
|
rejectUnauthorized: false
|
|
53211
53283
|
});
|
|
53212
|
-
this.ws
|
|
53213
|
-
|
|
53214
|
-
|
|
53215
|
-
|
|
53284
|
+
this.ws = ws2;
|
|
53285
|
+
let settled = false;
|
|
53286
|
+
const settle = (fn2) => {
|
|
53287
|
+
if (settled)
|
|
53288
|
+
return;
|
|
53289
|
+
settled = true;
|
|
53290
|
+
if (this.ws === ws2) {
|
|
53291
|
+
this.stopHeartbeat();
|
|
53216
53292
|
this.connected = false;
|
|
53217
53293
|
this.ws = null;
|
|
53218
|
-
|
|
53219
|
-
|
|
53294
|
+
}
|
|
53295
|
+
fn2();
|
|
53296
|
+
};
|
|
53297
|
+
ws2.on("open", () => {
|
|
53298
|
+
this.connected = true;
|
|
53299
|
+
this.connectedAt = Date.now();
|
|
53300
|
+
console.log("[info] WebSocket connected");
|
|
53301
|
+
this.startHeartbeat(ws2, () => settle(() => resolve(0)));
|
|
53220
53302
|
});
|
|
53221
|
-
|
|
53303
|
+
ws2.on("message", (data, isBinary) => {
|
|
53222
53304
|
if (isBinary) {
|
|
53223
53305
|
const frame = data instanceof Buffer ? new Uint8Array(data) : new Uint8Array(data);
|
|
53224
53306
|
this.storeBinaryFrame(frame);
|
|
@@ -53226,36 +53308,30 @@ class TODOforAIEdge {
|
|
|
53226
53308
|
}
|
|
53227
53309
|
this.handleMessage(data.toString()).catch((e) => {
|
|
53228
53310
|
if (e instanceof AuthenticationError || e instanceof ServerError) {
|
|
53229
|
-
|
|
53230
|
-
reject(e);
|
|
53311
|
+
ws2.close();
|
|
53312
|
+
settle(() => reject(e));
|
|
53231
53313
|
} else {
|
|
53232
53314
|
console.error("[handler error]", e);
|
|
53233
53315
|
}
|
|
53234
53316
|
});
|
|
53235
53317
|
});
|
|
53236
|
-
|
|
53237
|
-
this.stopHeartbeat();
|
|
53238
|
-
this.connected = false;
|
|
53239
|
-
this.ws = null;
|
|
53318
|
+
ws2.on("close", (code, reason) => {
|
|
53240
53319
|
const reasonText = reason?.toString() || "<empty>";
|
|
53241
|
-
|
|
53242
|
-
console.log(`[info] WebSocket closed code=${code} clean=${clean} reason=${reasonText}`);
|
|
53320
|
+
console.log(`[info] WebSocket closed code=${code} clean=${code === 1000} reason=${reasonText}`);
|
|
53243
53321
|
if (code === 4001) {
|
|
53244
53322
|
console.log(`\x1B[33m[info] ${reasonText}. Not reconnecting.\x1B[0m`);
|
|
53245
53323
|
console.log(`\x1B[33m[info] To replace the existing connection, restart with: todoforai-edge --kill\x1B[0m`);
|
|
53246
|
-
reject(new ServerError(reasonText));
|
|
53324
|
+
settle(() => reject(new ServerError(reasonText)));
|
|
53247
53325
|
} else if (code === 4002) {
|
|
53248
53326
|
console.log(`\x1B[33m[info] ${reasonText}. This instance was replaced by a new connection.\x1B[0m`);
|
|
53249
|
-
reject(new ServerError(reasonText));
|
|
53327
|
+
settle(() => reject(new ServerError(reasonText)));
|
|
53250
53328
|
} else {
|
|
53251
|
-
resolve();
|
|
53329
|
+
settle(() => resolve(code));
|
|
53252
53330
|
}
|
|
53253
53331
|
});
|
|
53254
|
-
|
|
53255
|
-
|
|
53256
|
-
|
|
53257
|
-
this.ws = null;
|
|
53258
|
-
reject(err2);
|
|
53332
|
+
ws2.on("error", (err2) => {
|
|
53333
|
+
console.error(`[error] WebSocket error: ${err2.message}`);
|
|
53334
|
+
settle(() => resolve(0));
|
|
53259
53335
|
});
|
|
53260
53336
|
});
|
|
53261
53337
|
}
|
|
@@ -53265,45 +53341,34 @@ class TODOforAIEdge {
|
|
|
53265
53341
|
} catch {}
|
|
53266
53342
|
this.fingerprint = generateFingerprint();
|
|
53267
53343
|
console.log(`\x1B[36m\x1B[1m\uD83D\uDC46 Fingerprint:\x1B[0m ${this.fingerprint}`);
|
|
53268
|
-
const maxAttempts = 20;
|
|
53269
53344
|
let attempt = 0;
|
|
53270
|
-
while (
|
|
53271
|
-
console.log(`[info] Connecting (
|
|
53345
|
+
while (!this.stopping) {
|
|
53346
|
+
console.log(`[info] Connecting${attempt > 0 ? ` (retry ${attempt})` : ""}`);
|
|
53347
|
+
this.connectedAt = 0;
|
|
53272
53348
|
try {
|
|
53273
53349
|
await this.connect();
|
|
53274
|
-
if (this.stopping)
|
|
53275
|
-
break;
|
|
53276
|
-
attempt = 0;
|
|
53277
53350
|
} catch (e) {
|
|
53278
|
-
|
|
53279
|
-
|
|
53280
|
-
|
|
53281
|
-
}
|
|
53282
|
-
if (e instanceof ServerError) {
|
|
53283
|
-
console.error(`\x1B[31mServer error: ${e.message}\x1B[0m`);
|
|
53284
|
-
break;
|
|
53285
|
-
}
|
|
53286
|
-
attempt++;
|
|
53287
|
-
console.error(`[error] Connection error: ${e.message}`);
|
|
53288
|
-
} finally {
|
|
53289
|
-
this.connected = false;
|
|
53290
|
-
this.ws = null;
|
|
53291
|
-
}
|
|
53292
|
-
if (attempt > 0 && attempt < maxAttempts && !this.stopping) {
|
|
53293
|
-
const delay = Math.min(4 + attempt, 20);
|
|
53294
|
-
console.log(`[info] Reconnecting in ${delay}s...`);
|
|
53295
|
-
await new Promise((r) => {
|
|
53296
|
-
this.reconnectTimer = setTimeout(r, delay * 1000);
|
|
53297
|
-
});
|
|
53351
|
+
const label = e instanceof AuthenticationError ? "Authentication failed" : "Server error";
|
|
53352
|
+
console.error(`\x1B[31m${label}: ${e.message}\x1B[0m`);
|
|
53353
|
+
break;
|
|
53298
53354
|
}
|
|
53299
|
-
|
|
53300
|
-
|
|
53301
|
-
|
|
53355
|
+
if (this.stopping)
|
|
53356
|
+
break;
|
|
53357
|
+
if (this.connectedAt && Date.now() - this.connectedAt > 60000)
|
|
53358
|
+
attempt = 0;
|
|
53359
|
+
attempt++;
|
|
53360
|
+
const delay = Math.min(2 * 2 ** Math.min(attempt - 1, 4), 30);
|
|
53361
|
+
console.log(`[info] Reconnecting in ${delay}s...`);
|
|
53362
|
+
await new Promise((r) => {
|
|
53363
|
+
this.wakeReconnect = r;
|
|
53364
|
+
this.reconnectTimer = setTimeout(r, delay * 1000);
|
|
53365
|
+
});
|
|
53302
53366
|
}
|
|
53303
53367
|
}
|
|
53304
53368
|
stop() {
|
|
53305
53369
|
this.stopping = true;
|
|
53306
53370
|
clearTimeout(this.reconnectTimer);
|
|
53371
|
+
this.wakeReconnect?.();
|
|
53307
53372
|
this.stopHeartbeat();
|
|
53308
53373
|
this.browserExtensionBridge.stop();
|
|
53309
53374
|
this.frontendWs?.close();
|