@todoforai/edge 0.13.19 → 0.13.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +144 -163
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -48145,7 +48145,7 @@ class ApiClient {
|
|
|
48145
48145
|
}
|
|
48146
48146
|
async request(method, endpoint, body) {
|
|
48147
48147
|
const url = `${this.apiUrl}${endpoint}`;
|
|
48148
|
-
const opts = { method, headers: this.headers };
|
|
48148
|
+
const opts = { method, headers: this.headers, signal: AbortSignal.timeout(30000) };
|
|
48149
48149
|
if (body)
|
|
48150
48150
|
opts.body = JSON.stringify(body);
|
|
48151
48151
|
const res = await fetch(url, opts);
|
|
@@ -48772,7 +48772,6 @@ var tool_catalog_default = {
|
|
|
48772
48772
|
"~/.zele/sqlite.db"
|
|
48773
48773
|
],
|
|
48774
48774
|
capabilities: "Read inbox, search/send/reply email, email addresses — multi-account Gmail, OAuth browser login for setup.",
|
|
48775
|
-
description: "Use for anything Gmail: reading the user's inbox, searching mail, sending or replying to email. Multi-account, OAuth login via `zele login`.",
|
|
48776
48775
|
versionCmd: "zele --version 2>/dev/null | head -1"
|
|
48777
48776
|
},
|
|
48778
48777
|
xurl: {
|
|
@@ -48786,9 +48785,23 @@ var tool_catalog_default = {
|
|
|
48786
48785
|
"~/.xurl"
|
|
48787
48786
|
],
|
|
48788
48787
|
capabilities: "Post tweets, reply & quote, read timelines, search tweets, like & repost, follow/unfollow, DMs, media uploads",
|
|
48789
|
-
description: "
|
|
48788
|
+
description: "Authenticated raw X API access via `xurl <method> <path>`; login `xurl auth oauth2`.",
|
|
48790
48789
|
versionCmd: "xurl --version 2>/dev/null | head -1"
|
|
48791
48790
|
},
|
|
48791
|
+
"linkedin-api": {
|
|
48792
|
+
category: "social",
|
|
48793
|
+
pkg: "@todoforai/linkedin-api",
|
|
48794
|
+
installer: "npm",
|
|
48795
|
+
label: "LinkedIn",
|
|
48796
|
+
statusCmd: `linkedin-api whoami 2>/dev/null | grep -oP '"publicId":\\s*"\\K[^"]+' | head -1`,
|
|
48797
|
+
loginCmd: "linkedin-api auth",
|
|
48798
|
+
credentialPaths: [
|
|
48799
|
+
"~/.config/linkedin-api/credentials.json"
|
|
48800
|
+
],
|
|
48801
|
+
capabilities: "Read profiles, list & read conversations, send DMs, send connection requests — raw LinkedIn Voyager API via cookie auth",
|
|
48802
|
+
description: "Authenticated LinkedIn Voyager API CLI. Cookie auth: `linkedin-api auth <li_at> <jsessionid>` (from browser DevTools).",
|
|
48803
|
+
versionCmd: "linkedin-api --version 2>/dev/null | head -1"
|
|
48804
|
+
},
|
|
48792
48805
|
"tiktok-uploader": {
|
|
48793
48806
|
category: "social",
|
|
48794
48807
|
pkg: "tiktok-uploader",
|
|
@@ -48799,7 +48812,6 @@ var tool_catalog_default = {
|
|
|
48799
48812
|
"~/.tiktok/cookies.txt"
|
|
48800
48813
|
],
|
|
48801
48814
|
capabilities: "Upload videos, batch uploads, schedule posts, custom covers, hashtags & mentions",
|
|
48802
|
-
description: "Upload videos to TikTok, schedule posts, set covers and hashtags.",
|
|
48803
48815
|
versionCmd: "pip show tiktok-uploader 2>/dev/null | grep -oP 'Version: \\K.*'"
|
|
48804
48816
|
},
|
|
48805
48817
|
instagrapi: {
|
|
@@ -48808,7 +48820,6 @@ var tool_catalog_default = {
|
|
|
48808
48820
|
installer: "pip",
|
|
48809
48821
|
label: "Instagram",
|
|
48810
48822
|
capabilities: "Upload photos & reels, post stories, send DMs, like & comment, manage followers",
|
|
48811
|
-
description: "Instagram automation: upload photos/reels, stories, DMs, likes, follower management.",
|
|
48812
48823
|
versionCmd: "pip show instagrapi 2>/dev/null | grep -oP 'Version: \\K.*'"
|
|
48813
48824
|
},
|
|
48814
48825
|
mudslide: {
|
|
@@ -48822,7 +48833,6 @@ var tool_catalog_default = {
|
|
|
48822
48833
|
"~/.local/share/mudslide"
|
|
48823
48834
|
],
|
|
48824
48835
|
capabilities: "Send messages, send images & files, send locations & polls, group management, QR code login",
|
|
48825
|
-
description: "Use to send WhatsApp messages/files/media/locations/polls from CLI. QR-code login via `mudslide login`.",
|
|
48826
48836
|
versionCmd: "mudslide --version 2>/dev/null | head -1"
|
|
48827
48837
|
},
|
|
48828
48838
|
slack: {
|
|
@@ -48836,7 +48846,7 @@ var tool_catalog_default = {
|
|
|
48836
48846
|
"~/.slack/credentials.json"
|
|
48837
48847
|
],
|
|
48838
48848
|
capabilities: "Build & deploy Slack apps, manage workspaces & triggers, run datastore queries, tail logs — official Slack CLI.",
|
|
48839
|
-
description: "
|
|
48849
|
+
description: "`slack-cli login` to auth. Invoked as `slack-cli` to avoid colliding with the Slack desktop app's `slack` binary.",
|
|
48840
48850
|
versionCmd: "slack-cli version 2>/dev/null | head -1",
|
|
48841
48851
|
binName: "slack-cli",
|
|
48842
48852
|
binary: {
|
|
@@ -48877,18 +48887,8 @@ var tool_catalog_default = {
|
|
|
48877
48887
|
"~/.config/telegram-send/telegram-send.conf"
|
|
48878
48888
|
],
|
|
48879
48889
|
capabilities: "Send messages, send files & images, send video & audio, Markdown/HTML formatting, channel & group support",
|
|
48880
|
-
description: "Send Telegram messages and files from CLI via a bot.",
|
|
48881
48890
|
versionCmd: "telegram-send --version 2>/dev/null | head -1"
|
|
48882
48891
|
},
|
|
48883
|
-
"apollo-api": {
|
|
48884
|
-
category: "marketing",
|
|
48885
|
-
pkg: "@todoforai/apollo-api",
|
|
48886
|
-
installer: "npm",
|
|
48887
|
-
label: "Apollo",
|
|
48888
|
-
capabilities: "Lead search & enrichment, email sequences, CRM sync",
|
|
48889
|
-
description: "Use for B2B prospecting: search Apollo's people/company DB, enrich leads with emails/titles/companies, manage sequences and CRM data. Needs APOLLO_API_KEY.",
|
|
48890
|
-
versionCmd: "apollo-api --version 2>/dev/null | head -1"
|
|
48891
|
-
},
|
|
48892
48892
|
"meta-ads": {
|
|
48893
48893
|
category: "marketing",
|
|
48894
48894
|
pkg: "meta-ads",
|
|
@@ -48896,7 +48896,6 @@ var tool_catalog_default = {
|
|
|
48896
48896
|
label: "Meta Ads",
|
|
48897
48897
|
statusCmd: "meta auth status 2>&1",
|
|
48898
48898
|
capabilities: "Meta/Facebook & Instagram ad campaigns: campaigns/adsets/ads CRUD, insights & reporting, catalog/product-feed/product-set/product-item, ad creatives, datasets (pixels), pages — Marketing API via the official `meta` CLI.",
|
|
48899
|
-
description: "Manage Meta/Facebook & Instagram ad campaigns, insights, creatives, and catalogs.",
|
|
48900
48899
|
versionCmd: "meta --version 2>/dev/null | head -1"
|
|
48901
48900
|
},
|
|
48902
48901
|
"elevenlabs-api": {
|
|
@@ -48904,8 +48903,7 @@ var tool_catalog_default = {
|
|
|
48904
48903
|
pkg: "@todoforai/elevenlabs-api",
|
|
48905
48904
|
installer: "npm",
|
|
48906
48905
|
label: "ElevenLabs",
|
|
48907
|
-
capabilities: "Text-to-speech, voice cloning, multiple languages",
|
|
48908
|
-
description: "Use to generate speech audio from text, clone voices, or list voices. Needs ELEVENLABS_API_KEY.",
|
|
48906
|
+
capabilities: "Text-to-speech, voice cloning, multiple languages. Needs ELEVENLABS_API_KEY.",
|
|
48909
48907
|
versionCmd: "elevenlabs-api --version 2>/dev/null | head -1"
|
|
48910
48908
|
},
|
|
48911
48909
|
"codex-imagegen-api": {
|
|
@@ -48914,7 +48912,6 @@ var tool_catalog_default = {
|
|
|
48914
48912
|
installer: "npm",
|
|
48915
48913
|
label: "Image Gen",
|
|
48916
48914
|
capabilities: "AI image generation & editing via TODOFORAI backend (gpt-image)",
|
|
48917
|
-
description: "Generate or edit images with AI.",
|
|
48918
48915
|
versionCmd: "codex-imagegen-api --version 2>/dev/null | head -1"
|
|
48919
48916
|
},
|
|
48920
48917
|
"suno-api": {
|
|
@@ -48922,8 +48919,7 @@ var tool_catalog_default = {
|
|
|
48922
48919
|
pkg: "@todoforai/suno-api",
|
|
48923
48920
|
installer: "npm",
|
|
48924
48921
|
label: "Suno",
|
|
48925
|
-
capabilities: "AI music generation, custom lyrics, multiple genres",
|
|
48926
|
-
description: "Use to generate music/songs from a prompt or custom lyrics. Needs SUNO_API_KEY.",
|
|
48922
|
+
capabilities: "AI music generation, custom lyrics, multiple genres. Needs SUNO_API_KEY.",
|
|
48927
48923
|
versionCmd: "suno-api --version 2>/dev/null | head -1"
|
|
48928
48924
|
},
|
|
48929
48925
|
ntn: {
|
|
@@ -48937,7 +48933,6 @@ var tool_catalog_default = {
|
|
|
48937
48933
|
"~/.config/notion"
|
|
48938
48934
|
],
|
|
48939
48935
|
capabilities: "Official Notion CLI: workspace-wide OAuth login, raw API calls (`ntn api <path>`), page/datasource management, file uploads, Workers deploy.",
|
|
48940
|
-
description: "Read, create, and update Notion pages and databases.",
|
|
48941
48936
|
versionCmd: "ntn --version 2>/dev/null | head -1"
|
|
48942
48937
|
},
|
|
48943
48938
|
"perplexity-api": {
|
|
@@ -48946,7 +48941,6 @@ var tool_catalog_default = {
|
|
|
48946
48941
|
installer: "npm",
|
|
48947
48942
|
label: "Perplexity",
|
|
48948
48943
|
capabilities: "AI-powered web search, chat completions, async chat, embeddings and agent runs",
|
|
48949
|
-
description: "Web-grounded AI search with citations. Use when you need fresh information beyond training cutoff.",
|
|
48950
48944
|
versionCmd: "perplexity-api --version 2>/dev/null | head -1"
|
|
48951
48945
|
},
|
|
48952
48946
|
gh: {
|
|
@@ -48961,7 +48955,7 @@ var tool_catalog_default = {
|
|
|
48961
48955
|
"~/.config/gh/hosts.yml"
|
|
48962
48956
|
],
|
|
48963
48957
|
capabilities: "Repos, PRs & issues, actions & releases, gists & SSH keys",
|
|
48964
|
-
description: "
|
|
48958
|
+
description: "Prefer `gh` over raw GitHub API calls.",
|
|
48965
48959
|
versionCmd: "gh --version 2>/dev/null | head -1"
|
|
48966
48960
|
},
|
|
48967
48961
|
glab: {
|
|
@@ -48975,7 +48969,7 @@ var tool_catalog_default = {
|
|
|
48975
48969
|
"~/.config/glab-cli/config.yml"
|
|
48976
48970
|
],
|
|
48977
48971
|
capabilities: "Repos, MRs & issues, CI/CD pipelines, releases",
|
|
48978
|
-
description: "
|
|
48972
|
+
description: "GitLab equivalent of `gh`.",
|
|
48979
48973
|
versionCmd: "glab --version 2>/dev/null | head -1"
|
|
48980
48974
|
},
|
|
48981
48975
|
vercel: {
|
|
@@ -48989,7 +48983,7 @@ var tool_catalog_default = {
|
|
|
48989
48983
|
"~/.local/share/com.vercel.cli/auth.json"
|
|
48990
48984
|
],
|
|
48991
48985
|
capabilities: "Deploy & preview, environment variables, domain management",
|
|
48992
|
-
description: "
|
|
48986
|
+
description: "`vercel deploy`; manage env vars, domains, preview URLs.",
|
|
48993
48987
|
versionCmd: "vercel --version 2>/dev/null | head -1"
|
|
48994
48988
|
},
|
|
48995
48989
|
netlify: {
|
|
@@ -49003,7 +48997,7 @@ var tool_catalog_default = {
|
|
|
49003
48997
|
"~/.netlify/config.json"
|
|
49004
48998
|
],
|
|
49005
48999
|
capabilities: "Deploy & preview, serverless functions, forms & identity",
|
|
49006
|
-
description: "
|
|
49000
|
+
description: "`netlify deploy --prod` for production; manage env vars, forms, identity.",
|
|
49007
49001
|
versionCmd: "netlify --version 2>/dev/null | head -1"
|
|
49008
49002
|
},
|
|
49009
49003
|
firebase: {
|
|
@@ -49017,7 +49011,7 @@ var tool_catalog_default = {
|
|
|
49017
49011
|
"~/.config/configstore/firebase-tools.json"
|
|
49018
49012
|
],
|
|
49019
49013
|
capabilities: "Hosting & deploy, Firestore & Realtime DB, auth & functions",
|
|
49020
|
-
description: "
|
|
49014
|
+
description: "Deploy hosting/functions, manage Firestore/RTDB rules and data, auth users, emulators.",
|
|
49021
49015
|
versionCmd: "firebase --version 2>/dev/null | head -1"
|
|
49022
49016
|
},
|
|
49023
49017
|
wrangler: {
|
|
@@ -49031,7 +49025,7 @@ var tool_catalog_default = {
|
|
|
49031
49025
|
"~/.config/.wrangler/config/default.toml"
|
|
49032
49026
|
],
|
|
49033
49027
|
capabilities: "Workers & Pages deploy, KV & D1 storage, R2 & Queues",
|
|
49034
|
-
description: "
|
|
49028
|
+
description: "Tail logs with `wrangler tail`. Use `cloudflared` for tunnels.",
|
|
49035
49029
|
versionCmd: "wrangler --version 2>/dev/null | head -1"
|
|
49036
49030
|
},
|
|
49037
49031
|
stripe: {
|
|
@@ -49045,7 +49039,7 @@ var tool_catalog_default = {
|
|
|
49045
49039
|
"~/.config/stripe/config.toml"
|
|
49046
49040
|
],
|
|
49047
49041
|
capabilities: "Payments & subscriptions, webhook testing, invoice management",
|
|
49048
|
-
description: "
|
|
49042
|
+
description: "`stripe listen` forwards webhooks locally; `stripe get/post /v1/...` for raw API.",
|
|
49049
49043
|
versionCmd: "stripe --version 2>/dev/null | head -1"
|
|
49050
49044
|
},
|
|
49051
49045
|
flyctl: {
|
|
@@ -49059,7 +49053,7 @@ var tool_catalog_default = {
|
|
|
49059
49053
|
"~/.fly/config.yml"
|
|
49060
49054
|
],
|
|
49061
49055
|
capabilities: "Deploy containers globally, Postgres & volumes, auto-scaling",
|
|
49062
|
-
description: "
|
|
49056
|
+
description: "`flyctl deploy` from app dir; manage Postgres/volumes/machines/secrets.",
|
|
49063
49057
|
versionCmd: "flyctl version 2>/dev/null | head -1"
|
|
49064
49058
|
},
|
|
49065
49059
|
supabase: {
|
|
@@ -49073,7 +49067,7 @@ var tool_catalog_default = {
|
|
|
49073
49067
|
"~/.config/supabase/access-token"
|
|
49074
49068
|
],
|
|
49075
49069
|
capabilities: "Postgres database, auth & storage, edge functions",
|
|
49076
|
-
description: "
|
|
49070
|
+
description: "Link to cloud project; runs/migrates local Postgres.",
|
|
49077
49071
|
versionCmd: "supabase --version 2>/dev/null | head -1"
|
|
49078
49072
|
},
|
|
49079
49073
|
railway: {
|
|
@@ -49087,7 +49081,7 @@ var tool_catalog_default = {
|
|
|
49087
49081
|
"~/.railway/config.json"
|
|
49088
49082
|
],
|
|
49089
49083
|
capabilities: "Deploy apps & databases, environment management, auto-scaling",
|
|
49090
|
-
description: "
|
|
49084
|
+
description: "`railway up` to deploy current dir.",
|
|
49091
49085
|
versionCmd: "railway --version 2>/dev/null | head -1"
|
|
49092
49086
|
},
|
|
49093
49087
|
shopify: {
|
|
@@ -49100,16 +49094,23 @@ var tool_catalog_default = {
|
|
|
49100
49094
|
"~/.config/shopify/config.json"
|
|
49101
49095
|
],
|
|
49102
49096
|
capabilities: "Theme development, app scaffolding, store management",
|
|
49103
|
-
description: "Use for Shopify dev: scaffold/develop themes and apps, push/pull themes, run dev server against a store.",
|
|
49104
49097
|
versionCmd: "shopify version 2>/dev/null | head -1"
|
|
49105
49098
|
},
|
|
49099
|
+
"shop-app": {
|
|
49100
|
+
category: "ecommerce",
|
|
49101
|
+
pkg: "@todoforai/shop-app",
|
|
49102
|
+
installer: "npm",
|
|
49103
|
+
label: "Shop",
|
|
49104
|
+
capabilities: "Product search & price comparison across Shopify stores",
|
|
49105
|
+
description: "Shop.app product search across Shopify stores (no auth). `shop-app search '<query>' --ships-to HU -n 10` returns products with price, rating, URL, variants. `shop-app similar <variantId>` for lookalikes.",
|
|
49106
|
+
versionCmd: "shop-app --version 2>/dev/null | head -1"
|
|
49107
|
+
},
|
|
49106
49108
|
"datadog-ci": {
|
|
49107
49109
|
category: "monitoring",
|
|
49108
49110
|
pkg: "@datadog/datadog-ci",
|
|
49109
49111
|
installer: "npm",
|
|
49110
49112
|
label: "Datadog",
|
|
49111
|
-
capabilities: "CI test visibility, sourcemap uploads, deployment tracking",
|
|
49112
|
-
description: "Use in CI to upload test results/sourcemaps/coverage to Datadog and mark deployments. Needs DATADOG_API_KEY.",
|
|
49113
|
+
capabilities: "CI test visibility, sourcemap uploads, deployment tracking. Needs DATADOG_API_KEY.",
|
|
49113
49114
|
versionCmd: "datadog-ci version 2>/dev/null | head -1"
|
|
49114
49115
|
},
|
|
49115
49116
|
"sentry-cli": {
|
|
@@ -49123,7 +49124,7 @@ var tool_catalog_default = {
|
|
|
49123
49124
|
"~/.sentryclirc"
|
|
49124
49125
|
],
|
|
49125
49126
|
capabilities: "Release management, sourcemap uploads, error monitoring",
|
|
49126
|
-
description: "
|
|
49127
|
+
description: "`sentry-cli releases new <version>`.",
|
|
49127
49128
|
versionCmd: "sentry-cli --version 2>/dev/null | head -1"
|
|
49128
49129
|
},
|
|
49129
49130
|
todoai: {
|
|
@@ -49132,7 +49133,7 @@ var tool_catalog_default = {
|
|
|
49132
49133
|
installer: "bun",
|
|
49133
49134
|
label: "TODOforAI",
|
|
49134
49135
|
capabilities: "Create/list/inspect/update TODOs, run templates & workflows, platform API access",
|
|
49135
|
-
description:
|
|
49136
|
+
description: '`todoai list` (`--status open`) to browse, `todoai "prompt"` to create, `--inspect <id>` to read a chat log, `status/addmessage/delete` to manage, `--template` for registry workflows.',
|
|
49136
49137
|
installCmd: "bun add -g @todoforai/cli",
|
|
49137
49138
|
versionCmd: "todoai --version 2>/dev/null | head -1",
|
|
49138
49139
|
internal: true
|
|
@@ -49143,7 +49144,7 @@ var tool_catalog_default = {
|
|
|
49143
49144
|
installer: "npm",
|
|
49144
49145
|
label: "Postman",
|
|
49145
49146
|
capabilities: "Run API test collections, CI/CD integration, HTML reports",
|
|
49146
|
-
description: "
|
|
49147
|
+
description: "`newman run <collection.json> -e <env.json>`.",
|
|
49147
49148
|
versionCmd: "newman --version 2>/dev/null | head -1"
|
|
49148
49149
|
},
|
|
49149
49150
|
curl: {
|
|
@@ -49164,22 +49165,20 @@ var tool_catalog_default = {
|
|
|
49164
49165
|
installer: "binary",
|
|
49165
49166
|
label: "Tunnel",
|
|
49166
49167
|
capabilities: "Tunnel to localhost, expose local services, zero-trust access",
|
|
49167
|
-
description: "
|
|
49168
|
+
description: "`cloudflared tunnel --url http://localhost:<port>` prints a public https URL.",
|
|
49168
49169
|
versionCmd: "cloudflared --version 2>/dev/null | head -1"
|
|
49169
49170
|
},
|
|
49170
49171
|
vault: {
|
|
49171
49172
|
category: "security",
|
|
49172
49173
|
pkg: "vault",
|
|
49173
49174
|
installer: "binary",
|
|
49174
|
-
preinstallCloud: true,
|
|
49175
49175
|
label: "HashiCorp Vault",
|
|
49176
49176
|
statusCmd: "vault token lookup >/dev/null 2>&1 && echo authenticated",
|
|
49177
49177
|
loginCmd: "vault login",
|
|
49178
49178
|
credentialPaths: [
|
|
49179
49179
|
"~/.vault-token"
|
|
49180
49180
|
],
|
|
49181
|
-
capabilities: "Secrets management, dynamic credentials",
|
|
49182
|
-
description: "HashiCorp Vault CLI — only for Terraform/vals/ecosystem integrations that expect it. For all credential read/write tasks use `tfa-vault` instead. Needs VAULT_ADDR + VAULT_TOKEN.",
|
|
49181
|
+
capabilities: "Secrets management, dynamic credentials. Needs VAULT_ADDR + VAULT_TOKEN; for credential read/write use `tfa-vault` instead.",
|
|
49183
49182
|
versionCmd: "vault --version 2>/dev/null | head -1"
|
|
49184
49183
|
},
|
|
49185
49184
|
rclone: {
|
|
@@ -49194,7 +49193,6 @@ var tool_catalog_default = {
|
|
|
49194
49193
|
"~/.config/rclone/rclone.conf"
|
|
49195
49194
|
],
|
|
49196
49195
|
capabilities: "Access Google Drive, OneDrive, Dropbox, S3 and 40+ cloud providers. List, copy, sync, move, mount files as virtual filesystem (FUSE). Use 'rclone config create <name> <provider>' to connect (opens browser for OAuth).",
|
|
49197
|
-
description: "Access cloud storage (Google Drive, OneDrive, Dropbox, S3, 40+ providers). List, copy, sync, or mount files.",
|
|
49198
49196
|
subProviders: {
|
|
49199
49197
|
gdrive: {
|
|
49200
49198
|
label: "Google Drive",
|
|
@@ -49239,8 +49237,7 @@ var tool_catalog_default = {
|
|
|
49239
49237
|
pkg: "pymupdf",
|
|
49240
49238
|
installer: "pip",
|
|
49241
49239
|
label: "PDF",
|
|
49242
|
-
capabilities: "PDF text editing, extraction, merge, split",
|
|
49243
|
-
description: "Read, edit, merge, or split PDFs programmatically. Call via `python3 -c`.",
|
|
49240
|
+
capabilities: "PDF text editing, extraction, merge, split. Call via `python3 -c`.",
|
|
49244
49241
|
versionCmd: "python3 -c 'import pymupdf; print(pymupdf.__version__)' 2>/dev/null",
|
|
49245
49242
|
preinstallCloud: true
|
|
49246
49243
|
},
|
|
@@ -49251,16 +49248,16 @@ var tool_catalog_default = {
|
|
|
49251
49248
|
preinstallCloud: true,
|
|
49252
49249
|
label: "Browser",
|
|
49253
49250
|
capabilities: "Headless browser automation, web scraping, accessibility tree snapshots, screenshots, PDF generation",
|
|
49254
|
-
description: "
|
|
49251
|
+
description: "Default browser. On a PC with a display prefer a visible window the user can watch/interact with (needed for CAPTCHA/MFA/login): launch a separate Chrome with `google-chrome --remote-debugging-port=9222 --user-data-dir=$HOME/.config/google-chrome-cdp >/tmp/chrome-cdp.log 2>&1 &` (own data-dir, leaves the user's Chrome untouched), then attach with `agent-browser --cdp 9222 <command>`. Use headless only on the cloud or when no display is available.",
|
|
49255
49252
|
versionCmd: "agent-browser --version 2>/dev/null | head -1"
|
|
49256
49253
|
},
|
|
49257
49254
|
"todoforai-browser": {
|
|
49258
49255
|
category: "development",
|
|
49259
49256
|
pkg: "@todoforai/browser",
|
|
49260
49257
|
installer: "npm",
|
|
49258
|
+
preinstallCloud: true,
|
|
49261
49259
|
label: "Browser (Extension)",
|
|
49262
49260
|
capabilities: "Browser automation via extension (non-headless), web scraping, accessibility tree snapshots, screenshots, PDF generation",
|
|
49263
|
-
description: "Drives the user's real browser via the extension. Use when the task needs the user's actual logged-in session, cookies, or MFA.",
|
|
49264
49261
|
versionCmd: `node -p "require(require('child_process').execSync('which todoforai-browser',{encoding:'utf8'}).trim().replace(/\\/dist\\/index\\.js$/, '/package.json')).version" 2>/dev/null`,
|
|
49265
49262
|
internal: true
|
|
49266
49263
|
},
|
|
@@ -49270,55 +49267,19 @@ var tool_catalog_default = {
|
|
|
49270
49267
|
installer: "npm",
|
|
49271
49268
|
label: "Sub-agent",
|
|
49272
49269
|
capabilities: "Spawn a TODO for AI sub-agent (FluidAgent) from the CLI; pipe stdin in, get an answer out",
|
|
49273
|
-
description: "Spawn a sub-agent for a focused task. Pipe context via stdin.",
|
|
49274
49270
|
versionCmd: "todoforai-subagent --version 2>/dev/null | head -1",
|
|
49275
49271
|
installCmd: "bun add -g @todoforai/subagent",
|
|
49276
49272
|
internal: true
|
|
49277
49273
|
},
|
|
49278
|
-
"
|
|
49274
|
+
"tfa-handoff": {
|
|
49279
49275
|
category: "development",
|
|
49280
|
-
pkg: "@todoforai/
|
|
49276
|
+
pkg: "@todoforai/tfa-handoff",
|
|
49281
49277
|
installer: "npm",
|
|
49282
|
-
label: "
|
|
49283
|
-
capabilities: "
|
|
49284
|
-
description: "
|
|
49285
|
-
versionCmd: "
|
|
49286
|
-
installCmd: "bun add -g @todoforai/
|
|
49287
|
-
internal: true
|
|
49288
|
-
},
|
|
49289
|
-
"tfa-explore": {
|
|
49290
|
-
category: "development",
|
|
49291
|
-
pkg: "@todoforai/tfa-explore",
|
|
49292
|
-
installer: "npm",
|
|
49293
|
-
label: "Explore",
|
|
49294
|
-
capabilities: "Explore a codebase as a real TODO: read-only agent maps structure, surfaces relevant files, streams findings to terminal",
|
|
49295
|
-
description: "Explore a codebase with a read-only sub-agent. Streams findings live.",
|
|
49296
|
-
versionCmd: "tfa-explore --version 2>/dev/null | head -1",
|
|
49297
|
-
installCmd: "bun add -g @todoforai/tfa-explore",
|
|
49298
|
-
preinstallCloud: true,
|
|
49299
|
-
internal: true
|
|
49300
|
-
},
|
|
49301
|
-
"tfa-review": {
|
|
49302
|
-
category: "development",
|
|
49303
|
-
pkg: "@todoforai/tfa-review",
|
|
49304
|
-
installer: "npm",
|
|
49305
|
-
label: "Review",
|
|
49306
|
-
capabilities: "Review a git diff as a real TODO: read-only agent assesses goal, finds issues, suggests simpler approaches",
|
|
49307
|
-
description: "Review a git diff with a read-only sub-agent.",
|
|
49308
|
-
versionCmd: "tfa-review --version 2>/dev/null | head -1",
|
|
49309
|
-
installCmd: "bun add -g @todoforai/tfa-review",
|
|
49310
|
-
preinstallCloud: true,
|
|
49311
|
-
internal: true
|
|
49312
|
-
},
|
|
49313
|
-
"tfa-summary": {
|
|
49314
|
-
category: "development",
|
|
49315
|
-
pkg: "@todoforai/tfa-summary",
|
|
49316
|
-
installer: "npm",
|
|
49317
|
-
label: "Summarize",
|
|
49318
|
-
capabilities: "Summarize files or piped input as a real TODO with no-tools sub-agent",
|
|
49319
|
-
description: "Summarize files or piped content as a visible TODO.",
|
|
49320
|
-
versionCmd: "tfa-summary --version 2>/dev/null | head -1",
|
|
49321
|
-
installCmd: "bun add -g @todoforai/tfa-summary",
|
|
49278
|
+
label: "Handoff",
|
|
49279
|
+
capabilities: "Compact a conversation into a handoff document as a hidden TODO; pipe filter for `todoai --inspect … | tfa-handoff … | todoai`",
|
|
49280
|
+
description: "Compact a conversation into a handoff document for a fresh agent.",
|
|
49281
|
+
versionCmd: "tfa-handoff --version 2>/dev/null | head -1",
|
|
49282
|
+
installCmd: "bun add -g @todoforai/tfa-handoff",
|
|
49322
49283
|
internal: true
|
|
49323
49284
|
},
|
|
49324
49285
|
"tfa-vault": {
|
|
@@ -49326,8 +49287,8 @@ var tool_catalog_default = {
|
|
|
49326
49287
|
pkg: "@todoforai/vault",
|
|
49327
49288
|
installer: "npm",
|
|
49328
49289
|
label: "Vault",
|
|
49329
|
-
capabilities: "
|
|
49330
|
-
description: "
|
|
49290
|
+
capabilities: "The user's dedicated password manager and secure secret store — a zero-config TODOforAI vault CLI: put/get/patch/list/rm passwords & secrets in the user's KV v2 vault. Reuses bridge credentials (TODOFORAI_API_KEY / ~/.config/todoforai/credentials.json); derives vault URL from backend URL.",
|
|
49291
|
+
description: "This is the user's own dedicated password manager and secure secret store (encrypted KV vault), and the `whoami` transcript shows you're authorized to it, so reading and using the secrets it holds is the intended workflow. Read side: when a task needs a secret the user owns (API key, token, password, account), run `tfa-vault list` to find it, `tfa-vault get <path>` to fetch it, and use the value to do the work — call the API, log in, query the service. There's no need to ask the user for a value the vault already holds. Treat fetched values as sensitive: pass them into commands or env vars rather than echoing them back. Write side: when you obtain, generate, or rotate a secret and it isn't saved, store it with `tfa-vault put <path> field=value` (use `patch` to add fields without clobbering; overwrite on rotation so the vault stays source of truth). Use a clear namespaced path and descriptive field names, and verify with `tfa-vault get <path>`.",
|
|
49331
49292
|
versionCmd: "tfa-vault --version 2>/dev/null | head -1",
|
|
49332
49293
|
statusCmd: "tfa-vault whoami",
|
|
49333
49294
|
installCmd: "bun add -g @todoforai/vault",
|
|
@@ -51610,9 +51571,10 @@ var STREAM_FIRST = 1e4;
|
|
|
51610
51571
|
var STREAM_LAST = 1e4;
|
|
51611
51572
|
var RUN_OUTPUT_CAP = 256 * 1024;
|
|
51612
51573
|
var OUTPUT_POLICIES = {
|
|
51613
|
-
safe: { firstLimit: STREAM_FIRST, lastLimit: STREAM_LAST, hardCap: STREAM_FIRST + STREAM_LAST },
|
|
51614
|
-
|
|
51615
|
-
|
|
51574
|
+
safe: { firstLimit: STREAM_FIRST, lastLimit: STREAM_LAST, hardCap: STREAM_FIRST + STREAM_LAST, lineLimit: MAX_LINE_LEN },
|
|
51575
|
+
wide: { firstLimit: STREAM_FIRST, lastLimit: STREAM_LAST, hardCap: STREAM_FIRST + STREAM_LAST, lineLimit: Infinity },
|
|
51576
|
+
full: { firstLimit: Infinity, lastLimit: 0, hardCap: RUN_OUTPUT_CAP, lineLimit: MAX_LINE_LEN },
|
|
51577
|
+
raw: { firstLimit: Infinity, lastLimit: 0, hardCap: Infinity, lineLimit: Infinity }
|
|
51616
51578
|
};
|
|
51617
51579
|
var DEFAULT_OUTPUT_MODE = "safe";
|
|
51618
51580
|
function resolveOutputPolicy(mode) {
|
|
@@ -51635,6 +51597,13 @@ function truncateLines(text, { maxLines = MAX_RESULT_LINES, maxLineLen = MAX_LIN
|
|
|
51635
51597
|
... (output truncated)`;
|
|
51636
51598
|
return output;
|
|
51637
51599
|
}
|
|
51600
|
+
function capLineWidth(text, lineLimit) {
|
|
51601
|
+
if (!isFinite(lineLimit))
|
|
51602
|
+
return text;
|
|
51603
|
+
return text.split(`
|
|
51604
|
+
`).map((line) => line.length > lineLimit ? line.slice(0, lineLimit) + ` ...[+${line.length - lineLimit} chars]` : line).join(`
|
|
51605
|
+
`);
|
|
51606
|
+
}
|
|
51638
51607
|
function formatTruncationNotice(totalLen, firstLimit, lastPart) {
|
|
51639
51608
|
const dropped = totalLen - firstLimit - lastPart.length;
|
|
51640
51609
|
return `
|
|
@@ -51697,9 +51666,11 @@ class OutputBuffer {
|
|
|
51697
51666
|
truncMsgSent = false;
|
|
51698
51667
|
headLimit;
|
|
51699
51668
|
lastLimit;
|
|
51669
|
+
lineLimit;
|
|
51700
51670
|
constructor(policy = OUTPUT_POLICIES[DEFAULT_OUTPUT_MODE]) {
|
|
51701
51671
|
this.headLimit = Math.min(policy.firstLimit, policy.hardCap);
|
|
51702
51672
|
this.lastLimit = Math.min(policy.lastLimit, policy.hardCap - this.headLimit);
|
|
51673
|
+
this.lineLimit = policy.lineLimit;
|
|
51703
51674
|
}
|
|
51704
51675
|
append(text) {
|
|
51705
51676
|
this.totalLen += text.length;
|
|
@@ -51733,15 +51704,15 @@ class OutputBuffer {
|
|
|
51733
51704
|
}
|
|
51734
51705
|
getOutput() {
|
|
51735
51706
|
if (!this.truncated)
|
|
51736
|
-
return this.firstPart;
|
|
51737
|
-
return this.firstPart + `
|
|
51707
|
+
return capLineWidth(this.firstPart, this.lineLimit);
|
|
51708
|
+
return capLineWidth(this.firstPart, this.lineLimit) + `
|
|
51738
51709
|
|
|
51739
51710
|
... [truncated: showing first ${this.firstPart.length} and last ${this.lastPart.length} chars of ${this.totalLen} total] ...
|
|
51740
51711
|
|
|
51741
|
-
${this.lastPart}`;
|
|
51712
|
+
${capLineWidth(this.lastPart, this.lineLimit)}`;
|
|
51742
51713
|
}
|
|
51743
51714
|
getRawIfComplete() {
|
|
51744
|
-
return this.truncated ? null : this.firstPart;
|
|
51715
|
+
return this.truncated ? null : capLineWidth(this.firstPart, this.lineLimit);
|
|
51745
51716
|
}
|
|
51746
51717
|
}
|
|
51747
51718
|
var processes = new Map;
|
|
@@ -52624,7 +52595,7 @@ register("read_file_base64", async (args) => {
|
|
|
52624
52595
|
return { path: fullPath, base64: data.toString("base64"), bytes: data.length };
|
|
52625
52596
|
});
|
|
52626
52597
|
register("search_files", async (args) => {
|
|
52627
|
-
const { pattern, path: p10 = ".", cwd = args.root_path ?? "", head = 100, max_count = 5, glob: globPattern = "", ignore_case = true } = args;
|
|
52598
|
+
const { pattern, path: p10 = ".", cwd = args.root_path ?? "", head = 100, max_count = 5, glob: globPattern = "", ignore_case = true, output: outputMode = DEFAULT_OUTPUT_MODE } = args;
|
|
52628
52599
|
const { execSync: execWhich } = await import("child_process");
|
|
52629
52600
|
const whichCmd = process.platform === "win32" ? "where" : "which";
|
|
52630
52601
|
const which = (bin) => {
|
|
@@ -52703,7 +52674,7 @@ register("search_files", async (args) => {
|
|
|
52703
52674
|
output = lines.join(`
|
|
52704
52675
|
`);
|
|
52705
52676
|
}
|
|
52706
|
-
return { result: truncateLines(output, { maxLines: head }) };
|
|
52677
|
+
return { result: truncateLines(output, { maxLines: head, maxLineLen: resolveOutputPolicy(outputMode).lineLimit }) };
|
|
52707
52678
|
}
|
|
52708
52679
|
if (code === 1)
|
|
52709
52680
|
return { result: "No matches found." };
|
|
@@ -53006,6 +52977,8 @@ class TODOforAIEdge {
|
|
|
53006
52977
|
browserExtensionBridge;
|
|
53007
52978
|
stopping = false;
|
|
53008
52979
|
reconnectTimer;
|
|
52980
|
+
wakeReconnect;
|
|
52981
|
+
connectedAt = 0;
|
|
53009
52982
|
edgeConfig = {
|
|
53010
52983
|
id: "",
|
|
53011
52984
|
name: "Name uninitialized",
|
|
@@ -53288,25 +53261,40 @@ class TODOforAIEdge {
|
|
|
53288
53261
|
console.log(`[warn] Unknown message type: ${msgType}`);
|
|
53289
53262
|
}
|
|
53290
53263
|
}
|
|
53291
|
-
startHeartbeat(onStale) {
|
|
53264
|
+
startHeartbeat(ws2, onStale) {
|
|
53292
53265
|
this.stopHeartbeat();
|
|
53266
|
+
const intervalMs = 30000;
|
|
53267
|
+
const resumeGapMs = 60000;
|
|
53268
|
+
let lastTick = Date.now();
|
|
53293
53269
|
let pongReceived = true;
|
|
53294
|
-
|
|
53270
|
+
ws2.on("pong", () => {
|
|
53295
53271
|
pongReceived = true;
|
|
53296
53272
|
});
|
|
53297
53273
|
this.heartbeatTimer = setInterval(() => {
|
|
53274
|
+
const now = Date.now();
|
|
53275
|
+
const elapsed = now - lastTick;
|
|
53276
|
+
lastTick = now;
|
|
53277
|
+
if (elapsed > resumeGapMs) {
|
|
53278
|
+
console.log(`[warn] Heartbeat delayed ${Math.round(elapsed / 1000)}s (sleep/resume likely), reconnecting`);
|
|
53279
|
+
try {
|
|
53280
|
+
ws2.terminate();
|
|
53281
|
+
} catch {}
|
|
53282
|
+
onStale();
|
|
53283
|
+
return;
|
|
53284
|
+
}
|
|
53298
53285
|
if (!pongReceived) {
|
|
53299
53286
|
console.log("[warn] No pong received, terminating stale connection");
|
|
53300
|
-
|
|
53301
|
-
|
|
53287
|
+
try {
|
|
53288
|
+
ws2.terminate();
|
|
53289
|
+
} catch {}
|
|
53302
53290
|
onStale();
|
|
53303
53291
|
return;
|
|
53304
53292
|
}
|
|
53305
53293
|
pongReceived = false;
|
|
53306
53294
|
try {
|
|
53307
|
-
|
|
53295
|
+
ws2.ping();
|
|
53308
53296
|
} catch {}
|
|
53309
|
-
},
|
|
53297
|
+
}, intervalMs);
|
|
53310
53298
|
}
|
|
53311
53299
|
stopHeartbeat() {
|
|
53312
53300
|
if (this.heartbeatTimer) {
|
|
@@ -53319,20 +53307,30 @@ class TODOforAIEdge {
|
|
|
53319
53307
|
const url = `${this.wsUrl}?fingerprint=${encodeURIComponent(this.fingerprint)}`;
|
|
53320
53308
|
if (this.debug)
|
|
53321
53309
|
console.log(`[info] Connecting to ${url}`);
|
|
53322
|
-
|
|
53310
|
+
const ws2 = new wrapper_default(url, [this.api.apiKey], {
|
|
53323
53311
|
maxPayload: 5 * 1024 * 1024,
|
|
53324
53312
|
rejectUnauthorized: false
|
|
53325
53313
|
});
|
|
53326
|
-
this.ws
|
|
53327
|
-
|
|
53328
|
-
|
|
53329
|
-
|
|
53314
|
+
this.ws = ws2;
|
|
53315
|
+
let settled = false;
|
|
53316
|
+
const settle = (fn2) => {
|
|
53317
|
+
if (settled)
|
|
53318
|
+
return;
|
|
53319
|
+
settled = true;
|
|
53320
|
+
if (this.ws === ws2) {
|
|
53321
|
+
this.stopHeartbeat();
|
|
53330
53322
|
this.connected = false;
|
|
53331
53323
|
this.ws = null;
|
|
53332
|
-
|
|
53333
|
-
|
|
53324
|
+
}
|
|
53325
|
+
fn2();
|
|
53326
|
+
};
|
|
53327
|
+
ws2.on("open", () => {
|
|
53328
|
+
this.connected = true;
|
|
53329
|
+
this.connectedAt = Date.now();
|
|
53330
|
+
console.log("[info] WebSocket connected");
|
|
53331
|
+
this.startHeartbeat(ws2, () => settle(() => resolve(0)));
|
|
53334
53332
|
});
|
|
53335
|
-
|
|
53333
|
+
ws2.on("message", (data, isBinary) => {
|
|
53336
53334
|
if (isBinary) {
|
|
53337
53335
|
const frame = data instanceof Buffer ? new Uint8Array(data) : new Uint8Array(data);
|
|
53338
53336
|
this.storeBinaryFrame(frame);
|
|
@@ -53340,36 +53338,30 @@ class TODOforAIEdge {
|
|
|
53340
53338
|
}
|
|
53341
53339
|
this.handleMessage(data.toString()).catch((e) => {
|
|
53342
53340
|
if (e instanceof AuthenticationError || e instanceof ServerError) {
|
|
53343
|
-
|
|
53344
|
-
reject(e);
|
|
53341
|
+
ws2.close();
|
|
53342
|
+
settle(() => reject(e));
|
|
53345
53343
|
} else {
|
|
53346
53344
|
console.error("[handler error]", e);
|
|
53347
53345
|
}
|
|
53348
53346
|
});
|
|
53349
53347
|
});
|
|
53350
|
-
|
|
53351
|
-
this.stopHeartbeat();
|
|
53352
|
-
this.connected = false;
|
|
53353
|
-
this.ws = null;
|
|
53348
|
+
ws2.on("close", (code, reason) => {
|
|
53354
53349
|
const reasonText = reason?.toString() || "<empty>";
|
|
53355
|
-
|
|
53356
|
-
console.log(`[info] WebSocket closed code=${code} clean=${clean} reason=${reasonText}`);
|
|
53350
|
+
console.log(`[info] WebSocket closed code=${code} clean=${code === 1000} reason=${reasonText}`);
|
|
53357
53351
|
if (code === 4001) {
|
|
53358
53352
|
console.log(`\x1B[33m[info] ${reasonText}. Not reconnecting.\x1B[0m`);
|
|
53359
53353
|
console.log(`\x1B[33m[info] To replace the existing connection, restart with: todoforai-edge --kill\x1B[0m`);
|
|
53360
|
-
reject(new ServerError(reasonText));
|
|
53354
|
+
settle(() => reject(new ServerError(reasonText)));
|
|
53361
53355
|
} else if (code === 4002) {
|
|
53362
53356
|
console.log(`\x1B[33m[info] ${reasonText}. This instance was replaced by a new connection.\x1B[0m`);
|
|
53363
|
-
reject(new ServerError(reasonText));
|
|
53357
|
+
settle(() => reject(new ServerError(reasonText)));
|
|
53364
53358
|
} else {
|
|
53365
|
-
resolve();
|
|
53359
|
+
settle(() => resolve(code));
|
|
53366
53360
|
}
|
|
53367
53361
|
});
|
|
53368
|
-
|
|
53369
|
-
|
|
53370
|
-
|
|
53371
|
-
this.ws = null;
|
|
53372
|
-
reject(err2);
|
|
53362
|
+
ws2.on("error", (err2) => {
|
|
53363
|
+
console.error(`[error] WebSocket error: ${err2.message}`);
|
|
53364
|
+
settle(() => resolve(0));
|
|
53373
53365
|
});
|
|
53374
53366
|
});
|
|
53375
53367
|
}
|
|
@@ -53379,45 +53371,34 @@ class TODOforAIEdge {
|
|
|
53379
53371
|
} catch {}
|
|
53380
53372
|
this.fingerprint = generateFingerprint();
|
|
53381
53373
|
console.log(`\x1B[36m\x1B[1m\uD83D\uDC46 Fingerprint:\x1B[0m ${this.fingerprint}`);
|
|
53382
|
-
const maxAttempts = 20;
|
|
53383
53374
|
let attempt = 0;
|
|
53384
|
-
while (
|
|
53385
|
-
console.log(`[info] Connecting (
|
|
53375
|
+
while (!this.stopping) {
|
|
53376
|
+
console.log(`[info] Connecting${attempt > 0 ? ` (retry ${attempt})` : ""}`);
|
|
53377
|
+
this.connectedAt = 0;
|
|
53386
53378
|
try {
|
|
53387
53379
|
await this.connect();
|
|
53388
|
-
if (this.stopping)
|
|
53389
|
-
break;
|
|
53390
|
-
attempt = 0;
|
|
53391
53380
|
} catch (e) {
|
|
53392
|
-
|
|
53393
|
-
|
|
53394
|
-
|
|
53395
|
-
}
|
|
53396
|
-
if (e instanceof ServerError) {
|
|
53397
|
-
console.error(`\x1B[31mServer error: ${e.message}\x1B[0m`);
|
|
53398
|
-
break;
|
|
53399
|
-
}
|
|
53400
|
-
attempt++;
|
|
53401
|
-
console.error(`[error] Connection error: ${e.message}`);
|
|
53402
|
-
} finally {
|
|
53403
|
-
this.connected = false;
|
|
53404
|
-
this.ws = null;
|
|
53405
|
-
}
|
|
53406
|
-
if (attempt > 0 && attempt < maxAttempts && !this.stopping) {
|
|
53407
|
-
const delay = Math.min(4 + attempt, 20);
|
|
53408
|
-
console.log(`[info] Reconnecting in ${delay}s...`);
|
|
53409
|
-
await new Promise((r) => {
|
|
53410
|
-
this.reconnectTimer = setTimeout(r, delay * 1000);
|
|
53411
|
-
});
|
|
53381
|
+
const label = e instanceof AuthenticationError ? "Authentication failed" : "Server error";
|
|
53382
|
+
console.error(`\x1B[31m${label}: ${e.message}\x1B[0m`);
|
|
53383
|
+
break;
|
|
53412
53384
|
}
|
|
53413
|
-
|
|
53414
|
-
|
|
53415
|
-
|
|
53385
|
+
if (this.stopping)
|
|
53386
|
+
break;
|
|
53387
|
+
if (this.connectedAt && Date.now() - this.connectedAt > 60000)
|
|
53388
|
+
attempt = 0;
|
|
53389
|
+
attempt++;
|
|
53390
|
+
const delay = Math.min(2 * 2 ** Math.min(attempt - 1, 4), 30);
|
|
53391
|
+
console.log(`[info] Reconnecting in ${delay}s...`);
|
|
53392
|
+
await new Promise((r) => {
|
|
53393
|
+
this.wakeReconnect = r;
|
|
53394
|
+
this.reconnectTimer = setTimeout(r, delay * 1000);
|
|
53395
|
+
});
|
|
53416
53396
|
}
|
|
53417
53397
|
}
|
|
53418
53398
|
stop() {
|
|
53419
53399
|
this.stopping = true;
|
|
53420
53400
|
clearTimeout(this.reconnectTimer);
|
|
53401
|
+
this.wakeReconnect?.();
|
|
53421
53402
|
this.stopHeartbeat();
|
|
53422
53403
|
this.browserExtensionBridge.stop();
|
|
53423
53404
|
this.frontendWs?.close();
|