@todoforai/cli 0.1.55 → 0.1.57

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.
@@ -42745,7 +42745,7 @@ function randomTip() {
42745
42745
  return TIPS[Math.floor(Math.random() * TIPS.length)];
42746
42746
  }
42747
42747
 
42748
- // ../../../home/hm/repo/todoforai/packages/shared-api/src/api.ts
42748
+ // ../packages/shared-api/src/api.ts
42749
42749
  function restBasePath(apiKey) {
42750
42750
  return apiKey.startsWith("dst_") ? "/dst/v1" : "/api/v1";
42751
42751
  }
@@ -42939,6 +42939,12 @@ class ApiClient {
42939
42939
  sendFeedMessage(msg) {
42940
42940
  return this.request("POST", "/api/v1/feed/message", msg);
42941
42941
  }
42942
+ getProjectMembers(projectId) {
42943
+ return this.request("GET", `/api/v1/projects/${projectId}/members`);
42944
+ }
42945
+ sendMemberMessage(msg) {
42946
+ return this.request("POST", "/api/v1/feed/message/member", msg);
42947
+ }
42942
42948
  listProjectSurfaces(projectId) {
42943
42949
  return this.request("GET", `/api/v1/projects/${projectId}/surfaces`);
42944
42950
  }
@@ -43169,11 +43175,11 @@ class ApiClient {
43169
43175
  return res.json();
43170
43176
  }
43171
43177
  }
43172
- // ../../../home/hm/repo/todoforai/packages/shared-api/src/frontend-ws.ts
43178
+ // ../packages/shared-api/src/frontend-ws.ts
43173
43179
  import { randomUUID } from "crypto";
43174
43180
  import WebSocket from "ws";
43175
43181
 
43176
- // ../../../home/hm/repo/todoforai/packages/shared-credentials/src/index.ts
43182
+ // ../packages/shared-credentials/src/index.ts
43177
43183
  import { readFileSync, writeFileSync, mkdirSync, chmodSync } from "node:fs";
43178
43184
  import { join, dirname } from "node:path";
43179
43185
  import { homedir, platform } from "node:os";
@@ -43213,7 +43219,7 @@ function writeCredential(apiUrl, apiKey) {
43213
43219
  writeSecure(c);
43214
43220
  }
43215
43221
 
43216
- // ../../../home/hm/repo/todoforai/packages/shared-api/src/frontend-ws.ts
43222
+ // ../packages/shared-api/src/frontend-ws.ts
43217
43223
  var log = (level, ...args) => console.log(`[frontend-ws:${level}]`, ...args);
43218
43224
 
43219
43225
  class FrontendWebSocket {
@@ -43483,7 +43489,7 @@ class FrontendWebSocket {
43483
43489
  });
43484
43490
  }
43485
43491
  }
43486
- // ../../../home/hm/repo/todoforai/packages/shared-fbe/src/enums.ts
43492
+ // ../packages/shared-fbe/src/enums.ts
43487
43493
  var TodoStatus;
43488
43494
  ((TodoStatus2) => {
43489
43495
  TodoStatus2["PROPOSED"] = "PROPOSED";
@@ -43507,7 +43513,7 @@ var TodoStatus;
43507
43513
  TodoStatus2["ARCHIVED"] = "ARCHIVED";
43508
43514
  TodoStatus2["DELETED"] = "DELETED";
43509
43515
  })(TodoStatus ||= {});
43510
- // ../../../home/hm/repo/todoforai/packages/shared-fbe/src/models.ts
43516
+ // ../packages/shared-fbe/src/models.ts
43511
43517
  var DEFAULT_MODEL = "anthropic:anthropic/claude-opus-5.5";
43512
43518
  var MODEL_ALIASES = {
43513
43519
  claude: DEFAULT_MODEL
@@ -43578,7 +43584,7 @@ function qualifiedModelIds(listing, filter) {
43578
43584
  }
43579
43585
  return q ? ids.filter((id) => id.toLowerCase().includes(q)) : ids;
43580
43586
  }
43581
- // ../../../home/hm/repo/todoforai/packages/shared-fbe/src/billing.ts
43587
+ // ../packages/shared-fbe/src/billing.ts
43582
43588
  var USAGE_WINDOWS = {
43583
43589
  SESSION_MS: 5 * 60 * 60 * 1000,
43584
43590
  WEEK_MS: 7 * 24 * 60 * 60 * 1000,
@@ -43586,7 +43592,7 @@ var USAGE_WINDOWS = {
43586
43592
  SESSION_FACTOR: 1 / 5
43587
43593
  };
43588
43594
  var NEVER_SCHEDULED_TIMESTAMP = 8640000000000000;
43589
- // ../../../home/hm/repo/todoforai/packages/shared-fbe/src/mimetypes.json
43595
+ // ../packages/shared-fbe/src/mimetypes.json
43590
43596
  var mimetypes_default = {
43591
43597
  conversionProfiles: {
43592
43598
  to_jpeg_90: { toMime: "image/jpeg", toExt: "jpg", quality: 0.9 }
@@ -43679,7 +43685,7 @@ var mimetypes_default = {
43679
43685
  }
43680
43686
  };
43681
43687
 
43682
- // ../../../home/hm/repo/todoforai/packages/shared-fbe/src/attachmentUtils.ts
43688
+ // ../packages/shared-fbe/src/attachmentUtils.ts
43683
43689
  var MCP_TEXT_MIME = Object.entries(mimetypes_default.aliases).find(([, type]) => type === "mcp_text")[0];
43684
43690
  var MCP_AUDIO_MIME = Object.entries(mimetypes_default.aliases).find(([, type]) => type === "mcp_audio")[0];
43685
43691
  var AttachmentType = Object.fromEntries(Object.keys(mimetypes_default.types).map((k) => [k.toUpperCase(), k]));
@@ -43719,7 +43725,7 @@ for (const [ext, entry] of Object.entries(mimetypes_default.extensions)) {
43719
43725
  if (!MIME_TO_EXT[mime])
43720
43726
  MIME_TO_EXT[mime] = ext;
43721
43727
  }
43722
- // ../../../home/hm/repo/todoforai/packages/shared-fbe/src/edgeTypes.ts
43728
+ // ../packages/shared-fbe/src/edgeTypes.ts
43723
43729
  var NON_SHELL_TYPES = new Set([
43724
43730
  "WEB_EXTENSION" /* WEB_EXTENSION */,
43725
43731
  "EXTENSION" /* EXTENSION */,
@@ -43727,7 +43733,7 @@ var NON_SHELL_TYPES = new Set([
43727
43733
  "ANDROID" /* ANDROID */,
43728
43734
  "IOS" /* IOS */
43729
43735
  ]);
43730
- // ../../../home/hm/repo/todoforai/packages/shared-fbe/src/blocks/index.ts
43736
+ // ../packages/shared-fbe/src/blocks/index.ts
43731
43737
  var USER_INPUT_BLOCK_TYPES = new Set([
43732
43738
  "question" /* Question */,
43733
43739
  "login_prompt" /* Login */,
@@ -43781,7 +43787,7 @@ var FILE_TOOL_TYPES = new Set([
43781
43787
  "edit" /* Edit */,
43782
43788
  "modify_file" /* ModifyFile */
43783
43789
  ]);
43784
- // ../../../home/hm/repo/todoforai/packages/shared-fbe/src/channels.ts
43790
+ // ../packages/shared-fbe/src/channels.ts
43785
43791
  var privateSuffix = (privateOwnerId) => privateOwnerId ? `:private:${privateOwnerId}` : "";
43786
43792
  var SERVER_TO_FRONTENDS = {
43787
43793
  task: {
@@ -43871,7 +43877,7 @@ var SERVER_TO_FRONTENDS = {
43871
43877
  function_call_request: (userId) => `${"FUNCTION_CALL_REQUEST_AGENT" /* FUNCTION_CALL_REQUEST_AGENT */}:frontend:${userId}`
43872
43878
  }
43873
43879
  };
43874
- // ../../../home/hm/repo/todoforai/packages/shared-fbe/src/REST_types.ts
43880
+ // ../packages/shared-fbe/src/REST_types.ts
43875
43881
  var MANUAL_CHANNEL = "Manual";
43876
43882
  function manualAnswersOf(sources) {
43877
43883
  const manual = sources?.find((s) => s.channel === MANUAL_CHANNEL);
@@ -43882,8 +43888,8 @@ function manualAnswersOf(sources) {
43882
43888
  return out;
43883
43889
  }
43884
43890
  var manualSamples = (answers) => Object.entries(answers).filter(([, a]) => a.trim()).map(([prompt, text]) => ({ prompt, text: text.trim() }));
43885
- var senderLabel = (s) => s.kind === "team" ? `${s.name} · TODO for AI team` : `${s.name} · your agent`;
43886
- // ../../../home/hm/repo/todoforai/packages/shared-fbe/src/templateBody.ts
43891
+ var senderLabel = (s) => s.kind === "team" ? `${s.name} · TODO for AI team` : s.kind === "member" ? `${s.name} · teammate` : `${s.name} · your agent`;
43892
+ // ../packages/shared-fbe/src/templateBody.ts
43887
43893
  var USER_HEADINGS = new Set([
43888
43894
  "your task",
43889
43895
  "your tasks",
@@ -43892,7 +43898,7 @@ var USER_HEADINGS = new Set([
43892
43898
  "steps",
43893
43899
  "data needed"
43894
43900
  ]);
43895
- // ../../../home/hm/repo/todoforai/packages/shared-fbe/src/bashPatterns.ts
43901
+ // ../packages/shared-fbe/src/bashPatterns.ts
43896
43902
  function splitShellCommands(input) {
43897
43903
  const parts = [];
43898
43904
  let current = "";
@@ -44134,7 +44140,7 @@ function getBlockServerId(block) {
44134
44140
  return prefix ? prefix.slice(0, -1) : "*";
44135
44141
  }
44136
44142
 
44137
- // ../../../home/hm/repo/todoforai/packages/shared-fbe/src/permissionUtils.ts
44143
+ // ../packages/shared-fbe/src/permissionUtils.ts
44138
44144
  function parsePattern(pattern) {
44139
44145
  const colonIndex = pattern.indexOf(":");
44140
44146
  if (colonIndex === -1)
@@ -44235,7 +44241,7 @@ function getBlockNewPatterns(block, permissions) {
44235
44241
  }
44236
44242
  return pairs.filter(({ raw, pattern }) => !isPatternAllowed(permissions, pattern) && !allow.some((rule) => bashRuleMatchesCmd(rule, serverId, raw))).map(({ pattern }) => pattern);
44237
44243
  }
44238
- // ../../../home/hm/repo/todoforai/packages/shared-fbe/src/systemAgents.ts
44244
+ // ../packages/shared-fbe/src/systemAgents.ts
44239
44245
  var BUSINESS_ANALYZER_AGENT = {
44240
44246
  id: "00000000-0000-0000-0000-202601013151",
44241
44247
  name: "Business Analyzer",
@@ -44381,7 +44387,7 @@ var SYSTEM_AGENTS = {
44381
44387
  [ONBOARDING_AGENT.id]: ONBOARDING_AGENT
44382
44388
  };
44383
44389
  var RECOMMEND_STEP = "Recommend growth TODOs for this project: materialize the ones you genuinely believe fit " + "as proposal cards on the board (api_recommend_template, one call per recommendation). " + "No quota — fewer good cards beat padding.";
44384
- // ../../../home/hm/repo/todoforai/packages/shared-fbe/src/outputLimits.ts
44390
+ // ../packages/shared-fbe/src/outputLimits.ts
44385
44391
  var MAX_LINE_LEN = 300;
44386
44392
  var STREAM_FIRST = 1e4;
44387
44393
  var STREAM_LAST = 1e4;
@@ -44392,7 +44398,1501 @@ var OUTPUT_POLICIES = {
44392
44398
  full: { firstLimit: Infinity, lastLimit: 0, hardCap: RUN_OUTPUT_CAP, lineLimit: MAX_LINE_LEN },
44393
44399
  raw: { firstLimit: Infinity, lastLimit: 0, hardCap: Infinity, lineLimit: Infinity }
44394
44400
  };
44395
- // ../../../home/hm/repo/todoforai/packages/shared-fbe/src/model_promos.json
44401
+ // ../packages/shared-fbe/src/tool_catalog.json
44402
+ var tool_catalog_default = {
44403
+ zele: {
44404
+ category: "email",
44405
+ pkg: "zele",
44406
+ installer: "npm",
44407
+ label: "Gmail",
44408
+ summary: "Read and send Gmail from your own account.",
44409
+ statusCmd: "zele whoami 2>/dev/null | grep 'email:' | grep -m1 .",
44410
+ loginNeedsPty: true,
44411
+ loginCmd: "zele login --method google",
44412
+ oauthUrlPattern: "(https://accounts\\.google\\.com\\S+)",
44413
+ oauthCallbackPort: 8089,
44414
+ credentialPaths: ["~/.zele/sqlite.db"],
44415
+ versionCmd: "zele --version 2>/dev/null | sed -n 1p",
44416
+ preinstallCloud: true,
44417
+ preinstallJarvis: true,
44418
+ description: "Gmail (multi-account, OAuth login): inbox, search, send, reply, contacts. Google Calendar via `zele cal`: list/create/update/delete events (Meet links, recurrence), respond to invites, free/busy."
44419
+ },
44420
+ "zele-calendar": {
44421
+ preinstallCloud: true,
44422
+ category: "utility",
44423
+ pkg: "zele",
44424
+ installer: "npm",
44425
+ binName: "zele",
44426
+ label: "Google Calendar",
44427
+ summary: "See your Google Calendar and book, move or cancel events.",
44428
+ statusCmd: "zele whoami 2>/dev/null | grep 'email:' | grep -m1 .",
44429
+ loginNeedsPty: true,
44430
+ loginCmd: "zele login --method google",
44431
+ oauthUrlPattern: "(https://accounts\\.google\\.com\\S+)",
44432
+ oauthCallbackPort: 8089,
44433
+ credentialPaths: ["~/.zele/sqlite.db"],
44434
+ versionCmd: "zele --version 2>/dev/null | sed -n 1p",
44435
+ preinstallJarvis: true,
44436
+ description: "Google Calendar via `zele cal`: list calendars and events, create/update/delete events with Google Meet links, recurrence and reminders, respond to invites, free/busy."
44437
+ },
44438
+ xurl: {
44439
+ category: "social",
44440
+ pkg: "@xdevplatform/xurl",
44441
+ installer: "npm",
44442
+ label: "X",
44443
+ summary: "Post to X and read timelines, replies and DMs.",
44444
+ statusCmd: `xurl whoami 2>&1 | sed -n 's/.*"username"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' | grep -m10 .`,
44445
+ loginCmd: "xurl auth oauth2",
44446
+ loginTerminalHint: "X needs your own developer app first: create one at developer.x.com (callback `http://localhost:8080/callback`), run `xurl auth apps add tfa --client-id <id> --client-secret <secret>`, then `xurl auth oauth2`.",
44447
+ oauthUrlPattern: "(https://(?:twitter|x)\\.com/i/oauth2\\S+)",
44448
+ oauthCallbackPort: 8080,
44449
+ credentialPaths: ["~/.xurl"],
44450
+ description: "Raw X API access via `xurl <method> <path>`.",
44451
+ versionCmd: "xurl version 2>/dev/null | sed -n 1p",
44452
+ preinstallJarvis: true,
44453
+ preinstallCloud: true
44454
+ },
44455
+ "linkedin-api": {
44456
+ preinstallCloud: true,
44457
+ category: "social",
44458
+ pkg: "@todoforai/linkedin-api",
44459
+ installer: "npm",
44460
+ label: "LinkedIn",
44461
+ summary: "Read LinkedIn profiles and send messages and connection requests.",
44462
+ statusCmd: `linkedin-api whoami 2>/dev/null | sed -n 's/.*"publicId"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' | grep -m10 .`,
44463
+ loginCmd: "linkedin-api auth <li_at> <jsessionid>",
44464
+ credentialPaths: ["~/.config/linkedin-api/credentials.json"],
44465
+ description: "LinkedIn Voyager API CLI for profiles, conversations, DMs, and connections.",
44466
+ versionCmd: "linkedin-api --version 2>/dev/null | sed -n 1p"
44467
+ },
44468
+ "figma-api": {
44469
+ category: "design",
44470
+ pkg: "@todoforai/figma-api",
44471
+ installer: "npm",
44472
+ label: "Figma",
44473
+ summary: "Read Figma files, export images and leave comments.",
44474
+ statusCmd: `figma-api me 2>/dev/null | sed -n 's/.*"email"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' | grep -m10 .`,
44475
+ loginCmd: "figma-api auth <token>",
44476
+ credentialPaths: ["~/.config/figma-api/credentials.json"],
44477
+ description: "Figma REST API CLI. File commands take a file key or Figma URL. Canvas writes (frames/text/shapes) use `figma-api run` via the plugin bridge. Run `figma-api <cmd> --help` for params.",
44478
+ versionCmd: "figma-api --version 2>/dev/null | sed -n 1p"
44479
+ },
44480
+ "slack-cli": {
44481
+ preinstallCloud: true,
44482
+ cloudInstallCmd: "curl -fsSL https://github.com/slackapi/slack-cli/releases/download/v4.2.0/slack_cli_4.2.0_linux_amd64.tar.gz | tar -xz -C /tmp bin/slack && install -m0755 /tmp/bin/slack /usr/local/bin/slack-cli && rm -rf /tmp/bin",
44483
+ cloudVerifyCmd: "slack-cli --version",
44484
+ category: "social",
44485
+ pkg: "slack-cli",
44486
+ installer: "binary",
44487
+ label: "Slack",
44488
+ summary: "Work with Slack workspaces and apps.",
44489
+ statusCmd: "slack-cli auth list 2>&1 | grep 'Team' >/dev/null && echo authenticated",
44490
+ loginCmd: "slack-cli login",
44491
+ loginTerminalHint: "Run `slack-cli login` in a terminal on this host, paste the `/slackauthticket …` command it prints into any Slack channel, then type the challenge code Slack shows you back into the terminal.",
44492
+ credentialPaths: ["~/.slack/credentials.json"],
44493
+ description: "Invoked as `slack-cli` to avoid colliding with the Slack desktop app’s `slack` binary.",
44494
+ versionCmd: "slack-cli version 2>/dev/null | sed -n 1p",
44495
+ binName: "slack-cli",
44496
+ binary: {
44497
+ "linux-x86_64": {
44498
+ url: "https://github.com/slackapi/slack-cli/releases/download/v4.2.0/slack_cli_4.2.0_linux_amd64.tar.gz",
44499
+ archive: "tar.gz",
44500
+ extract: "bin/slack"
44501
+ },
44502
+ "linux-aarch64": {
44503
+ url: "https://github.com/slackapi/slack-cli/releases/download/v4.2.0/slack_cli_4.2.0_linux_arm64.tar.gz",
44504
+ archive: "tar.gz",
44505
+ extract: "bin/slack"
44506
+ },
44507
+ "darwin-x86_64": {
44508
+ url: "https://github.com/slackapi/slack-cli/releases/download/v4.2.0/slack_cli_4.2.0_macOS_amd64.tar.gz",
44509
+ archive: "tar.gz",
44510
+ extract: "bin/slack"
44511
+ },
44512
+ "darwin-aarch64": {
44513
+ url: "https://github.com/slackapi/slack-cli/releases/download/v4.2.0/slack_cli_4.2.0_macOS_arm64.tar.gz",
44514
+ archive: "tar.gz",
44515
+ extract: "bin/slack"
44516
+ },
44517
+ "windows-x86_64": {
44518
+ url: "https://github.com/slackapi/slack-cli/releases/download/v4.2.0/slack_cli_4.2.0_windows_64-bit.zip",
44519
+ archive: "zip",
44520
+ extract: "bin/slack.exe"
44521
+ }
44522
+ }
44523
+ },
44524
+ "telegram-send": {
44525
+ preinstallCloud: true,
44526
+ packages: ["telegram-send==0.39.2"],
44527
+ category: "social",
44528
+ pkg: "telegram-send",
44529
+ installer: "pip",
44530
+ label: "Telegram",
44531
+ summary: "Send Telegram messages, photos and files.",
44532
+ loginCmd: `t=<bot_token>; c=$(curl -sf "https://api.telegram.org/bot$t/getUpdates" | python3 -c "import json,sys; u=[x for x in json.load(sys.stdin)['result'] if 'message' in x]; print(u[-1]['message']['chat']['id'])" 2>/dev/null) || { echo "No message from you yet: open your bot in Telegram, send it any message, then retry"; exit 1; }; mkdir -p "$HOME/.config" && printf '[telegram]\\ntoken = %s\\nchat_id = %s\\n' "$t" "$c" > "$HOME/.config/telegram-send.conf" && telegram-send "Connected to TODOforAI"`,
44533
+ credentialPaths: ["~/.config/telegram-send.conf"],
44534
+ versionCmd: "telegram-send --version 2>/dev/null | sed -n 1p",
44535
+ statusCmd: "test -f ~/.config/telegram-send.conf && echo authenticated",
44536
+ description: "Send messages, send files & images, send video & audio, Markdown/HTML formatting, channel & group support"
44537
+ },
44538
+ "meta-ads": {
44539
+ preinstallCloud: true,
44540
+ packages: ["meta-ads==1.1.0"],
44541
+ category: "marketing",
44542
+ pkg: "meta-ads",
44543
+ installer: "pip",
44544
+ label: "Meta Ads",
44545
+ summary: "Run Facebook and Instagram ad campaigns and see how they perform.",
44546
+ binName: "meta",
44547
+ statusCmd: `out=$(meta auth status 2>&1); rc=$?; printf '%s\\n' "$out" | grep -m1 .; exit $rc`,
44548
+ versionCmd: "meta --version 2>/dev/null | sed -n 1p",
44549
+ description: "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. No raw/passthrough mode; for arbitrary Graph API calls with a Page token use `facebook-pages-api request <method> <path>`."
44550
+ },
44551
+ "elevenlabs-api": {
44552
+ preinstallCloud: true,
44553
+ category: "media",
44554
+ pkg: "@todoforai/elevenlabs-api",
44555
+ installer: "npm",
44556
+ label: "ElevenLabs",
44557
+ summary: "Turn text into natural speech, in many voices and languages.",
44558
+ versionCmd: "elevenlabs-api --version 2>/dev/null | sed -n 1p",
44559
+ statusCmd: 'test -f ~/.config/todoforai/elevenlabs.json -o -n "$ELEVENLABS_API_KEY" && echo authenticated',
44560
+ loginCmd: "elevenlabs-api auth <api_key>",
44561
+ credentialPaths: ["~/.config/todoforai/elevenlabs.json"],
44562
+ description: "Text-to-speech, voice cloning, multiple languages."
44563
+ },
44564
+ ffmpeg: {
44565
+ commodity: true,
44566
+ category: "media",
44567
+ pkg: "ffmpeg",
44568
+ installer: "system",
44569
+ label: "FFmpeg",
44570
+ summary: "Convert, trim and resize video and audio files.",
44571
+ cloudAptPackages: ["ffmpeg"],
44572
+ preinstallCloud: true,
44573
+ description: "Media swiss-army knife: `ffmpeg -i in.mp4 out.webm`; `ffprobe` for metadata.",
44574
+ versionCmd: "ffmpeg -version 2>/dev/null | sed -n 1p"
44575
+ },
44576
+ "tfa-imagegen": {
44577
+ preinstallCloud: true,
44578
+ category: "media",
44579
+ pkg: "@todoforai/image-api",
44580
+ installer: "npm",
44581
+ label: "Image Gen",
44582
+ summary: "Generate and edit images from a description.",
44583
+ platformAuth: true,
44584
+ versionCmd: "tfa-imagegen --version 2>/dev/null | sed -n 1p",
44585
+ credentialPaths: ["~/.config/todoforai/credentials.json"],
44586
+ description: "AI image generation & editing via TODOFORAI backend (gpt-image, nano-banana, nano-banana-pro, mai-image-2.6)"
44587
+ },
44588
+ "suno-api": {
44589
+ preinstallCloud: true,
44590
+ category: "media",
44591
+ pkg: "@todoforai/suno-api",
44592
+ installer: "npm",
44593
+ label: "Suno",
44594
+ summary: "Generate music and songs from a prompt.",
44595
+ versionCmd: "suno-api --version 2>/dev/null | sed -n 1p",
44596
+ statusCmd: 'test -f ~/.config/todoforai/suno.json -o -n "$SUNO_API_KEY" && echo authenticated',
44597
+ loginCmd: "suno-api auth <api_key>",
44598
+ credentialPaths: [
44599
+ "~/.config/todoforai/suno.json"
44600
+ ],
44601
+ description: "AI music generation, custom lyrics, multiple genres."
44602
+ },
44603
+ ntn: {
44604
+ preinstallCloud: true,
44605
+ category: "utility",
44606
+ pkg: "ntn",
44607
+ installer: "npm",
44608
+ label: "Notion",
44609
+ summary: "Read and update Notion pages and databases.",
44610
+ statusCmd: "ntn doctor 2>&1 | grep -E 'Token valid\\s+✔' >/dev/null && echo authenticated",
44611
+ loginCmd: "ntn login",
44612
+ loginNeedsPty: true,
44613
+ oauthUrlPattern: "(https://www\\.notion\\.so/workers/cli-login\\S+)",
44614
+ sessionIdPattern: "verificationCode=([A-Z0-9-]{5,12})",
44615
+ credentialPaths: ["~/.config/notion"],
44616
+ versionCmd: "ntn --version 2>/dev/null | sed -n 1p",
44617
+ description: "Official Notion CLI: raw API calls (`ntn api <path>`), page/data-source management, file uploads, Workers deploy."
44618
+ },
44619
+ "perplexity-api": {
44620
+ preinstallCloud: true,
44621
+ category: "utility",
44622
+ pkg: "@todoforai/perplexity-api",
44623
+ installer: "npm",
44624
+ label: "Perplexity",
44625
+ summary: "Ask questions and get answers researched from the web.",
44626
+ versionCmd: "perplexity-api --version 2>/dev/null | sed -n 1p",
44627
+ statusCmd: 'test -f ~/.config/todoforai/perplexity.json && echo authenticated || test -n "$PERPLEXITY_API_KEY" && echo authenticated',
44628
+ loginCmd: "perplexity-api auth <api_key>",
44629
+ credentialPaths: ["~/.config/todoforai/perplexity.json"],
44630
+ description: "AI-powered web search, chat completions, async chat, embeddings and agent runs"
44631
+ },
44632
+ gh: {
44633
+ category: "development",
44634
+ pkg: "gh",
44635
+ installer: "binary",
44636
+ preinstallCloud: true,
44637
+ label: "GitHub",
44638
+ summary: "Work with your GitHub repos, pull requests and issues.",
44639
+ statusCmd: "gh auth status 2>&1 | grep 'Logged in to' | grep -m1 .",
44640
+ loginCmd: "gh auth login --hostname github.com --git-protocol https",
44641
+ oauthUrlPattern: "(https://github\\.com/login/device\\S*)",
44642
+ deviceCodePattern: "one-time code:\\s*([A-Z0-9]{4}-[A-Z0-9]{4})",
44643
+ canStreamLogin: true,
44644
+ logoutCmd: "gh auth logout --hostname github.com --user {{ACCOUNT}}",
44645
+ credentialPaths: ["~/.config/gh/hosts.yml"],
44646
+ description: "Prefer `gh` over raw GitHub API calls.",
44647
+ versionCmd: "gh --version 2>/dev/null | sed -n 1p",
44648
+ preinstallJarvis: true
44649
+ },
44650
+ mcporter: {
44651
+ category: "development",
44652
+ pkg: "mcporter",
44653
+ installer: "npm",
44654
+ label: "MCP (mcporter)",
44655
+ summary: "Use tools from any MCP server.",
44656
+ statusCmd: "mcporter list 2>/dev/null | grep -v 'No MCP servers' | grep -m1 .",
44657
+ description: "MCP servers as CLI: `mcporter list [server] [--schema]`, `mcporter call <server>.<tool> key=value` (`key=@file`), `mcporter config add <name> <cmd-or-url>`, `mcporter auth <server>`. `--help` for more.",
44658
+ versionCmd: "mcporter --version 2>/dev/null | sed -n 1p",
44659
+ preinstallJarvis: true,
44660
+ preinstallCloud: true
44661
+ },
44662
+ glab: {
44663
+ category: "development",
44664
+ pkg: "glab",
44665
+ installer: "binary",
44666
+ label: "GitLab",
44667
+ summary: "Work with your GitLab repos, merge requests and pipelines.",
44668
+ statusCmd: "glab auth status 2>&1 | grep 'Logged in to' | grep -m1 .",
44669
+ loginCmd: "glab auth login --device",
44670
+ credentialPaths: ["~/.config/glab-cli/config.yml"],
44671
+ description: "GitLab equivalent of `gh`.",
44672
+ versionCmd: "glab --version 2>/dev/null | sed -n 1p",
44673
+ deviceCodePattern: "one-time code: ([A-Z0-9-]+)",
44674
+ oauthUrlPattern: "(https://gitlab\\.com/oauth/device)",
44675
+ canStreamLogin: true
44676
+ },
44677
+ vercel: {
44678
+ preinstallCloud: true,
44679
+ category: "deployment",
44680
+ pkg: "vercel",
44681
+ installer: "npm",
44682
+ label: "Vercel",
44683
+ summary: "Deploy websites to Vercel and manage them.",
44684
+ statusCmd: "vercel whoami 2>/dev/null | tr -d '\\r' | grep -m1 .",
44685
+ loginCmd: "vercel login",
44686
+ credentialPaths: ["~/.local/share/com.vercel.cli/auth.json"],
44687
+ description: "`vercel deploy`; manage env vars, domains, preview URLs.",
44688
+ versionCmd: "vercel --version 2>/dev/null | sed -n 1p",
44689
+ deviceCodePattern: "user_code=([A-Z0-9-]+)",
44690
+ oauthUrlPattern: "(https://vercel\\.com/oauth/device\\S*)",
44691
+ canStreamLogin: true
44692
+ },
44693
+ eas: {
44694
+ category: "deployment",
44695
+ pkg: "eas-cli",
44696
+ installer: "npm",
44697
+ label: "Expo EAS",
44698
+ summary: "Build, update and submit mobile apps with Expo.",
44699
+ statusCmd: `out=$(eas whoami 2>/dev/null) && printf '%s\\n' "$out" | tr -d '\\r' | grep -m1 .`,
44700
+ loginCmd: "eas login --browser",
44701
+ logoutCmd: "eas logout",
44702
+ oauthUrlPattern: "(https://expo\\.dev/(?:sso-)?login\\?\\S+)",
44703
+ canStreamLogin: true,
44704
+ credentialPaths: ["~/.expo/state.json"],
44705
+ description: "Expo Application Services for React Native apps: `eas update --channel <c>` ships a JS-only OTA, `eas build -p ios --profile production --auto-submit` (`-p all` for both) makes a native build and sends it to TestFlight / Play. `eas build:list`, `eas update:list` and `eas submit` inspect or re-submit existing builds. An OTA only reaches installs whose runtimeVersion matches, so a native version bump needs a real build. The browser login binds to a RANDOM loopback port, so it only completes in a browser on the same host; on a remote/headless host set EXPO_TOKEN (Account -> Access tokens) instead. EXPO_TOKEN (Account -> Access tokens) works in CI instead of a login.",
44706
+ versionCmd: "eas --version 2>/dev/null | sed -n 1p"
44707
+ },
44708
+ netlify: {
44709
+ category: "deployment",
44710
+ pkg: "netlify-cli",
44711
+ installer: "npm",
44712
+ label: "Netlify",
44713
+ summary: "Deploy websites to Netlify and manage them.",
44714
+ statusCmd: `netlify api getCurrentUser 2>&1 | sed -n 's/.*"email"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' | grep -m10 .`,
44715
+ loginCmd: "netlify login",
44716
+ credentialPaths: ["~/.netlify/config.json"],
44717
+ description: "`netlify deploy --prod` for production; manage env vars, forms, identity.",
44718
+ versionCmd: "netlify --version 2>/dev/null | sed -n 1p",
44719
+ oauthUrlPattern: "(https://app\\.netlify\\.com/authorize\\S+)",
44720
+ canStreamLogin: true
44721
+ },
44722
+ firebase: {
44723
+ preinstallCloud: true,
44724
+ category: "deployment",
44725
+ pkg: "firebase-tools",
44726
+ installer: "npm",
44727
+ label: "Firebase",
44728
+ summary: "Deploy and manage Firebase hosting, database and functions.",
44729
+ statusCmd: "firebase login:list 2>&1 | grep -oiE '[[:alnum:]._%+-]+@[[:alnum:].-]+\\.[[:alpha:]]{2,}' | grep -m1 .",
44730
+ loginCmd: "firebase login --no-localhost --reauth",
44731
+ loginNeedsPty: true,
44732
+ stdinPaste: true,
44733
+ credentialPaths: ["~/.config/configstore/firebase-tools.json"],
44734
+ description: "Deploy hosting/functions, manage Firestore/RTDB rules and data, auth users, emulators.",
44735
+ versionCmd: "firebase --version 2>/dev/null | sed -n 1p",
44736
+ oauthUrlPattern: "(https://auth\\.firebase\\.tools/login\\S+)",
44737
+ sessionIdPattern: "session ID:\\s*([0-9A-Za-z]{4,8})"
44738
+ },
44739
+ wrangler: {
44740
+ preinstallCloud: true,
44741
+ category: "deployment",
44742
+ pkg: "wrangler",
44743
+ installer: "npm",
44744
+ label: "Cloudflare",
44745
+ summary: "Deploy and manage Cloudflare Workers and Pages.",
44746
+ statusCmd: "wrangler whoami 2>&1 | grep -oiE '[[:alnum:]._%+-]+@[[:alnum:].-]+\\.[[:alpha:]]{2,}' | grep -m1 .",
44747
+ loginCmd: "wrangler login",
44748
+ credentialPaths: ["~/.config/.wrangler/config/default.toml"],
44749
+ description: "Tail logs with `wrangler tail`. Use `cloudflared` for tunnels.",
44750
+ versionCmd: "wrangler --version 2>/dev/null | sed -n 1p",
44751
+ oauthUrlPattern: "(https://dash\\.cloudflare\\.com/oauth2/auth\\S+)",
44752
+ oauthCallbackPort: 8976
44753
+ },
44754
+ stripe: {
44755
+ preinstallCloud: true,
44756
+ cloudInstallCmd: "curl -fsSL https://github.com/stripe/stripe-cli/releases/download/v1.50.10/stripe_1.50.10_linux_x86_64.tar.gz | tar -xz -C /usr/local/bin stripe",
44757
+ cloudVerifyCmd: "stripe --version",
44758
+ category: "payments",
44759
+ pkg: "stripe",
44760
+ installer: "binary",
44761
+ label: "Stripe",
44762
+ summary: "Look up Stripe payments, customers and subscriptions.",
44763
+ statusCmd: "stripe config --list 2>/dev/null | sed -n 's/^account_id[[:space:]]*=[[:space:]]*.\\([^'\\'']*\\).*/\\1/p' | grep -m10 .",
44764
+ loginCmd: "stripe login",
44765
+ loginNeedsPty: true,
44766
+ credentialPaths: ["~/.config/stripe/config.toml"],
44767
+ description: "`stripe listen` forwards webhooks locally; `stripe get/post /v1/...` for raw API.",
44768
+ versionCmd: "stripe --version 2>/dev/null | sed -n 1p",
44769
+ deviceCodePattern: "verification code:\\s*([A-Z0-9]{4}-[A-Z0-9]{4})",
44770
+ oauthUrlPattern: "(https://access\\.stripe\\.com/stripecli/\\S+)",
44771
+ canStreamLogin: true
44772
+ },
44773
+ flyctl: {
44774
+ category: "cloud",
44775
+ pkg: "flyctl",
44776
+ installer: "binary",
44777
+ label: "Fly.io",
44778
+ summary: "Deploy and scale apps on Fly.io.",
44779
+ statusCmd: "flyctl auth whoami 2>/dev/null | tr -d '\\r' | grep -m1 .",
44780
+ loginCmd: "flyctl auth login -t <access_token>",
44781
+ credentialPaths: ["~/.fly/config.yml"],
44782
+ description: "`flyctl deploy` from app dir; manage Postgres/volumes/machines/secrets.",
44783
+ versionCmd: "flyctl version 2>/dev/null | sed -n 1p"
44784
+ },
44785
+ supabase: {
44786
+ preinstallCloud: true,
44787
+ cloudInstallCmd: "curl -fsSL https://github.com/supabase/cli/releases/download/v2.117.0/supabase_2.117.0_linux_amd64.tar.gz | tar -xz -C /usr/local/bin supabase",
44788
+ cloudVerifyCmd: "supabase --version",
44789
+ category: "cloud",
44790
+ pkg: "supabase",
44791
+ installer: "binary",
44792
+ label: "Supabase",
44793
+ summary: "Manage your Supabase database, auth and storage.",
44794
+ statusCmd: "supabase projects list >/dev/null 2>&1 && echo authenticated",
44795
+ loginCmd: "supabase login --token <access_token>",
44796
+ credentialPaths: ["~/.config/supabase/access-token"],
44797
+ description: "Link to cloud project; runs/migrates local Postgres.",
44798
+ versionCmd: "supabase --version 2>/dev/null | sed -n 1p"
44799
+ },
44800
+ railway: {
44801
+ category: "cloud",
44802
+ pkg: "@railway/cli",
44803
+ installer: "npm",
44804
+ label: "Railway",
44805
+ summary: "Deploy and manage apps and databases on Railway.",
44806
+ statusCmd: `out=$(RAILWAY_NO_TELEMETRY=1 railway whoami 2>&1); rc=$?; printf '%s\\n' "$out" | grep -m1 .; exit $rc`,
44807
+ loginCmd: "railway login --browserless",
44808
+ credentialPaths: ["~/.railway/config.json"],
44809
+ description: "`railway up` to deploy current dir.",
44810
+ versionCmd: "railway --version 2>/dev/null | sed -n 1p",
44811
+ deviceCodePattern: "user_code=([A-Z0-9-]+)",
44812
+ oauthUrlPattern: "(https://railway\\.com/activate\\S*)",
44813
+ canStreamLogin: true
44814
+ },
44815
+ shopify: {
44816
+ preinstallCloud: true,
44817
+ category: "ecommerce",
44818
+ pkg: "@shopify/cli",
44819
+ installer: "npm",
44820
+ label: "Shopify",
44821
+ summary: "Build and manage Shopify stores and themes.",
44822
+ loginCmd: "shopify auth login --alias tfa",
44823
+ credentialPaths: ["~/.config/shopify-cli-kit-nodejs/config.json"],
44824
+ versionCmd: `node -p "require(require('fs').realpathSync(require('child_process').execSync('command -v shopify',{encoding:'utf8'}).trim()).replace(/\\/bin\\/[^/]*$/, '/package.json')).version" 2>/dev/null`,
44825
+ statusCmd: `python3 -c "import json,os,sys;p=os.path.expanduser('~/.config/shopify-cli-kit-nodejs/config.json');sys.exit(0 if json.load(open(p)).get('session') else 1)" 2>/dev/null && echo authenticated`,
44826
+ deviceCodePattern: "verification code: ([A-Z0-9-]+)",
44827
+ oauthUrlPattern: "(https://accounts\\.shopify\\.com/activate-with-code\\S*)",
44828
+ canStreamLogin: true,
44829
+ description: "Theme development, app scaffolding, store management"
44830
+ },
44831
+ "shop-app": {
44832
+ preinstallCloud: true,
44833
+ category: "ecommerce",
44834
+ pkg: "@todoforai/shop-app",
44835
+ installer: "npm",
44836
+ label: "Shop",
44837
+ summary: "Search products and compare prices across Shopify stores.",
44838
+ description: "Shop.app product search across Shopify stores. `shop-app search '<query>' --ships-to HU -n 10` returns products with price, rating, URL, and variants. `shop-app similar <variantId>` finds lookalikes.",
44839
+ versionCmd: "shop-app --version 2>/dev/null | sed -n 1p"
44840
+ },
44841
+ "datadog-ci": {
44842
+ category: "monitoring",
44843
+ pkg: "@datadog/datadog-ci",
44844
+ installer: "npm",
44845
+ label: "Datadog",
44846
+ summary: "Send test results and releases to Datadog.",
44847
+ versionCmd: "datadog-ci version 2>/dev/null | sed -n 1p",
44848
+ statusCmd: 'test -n "$DATADOG_API_KEY" && echo authenticated',
44849
+ description: "CI test visibility, sourcemap uploads, deployment tracking."
44850
+ },
44851
+ "sentry-cli": {
44852
+ preinstallCloud: true,
44853
+ category: "monitoring",
44854
+ pkg: "@sentry/cli",
44855
+ installer: "npm",
44856
+ label: "Sentry",
44857
+ summary: "Manage Sentry releases and error tracking.",
44858
+ statusCmd: "sentry-cli info --no-defaults >/dev/null 2>&1 && echo authenticated",
44859
+ loginCmd: "sentry-cli login --auth-token <auth_token>",
44860
+ credentialPaths: ["~/.sentryclirc"],
44861
+ description: "`sentry-cli releases new <version>`.",
44862
+ versionCmd: "sentry-cli --version 2>/dev/null | sed -n 1p"
44863
+ },
44864
+ "tfa-cli": {
44865
+ category: "utility",
44866
+ pkg: "@todoforai/cli",
44867
+ installer: "npm",
44868
+ label: "TODOforAI",
44869
+ summary: "Create and follow up on your TODOs, and show results in the chat.",
44870
+ description: 'Manage the user\'s OWN TODOforAI tasks (not code `// TODO` comments). Delegate/spin off work: `tfa-cli "prompt"` creates a real TODO with its own agent and chat (`-n` wait for completion, `--no-watch` fire-and-forget, `--project/--agent/--model`, `--resume <id>`, `addmessage <id> "text"`). Inspect: `list [--status open]`, `--inspect <id>`. Edit any todo without opening it: `status <id> DONE|READY|ARCHIVED|POSTPONED|CANCELLED|DELETED` (close, reopen, shelve, trash — `todo restore <id>` undoes a delete) and `todo set <id|-> title=… group=… star=true schedule=<unix-ms> agent=… brand=…` (own todo fields). Both take ONE id, so a loop over `list --json | jq -r \'.items[].id\'` is how you close, retitle, refile or archive MANY todos at once; `project set|settings|groups …` (name, settings, todo groups), `agent create|update <agent> model=… sysmsg=…` (permissions/MCP/device wiring and delete are refused from an agent shell). Surface results into chat: `show <file|-> [todo-id]` (renders by mimetype; `--card <name>` via project renderer; `--alias <name>` updates in place), `open <url>` embeds a live preview. Notifications: `inbox [needs-you|messages|activity] [--unread]` reads the user\'s bell feed (what finished/failed/needs them while away), `inbox seen [tab]` marks it read; `notify "Title" "message" [--href /t/<id>]` leaves a note in the user\'s Messages tab + phone push (shown as from you); `--to <email>` reaches a member of the current project instead (`project members` lists them, 20/day) — nobody else; same text is delivered once, max 10/day to the user, use for things they must see later (not chat replies). This is the ONLY way to display a local file (image, pdf, html) in the chat: markdown `![](path.png)` with a local/relative path does NOT resolve to workspace files or attachments and 404s in the browser. `--template` for registry workflows.',
44871
+ installCmd: "npm install -g @todoforai/cli",
44872
+ versionCmd: "tfa-cli --version 2>/dev/null | sed -n 1p",
44873
+ preinstallCloud: true,
44874
+ preinstallJarvis: true,
44875
+ aliases: ["todoforai-cli", "todoai"]
44876
+ },
44877
+ curl: {
44878
+ commodity: true,
44879
+ category: "networking",
44880
+ pkg: "curl",
44881
+ installer: "system",
44882
+ label: "Web Request",
44883
+ summary: "Fetch URLs and call web APIs.",
44884
+ binName: "curl",
44885
+ description: "HTTP/HTTPS/FTP client: GET/POST/PUT/DELETE, headers, auth, file upload/download, follow redirects, cookies, TLS.",
44886
+ versionCmd: "curl --version 2>/dev/null | sed -n 1p | cut -d' ' -f1-2",
44887
+ preinstallCloud: true,
44888
+ internal: true,
44889
+ preinstallJarvis: true
44890
+ },
44891
+ cloudflared: {
44892
+ preinstallCloud: true,
44893
+ cloudInstallCmd: "curl -fsSL https://github.com/cloudflare/cloudflared/releases/download/2026.9.0/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod 0755 /usr/local/bin/cloudflared",
44894
+ cloudVerifyCmd: "cloudflared --version",
44895
+ commodity: true,
44896
+ category: "networking",
44897
+ pkg: "cloudflared",
44898
+ installer: "binary",
44899
+ label: "Tunnel",
44900
+ summary: "Put a local app on a public web address.",
44901
+ description: "`cloudflared tunnel --url http://localhost:<port>` prints a public https URL.",
44902
+ versionCmd: "cloudflared --version 2>/dev/null | sed -n 1p"
44903
+ },
44904
+ vault: {
44905
+ category: "security",
44906
+ pkg: "vault",
44907
+ installer: "binary",
44908
+ label: "HashiCorp Vault",
44909
+ summary: "Read and manage secrets in HashiCorp Vault.",
44910
+ statusCmd: "vault token lookup >/dev/null 2>&1 && echo authenticated",
44911
+ loginCmd: "vault login <token>",
44912
+ credentialPaths: ["~/.vault-token"],
44913
+ versionCmd: "vault --version 2>/dev/null | sed -n '1s/ (.*//p'",
44914
+ description: "HashiCorp Vault secrets management and dynamic credentials."
44915
+ },
44916
+ rclone: {
44917
+ category: "cloud",
44918
+ pkg: "rclone",
44919
+ installer: "binary",
44920
+ preinstallCloud: true,
44921
+ binary: {
44922
+ "linux-x86_64": { url: "https://github.com/todoforai/rclone-backend/releases/download/v0.1.6/rclone-linux-amd64.tar.gz", archive: "tar.gz", extract: "rclone" },
44923
+ "linux-aarch64": { url: "https://github.com/todoforai/rclone-backend/releases/download/v0.1.6/rclone-linux-arm64.tar.gz", archive: "tar.gz", extract: "rclone" },
44924
+ "darwin-x86_64": { url: "https://github.com/todoforai/rclone-backend/releases/download/v0.1.6/rclone-darwin-amd64.tar.gz", archive: "tar.gz", extract: "rclone" },
44925
+ "darwin-aarch64": { url: "https://github.com/todoforai/rclone-backend/releases/download/v0.1.6/rclone-darwin-arm64.tar.gz", archive: "tar.gz", extract: "rclone" },
44926
+ "windows-x86_64": { url: "https://github.com/todoforai/rclone-backend/releases/download/v0.1.6/rclone-windows-amd64.zip", archive: "zip", extract: "rclone.exe" }
44927
+ },
44928
+ label: "Cloud Storage",
44929
+ summary: "Move files between Google Drive, Dropbox, OneDrive, S3 and more.",
44930
+ statusCmd: "rclone listremotes --long 2>/dev/null | tr -s ' ' | grep -v ' todoforai *$' | grep -m20 .",
44931
+ loginCmd: "rclone config",
44932
+ loginTerminalHint: "Connect a storage from its own tile (Google Drive, OneDrive, Dropbox…). For other backends run `rclone config` in a terminal on this host.",
44933
+ oauthUrlPattern: "(https?://127\\.0\\.0\\.1:\\d+/auth\\?\\S+)",
44934
+ oauthCallbackPort: 53682,
44935
+ credentialPaths: ["~/.config/rclone/rclone.conf"],
44936
+ subProviders: {
44937
+ gdrive: {
44938
+ label: "Google Drive",
44939
+ authType: "oauth",
44940
+ connectCmd: "rclone config create {{REMOTE}} drive",
44941
+ remoteName: "gdrive",
44942
+ rcloneType: "drive",
44943
+ userInfoCmd: `TOKEN=$(rclone config dump 2>/dev/null | python3 -c "import json,sys; print(json.loads(json.load(sys.stdin)['{{REMOTE}}']['token'])['access_token'])") && curl -sf 'https://www.googleapis.com/drive/v3/about?fields=user' -H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; print(json.load(sys.stdin)['user']['emailAddress'])" 2>/dev/null`
44944
+ },
44945
+ onedrive: {
44946
+ label: "OneDrive",
44947
+ authType: "oauth",
44948
+ connectCmd: "rclone config create {{REMOTE}} onedrive",
44949
+ remoteName: "onedrive",
44950
+ rcloneType: "onedrive",
44951
+ userInfoCmd: `TOKEN=$(rclone config dump 2>/dev/null | python3 -c "import json,sys; print(json.loads(json.load(sys.stdin)['{{REMOTE}}']['token'])['access_token'])") && curl -sf 'https://graph.microsoft.com/v1.0/me' -H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('mail') or d.get('userPrincipalName',''))" 2>/dev/null`
44952
+ },
44953
+ dropbox: {
44954
+ label: "Dropbox",
44955
+ authType: "oauth",
44956
+ connectCmd: "rclone config create {{REMOTE}} dropbox",
44957
+ remoteName: "dropbox",
44958
+ rcloneType: "dropbox",
44959
+ userInfoCmd: `TOKEN=$(rclone config dump 2>/dev/null | python3 -c "import json,sys; print(json.loads(json.load(sys.stdin)['{{REMOTE}}']['token'])['access_token'])") && curl -sf -X POST 'https://api.dropboxapi.com/2/users/get_current_account' -H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; print(json.load(sys.stdin)['email'])" 2>/dev/null`
44960
+ },
44961
+ s3: {
44962
+ label: "Amazon S3",
44963
+ authType: "credentials",
44964
+ connectCmd: "rclone config create {{REMOTE}} s3",
44965
+ remoteName: "s3",
44966
+ rcloneType: "s3"
44967
+ }
44968
+ },
44969
+ versionCmd: "rclone version 2>/dev/null | sed -n 1p",
44970
+ preinstallJarvis: true,
44971
+ description: "Access Google Drive, OneDrive, Dropbox, S3, and 40+ cloud providers. List, copy, sync, move, and mount files; use `rclone config create <name> <provider>` to connect."
44972
+ },
44973
+ aws: {
44974
+ preinstallCloud: true,
44975
+ cloudInstallCmd: "curl -fsSL https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -o /tmp/awscli.zip && unzip -qo /tmp/awscli.zip -d /tmp && /tmp/aws/install --update && rm -rf /tmp/aws /tmp/awscli.zip",
44976
+ cloudVerifyCmd: "aws --version",
44977
+ category: "cloud",
44978
+ pkg: "awscli",
44979
+ installer: "system",
44980
+ label: "AWS",
44981
+ summary: "Manage your AWS account and services.",
44982
+ installCmd: `mkdir -p "$HOME/.todoforai/tools/bin"; if [ "$(uname -s)" = Darwin ]; then curl -fsSL https://awscli.amazonaws.com/AWSCLIV2.pkg -o /tmp/AWSCLIV2.pkg && printf '<?xml version="1.0" encoding="UTF-8"?>\\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\\n<plist version="1.0"><array><dict><key>choiceAttribute</key><string>customLocation</string><key>attributeSetting</key><string>%s</string><key>choiceIdentifier</key><string>default</string></dict></array></plist>\\n' "$HOME/.todoforai/tools" > /tmp/aws-choices.xml && installer -pkg /tmp/AWSCLIV2.pkg -target CurrentUserHomeDirectory -applyChoiceChangesXML /tmp/aws-choices.xml && mkdir -p "$HOME/.todoforai/tools/bin" && ln -sf "$HOME/.todoforai/tools/aws-cli/aws" "$HOME/.todoforai/tools/bin/aws" && ln -sf "$HOME/.todoforai/tools/aws-cli/aws_completer" "$HOME/.todoforai/tools/bin/aws_completer"; rc=$?; rm -f /tmp/AWSCLIV2.pkg /tmp/aws-choices.xml; exit $rc; else ARCH=$(uname -m); case "$ARCH" in arm64|aarch64) ARCH=aarch64;; *) ARCH=x86_64;; esac; curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-$ARCH.zip" -o /tmp/awscli.zip && rm -rf /tmp/aws && unzip -qo /tmp/awscli.zip -d /tmp && /tmp/aws/install --update --install-dir "$HOME/.todoforai/tools/aws-cli" --bin-dir "$HOME/.todoforai/tools/bin"; rc=$?; rm -rf /tmp/aws /tmp/awscli.zip; exit $rc; fi`,
44983
+ statusCmd: "aws sts get-caller-identity --query Arn --output text 2>/dev/null | sed 's/.*\\///' | grep -m1 .",
44984
+ loginCmd: "aws configure set aws_access_key_id <access_key_id> && aws configure set aws_secret_access_key <secret_access_key> && aws configure set region <region>",
44985
+ credentialPaths: ["~/.aws/credentials", "~/.aws/config", "~/.aws/sso/cache"],
44986
+ description: "Official AWS CLI v2, installed user-locally into ~/.todoforai/tools (no sudo; Linux x86_64/aarch64 + macOS). Auth via `aws configure` (access keys) or `aws configure sso` + `aws sso login` (browser device flow).",
44987
+ versionCmd: "aws --version 2>/dev/null | sed -n 1p",
44988
+ uninstallCmd: 'rm -rf "$HOME/.todoforai/tools/aws-cli" "$HOME/.todoforai/tools/bin/aws" "$HOME/.todoforai/tools/bin/aws_completer"; hash -r 2>/dev/null; ! command -v aws >/dev/null 2>&1 || { echo "aws is still on PATH: $(command -v aws) — not installed by TODOforAI, remove it with the package manager that installed it (snap/apt/brew/sudo)"; exit 1; }'
44989
+ },
44990
+ gcloud: {
44991
+ category: "cloud",
44992
+ pkg: "google-cloud-sdk",
44993
+ installer: "system",
44994
+ label: "Google Cloud",
44995
+ summary: "Manage your Google Cloud projects and services.",
44996
+ needsPython: true,
44997
+ installCmd: `curl -fsSL https://dl.google.com/dl/cloudsdk/channels/rapid/google-cloud-sdk.tar.gz | tar -xzC "$HOME" && CLOUDSDK_PYTHON="$py" "$HOME/google-cloud-sdk/install.sh" --quiet --install-python false --usage-reporting false --path-update false --command-completion false && mkdir -p "$HOME/.todoforai/tools/bin" && rm -f "$HOME/.todoforai/tools/bin/gcloud" && printf '#!/bin/sh\\nCLOUDSDK_PYTHON="\${CLOUDSDK_PYTHON:-%s}" exec "$HOME/google-cloud-sdk/bin/gcloud" "$@"\\n' "$py" > "$HOME/.todoforai/tools/bin/gcloud" && chmod +x "$HOME/.todoforai/tools/bin/gcloud"`,
44998
+ statusCmd: `gcloud auth list --filter=status:ACTIVE --format="value(account)" 2>/dev/null | tr -d '\\r' | grep -m1 .`,
44999
+ loginCmd: "gcloud auth login --no-launch-browser",
45000
+ loginNeedsPty: true,
45001
+ stdinPaste: true,
45002
+ oauthUrlPattern: "(https://accounts\\.google\\.com/o/oauth2/auth\\S+)",
45003
+ credentialPaths: ["~/.config/gcloud"],
45004
+ description: "Official Google Cloud CLI. `gcloud auth login --no-launch-browser` prints the OAuth URL and waits for the pasted verification code.",
45005
+ versionCmd: "gcloud --version 2>/dev/null | sed -n 1p",
45006
+ uninstallCmd: 'rm -rf "$HOME/google-cloud-sdk" "$HOME/.todoforai/tools/bin/gcloud"; hash -r 2>/dev/null; ! command -v gcloud >/dev/null 2>&1 || { echo "gcloud is still on PATH: $(command -v gcloud)"; exit 1; }',
45007
+ updateCmd: "gcloud components update --quiet"
45008
+ },
45009
+ az: {
45010
+ category: "cloud",
45011
+ pkg: "azure-cli",
45012
+ installer: "pip",
45013
+ label: "Azure",
45014
+ summary: "Manage your Microsoft Azure resources.",
45015
+ binName: "az",
45016
+ statusCmd: "az account show --query user.name -o tsv 2>/dev/null | tr -d '\\r' | grep -m1 .",
45017
+ loginCmd: "az login --use-device-code",
45018
+ oauthUrlPattern: "(https://login\\.microsoft\\.com/device\\S*)",
45019
+ deviceCodePattern: "enter the code ([A-Z0-9]+)",
45020
+ canStreamLogin: true,
45021
+ credentialPaths: ["~/.azure"],
45022
+ description: "Official Azure CLI. Device-code login: prints login.microsoft.com/device + a code, then polls — no callback port.",
45023
+ versionCmd: `az version --query '"azure-cli"' -o tsv 2>/dev/null | sed -n 1p`
45024
+ },
45025
+ doctl: {
45026
+ preinstallCloud: true,
45027
+ cloudInstallCmd: "curl -fsSL https://github.com/digitalocean/doctl/releases/download/v1.168.0/doctl-1.168.0-linux-amd64.tar.gz | tar -xz -C /usr/local/bin doctl",
45028
+ cloudVerifyCmd: "doctl version",
45029
+ category: "cloud",
45030
+ pkg: "doctl",
45031
+ installer: "binary",
45032
+ label: "DigitalOcean",
45033
+ summary: "Manage your DigitalOcean servers and apps.",
45034
+ statusCmd: "doctl account get --format Email --no-header 2>/dev/null | tr -d '\\r' | grep -m1 .",
45035
+ loginCmd: "doctl auth init -t <access_token>",
45036
+ loginNeedsPty: true,
45037
+ credentialPaths: ["~/.config/doctl/config.yaml"],
45038
+ description: "Official DigitalOcean CLI. `doctl auth init` prompts for an API token from cloud.digitalocean.com/account/api/tokens.",
45039
+ versionCmd: "doctl version 2>/dev/null | sed -n 1p"
45040
+ },
45041
+ rdt: {
45042
+ preinstallCloud: true,
45043
+ packages: ["rdt-cli==0.4.1"],
45044
+ category: "social",
45045
+ pkg: "rdt-cli",
45046
+ installer: "pip",
45047
+ label: "Reddit",
45048
+ summary: "Search Reddit, read threads and post comments.",
45049
+ statusCmd: `rdt status 2>&1 | sed -n 's/.*"username"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' | grep -m10 .`,
45050
+ loginCmd: "rdt login",
45051
+ loginTerminalHint: "Log in to reddit.com in a browser on this host (Chrome/Firefox), then run `rdt login`: it imports that browser's Reddit cookies.",
45052
+ credentialPaths: ["~/.config/rdt-cli/credential.json"],
45053
+ description: 'Reddit CLI. Read: `rdt search "<query>" -r <sub> --json -c`, `rdt sub <name>`, `rdt read <postId>`, `rdt user-comments <user>`. Write: `rdt comment <postId> "<text>"`, `rdt upvote`, `rdt save`.',
45054
+ versionCmd: "rdt --version 2>/dev/null | sed -n 1p"
45055
+ },
45056
+ pymupdf: {
45057
+ category: "utility",
45058
+ pkg: "pymupdf",
45059
+ packages: ["pymupdf==1.28.2"],
45060
+ installer: "pip",
45061
+ label: "PDF",
45062
+ summary: "Read, edit, merge and split PDF files.",
45063
+ versionCmd: "{ python -c 'import pymupdf; print(pymupdf.__version__)' || python3 -c 'import pymupdf; print(pymupdf.__version__)' || py -c 'import pymupdf; print(pymupdf.__version__)'; } 2>/dev/null",
45064
+ preinstallCloud: true,
45065
+ cloudVerifyCmd: "python3 -c 'import pymupdf'",
45066
+ preinstallJarvis: true,
45067
+ description: "PDF text editing, extraction, merge, split. Call via `python3 -c`."
45068
+ },
45069
+ pyhanko: {
45070
+ category: "security",
45071
+ pkg: "pyhanko-cli",
45072
+ packages: ["pyhanko-cli==0.5.0", "pyHanko[pkcs11,image-support,opentype]==0.37.0"],
45073
+ installer: "pip",
45074
+ binName: "pyhanko",
45075
+ label: "PDF Signing",
45076
+ summary: "Digitally sign and verify PDFs with a certificate (PAdES, timestamps, smart cards).",
45077
+ versionCmd: "pyhanko --version 2>/dev/null | sed -n 1p",
45078
+ preinstallCloud: true,
45079
+ cloudVerifyCmd: "pyhanko --version",
45080
+ preinstallJarvis: true,
45081
+ description: "Cryptographic PDF signing/validation (PAdES B-B..B-LTA). Sign: `pyhanko sign addsig --field Sig1 [--timestamp-url <tsa>] pkcs12 --passfile pass.txt in.pdf out.pdf cert.p12`; smart card/eID: `... pkcs11 --lib <pkcs11.so> in.pdf out.pdf`; verify: `pyhanko sign validate --pretty-print [--trust ca.pem] out.pdf`; list fields: `pyhanko sign list`. The signer's cert/key comes from the user (vault); self-signed certs validate only with `--trust`. Not for click-to-sign workflows."
45082
+ },
45083
+ anydoc: {
45084
+ category: "utility",
45085
+ pkg: "@firecrawl/anydoc",
45086
+ installer: "npm",
45087
+ binName: "anydoc",
45088
+ label: "Doc to Markdown",
45089
+ summary: "Turn Word, PowerPoint, Excel and other documents into clean text.",
45090
+ description: "Office/rich docs (docx/pptx/xlsx/odt/rtf/epub/csv) → Markdown, fully local. `anydoc file.docx` → stdout, `-o out.md` writes, `anydoc - --format csv < stdin`. Prefer over raw XML extraction for reading; one-way, no OCR for scanned PDFs.",
45091
+ versionCmd: "anydoc --version 2>/dev/null | sed -n 1p",
45092
+ preinstallCloud: true,
45093
+ preinstallJarvis: true
45094
+ },
45095
+ "agent-browser": {
45096
+ category: "development",
45097
+ pkg: "agent-browser@0.30.1",
45098
+ installer: "npm",
45099
+ preinstallCloud: true,
45100
+ label: "Browser",
45101
+ summary: "Browse websites automatically: click, type, read and screenshot.",
45102
+ description: "Browser automation over CDP (`open`/`snapshot`/`click`/`type`/`fill`/`read`/`screenshot`/`eval`). `connect '<url>'` to a listed live browser (prefer the user's real Chrome), verify with `snapshot`; if empty, `close` (own session only) and reconnect once. NEVER `close --all`. Shared browser: `tab new` for your own tab, `tab <id>` before each command. No URL listed: cloud → browser-manager-cli; PC with display → Chrome `--remote-debugging-port=9222`, attach `--cdp 9222`.",
45103
+ versionCmd: "agent-browser --version 2>/dev/null | sed -n 1p",
45104
+ preinstallJarvis: true
45105
+ },
45106
+ "browser-manager-cli": {
45107
+ category: "development",
45108
+ pkg: "browser-manager-cli",
45109
+ installer: "binary",
45110
+ binName: "browser-manager-cli",
45111
+ preinstallCloud: true,
45112
+ label: "Browser Manager",
45113
+ summary: "Start browsers in the cloud for automated browsing.",
45114
+ platformAuth: true,
45115
+ description: "Spawns cloud Chromium sessions, each exposed as a CDP WebSocket for agent-browser to drive. `create` prints one line ending in a cdpUrl — pass it straight to `agent-browser connect '<cdpUrl>'` (connect the cdpUrl, not the Chrome debug port). `list`/`get <id>`, `delete <id>`/`delete-all`, `hibernate`/`restore`. Sessions run stealth Chromium that reads as a real desktop browser. For sites that block datacenter IPs, route through a proxy: `create --proxy socks5://host:port [--proxy-user U --proxy-pass P]`. Use this on the cloud VM instead of launching a local browser.",
45116
+ versionCmd: "browser-manager-cli version 2>/dev/null | sed -n 1p",
45117
+ binary: {
45118
+ "linux-x86_64": {
45119
+ url: "https://github.com/todoforai/browser-manager/releases/download/cli-v0.1.1/browser-manager-cli-linux-x86_64",
45120
+ archive: "raw"
45121
+ },
45122
+ "linux-aarch64": {
45123
+ url: "https://github.com/todoforai/browser-manager/releases/download/cli-v0.1.1/browser-manager-cli-linux-aarch64",
45124
+ archive: "raw"
45125
+ },
45126
+ "darwin-x86_64": {
45127
+ url: "https://github.com/todoforai/browser-manager/releases/download/cli-v0.1.1/browser-manager-cli-darwin-x86_64",
45128
+ archive: "raw"
45129
+ },
45130
+ "darwin-aarch64": {
45131
+ url: "https://github.com/todoforai/browser-manager/releases/download/cli-v0.1.1/browser-manager-cli-darwin-aarch64",
45132
+ archive: "raw"
45133
+ },
45134
+ "windows-x86_64": {
45135
+ url: "https://github.com/todoforai/browser-manager/releases/download/cli-v0.1.1/browser-manager-cli-windows-x86_64.exe",
45136
+ archive: "raw"
45137
+ }
45138
+ },
45139
+ preinstallJarvis: true
45140
+ },
45141
+ "todoforai-browser": {
45142
+ category: "development",
45143
+ pkg: "@todoforai/browser",
45144
+ aliases: ["tfa-browser"],
45145
+ installer: "npm",
45146
+ preinstallCloud: true,
45147
+ label: "Browser (Extension)",
45148
+ summary: "Drive your own browser through the TODOforAI extension.",
45149
+ versionCmd: `node -p "require(require('fs').realpathSync(require('child_process').execSync('command -v todoforai-browser',{encoding:'utf8'}).trim()).replace(/\\/dist\\/index\\.js$/, '/package.json')).version" 2>/dev/null`,
45150
+ internal: true,
45151
+ preinstallJarvis: true,
45152
+ description: "Browser automation via extension (non-headless), web scraping, accessibility tree snapshots, screenshots, PDF generation"
45153
+ },
45154
+ "tfa-explore": {
45155
+ preinstallCloud: true,
45156
+ category: "development",
45157
+ pkg: "@todoforai/tfa-explore",
45158
+ installer: "npm",
45159
+ label: "Explore",
45160
+ summary: "Send a helper agent to explore a codebase and report back.",
45161
+ platformAuth: true,
45162
+ description: 'Sub-agent codebase exploration in its own context, you get only the answer: `tfa-explore "where is auth handled?" [--repo <path>]`. Prefer over manual multi-file searching.',
45163
+ versionCmd: "tfa-explore --version 2>/dev/null | sed -n 1p",
45164
+ installCmd: "npm install -g @todoforai/tfa-explore",
45165
+ internal: true
45166
+ },
45167
+ "tfa-review": {
45168
+ preinstallCloud: true,
45169
+ category: "development",
45170
+ pkg: "@todoforai/tfa-review",
45171
+ installer: "npm",
45172
+ label: "Review",
45173
+ summary: "Send a helper agent to review your code changes.",
45174
+ platformAuth: true,
45175
+ description: 'Sub-agent review of a git diff in its own context: `tfa-review "<goal>" [--repo <path>] [--against <ref>]` (default: uncommitted). Run before calling a non-trivial change done.',
45176
+ versionCmd: "tfa-review --version 2>/dev/null | sed -n 1p",
45177
+ installCmd: "npm install -g @todoforai/tfa-review",
45178
+ internal: true
45179
+ },
45180
+ "tfa-summary": {
45181
+ preinstallCloud: true,
45182
+ category: "development",
45183
+ pkg: "@todoforai/tfa-summary",
45184
+ installer: "npm",
45185
+ label: "Summary",
45186
+ summary: "Summarize files or long output with a helper agent.",
45187
+ platformAuth: true,
45188
+ description: 'Sub-agent summary so raw text never enters your context: `cat big.log | tfa-summary "what failed?"`, or `-f <file>` (repeatable).',
45189
+ versionCmd: "tfa-summary --version 2>/dev/null | sed -n 1p",
45190
+ installCmd: "npm install -g @todoforai/tfa-summary",
45191
+ internal: true
45192
+ },
45193
+ "tfa-handoff": {
45194
+ preinstallCloud: true,
45195
+ category: "development",
45196
+ pkg: "@todoforai/tfa-handoff",
45197
+ installer: "npm",
45198
+ label: "Handoff",
45199
+ summary: "Turn a long conversation into a short handover note.",
45200
+ platformAuth: true,
45201
+ description: "Compact a conversation into a handoff document for a fresh agent.",
45202
+ versionCmd: "tfa-handoff --version 2>/dev/null | sed -n 1p",
45203
+ installCmd: "npm install -g @todoforai/tfa-handoff",
45204
+ internal: true
45205
+ },
45206
+ "tfa-wait": {
45207
+ category: "development",
45208
+ pkg: "@todoforai/tfa-wait",
45209
+ installer: "npm",
45210
+ label: "Wait",
45211
+ summary: "Wait for a time or an event, then continue the task.",
45212
+ platformAuth: true,
45213
+ description: "Schedule a wake-up: `tfa-wait '<expr>' \"<message>\"` — expr is a time (`3h`, `daily@9:00`), a webhook, or a command's output; the message arrives later as a new chat message. Wait for a long job: `tfa-wait 'bench || 6h' \"Done: exit {{payload.exit}}\"` then `tfa-wait run '<cmd>' --pipe bench --on-exit &` (each stdout line = event; cancelling kills the command). Never pgrep/sleep poll loops.",
45214
+ versionCmd: "tfa-wait --version 2>/dev/null | sed -n 1p",
45215
+ installCmd: "npm install -g @todoforai/tfa-wait",
45216
+ preinstallCloud: true,
45217
+ internal: true,
45218
+ preinstallJarvis: true
45219
+ },
45220
+ "tfa-vault": {
45221
+ category: "security",
45222
+ pkg: "@todoforai/tfa-vault",
45223
+ installer: "npm",
45224
+ label: "Vault",
45225
+ summary: "Your password and secret store.",
45226
+ platformAuth: true,
45227
+ description: "The user's encrypted secret vault — using its secrets is the intended workflow. READ: when a task needs a key/token/password the user owns, `tfa-vault list` then `get <path>`; don't ask for what's already stored; never echo values. WRITE proactively: any new secret in the conversation (pasted, generated, from a login) → `put <path> field=value` (`patch` adds fields; `put` refuses an existing path — `put --force` replaces all fields on rotation). Paths: `accounts/<svc>`, `api/<svc>`, `secrets/<name>`.",
45228
+ versionCmd: "tfa-vault --version 2>/dev/null | sed -n 1p",
45229
+ installCmd: "npm install -g @todoforai/tfa-vault",
45230
+ preinstallCloud: true,
45231
+ preinstallJarvis: true
45232
+ },
45233
+ "tfa-settings": {
45234
+ category: "utility",
45235
+ pkg: "@todoforai/tfa-settings",
45236
+ installer: "npm",
45237
+ label: "Settings",
45238
+ summary: "Change your TODOforAI app preferences from the shell.",
45239
+ platformAuth: true,
45240
+ description: "The user's TODOforAI app preferences (AppSettings): `list`, `get <key>`, `set key=value …`, `keys` (documented keys). Use when asked to change theme, list style, tool-block density, thinking traces, sounds/toasts, preview columns, wallpaper etc. Values are JSON when parseable. Consent/credential/escalation keys (shellExecution, notifications, analytics, keyboard bindings, vault provider, billing address) are refused from an agent shell — tell the user to change those in Settings.",
45241
+ versionCmd: "tfa-settings --version 2>/dev/null | sed -n 1p",
45242
+ installCmd: "npm install -g @todoforai/tfa-settings",
45243
+ preinstallCloud: true,
45244
+ preinstallJarvis: true
45245
+ },
45246
+ "tfa-memory": {
45247
+ category: "productivity",
45248
+ pkg: "@todoforai/tfa-memory",
45249
+ installer: "npm",
45250
+ label: "Memory",
45251
+ summary: "Your long-term memory of notes, facts and past TODOs.",
45252
+ platformAuth: true,
45253
+ description: 'User\'s long-term memory (notes/procedures/decisions, address `source/ref`) plus every past TODO message (read-only, `todo/<todoId>:<messageId>`). READ: `search "<topic>" --mode fused` first (`lexical` = all words newest first, `semantic` = meaning; `--source todo` / `--todo <id>` narrow). stderr prints `N row(s) contain a query word` — if N exceeds what you saw, page with `--exclude <address>`. `get <address>` = full row, `around <address> --before N --after N` = surrounding dialogue. Before answering from memory, search once more with your draft\'s key facts. WRITE durable facts proactively: `add <source>/<ref> "text"` (same address updates in place; short source names like deploy-notes, preferences). `sources` lists them.',
45254
+ versionCmd: "tfa-memory --version 2>/dev/null | sed -n 1p",
45255
+ installCmd: "npm install -g @todoforai/tfa-memory",
45256
+ preinstallCloud: true,
45257
+ preinstallJarvis: true
45258
+ },
45259
+ "todoregistry-cli": {
45260
+ category: "development",
45261
+ pkg: "@todoforai/todoregistry-cli",
45262
+ installer: "npm",
45263
+ label: "TODO Registry",
45264
+ summary: "Browse ready-made business task templates.",
45265
+ description: "Reusable business task templates (SEO, email, CRM, ads, social, …): `search <q>`, `get <id>`, `list [category]`, `categories`, `create --name … --description … --body @prompt.md`. To recommend a template, don't create a todo directly — `tfa-cli recommend --template <id> --project <projectId>`.",
45266
+ versionCmd: "todoregistry-cli --version 2>/dev/null | sed -n 1p",
45267
+ installCmd: "npm install -g @todoforai/todoregistry-cli",
45268
+ preinstallCloud: true,
45269
+ internal: true,
45270
+ preinstallJarvis: true
45271
+ },
45272
+ "tfa-surface": {
45273
+ preinstallCloud: true,
45274
+ category: "utility",
45275
+ pkg: "@todoforai/tfa-surface",
45276
+ installer: "npm",
45277
+ label: "Surface",
45278
+ summary: "See and control what is on your TODOforAI screen.",
45279
+ platformAuth: true,
45280
+ description: "The user's live UI from the shell. Everything on screen is a SURFACE holding one ref (a todo id, `<todoId>:<alias>` = a shown artifact, or a url). `describe` lists them (kind, id, ref, focus, embedded-browser CDP URL; `--json`). `open overlay <todoId>` (full page), `open window <ref>` (floating), `open panel changes|preview|browser|terminal [--url]` (a todo's side panel), `open card <ref> [--title] [--group <slug>]` (a persisted board card every member sees); `close|focus <kind> <id>`. Persistent state (plans, reports, live status) → `show chat.panel <todoId> \"...md\"` (stdin ok; re-show updates in place), `clear` removes; events go in chat. `card add|list|get|rm <name>` = project HTML renderers. Exit 2 = no frontend connected.",
45281
+ versionCmd: "tfa-surface --version 2>/dev/null | sed -n 1p",
45282
+ installCmd: "npm install -g @todoforai/tfa-surface",
45283
+ internal: true
45284
+ },
45285
+ bun: {
45286
+ commodity: true,
45287
+ category: "development",
45288
+ pkg: "bun",
45289
+ installer: "system",
45290
+ label: "JS/TS Runtime",
45291
+ summary: "Run JavaScript and TypeScript code, fast.",
45292
+ versionCmd: "bun --version 2>/dev/null",
45293
+ preinstallCloud: true,
45294
+ cloudInstallCmd: "export BUN_INSTALL=/usr/local && curl -fsSL https://bun.sh/install | bash && ln -sf /usr/local/bin/bun /usr/local/bin/bunx",
45295
+ cloudVerifyCmd: "bun --version",
45296
+ internal: true,
45297
+ preinstallJarvis: true,
45298
+ description: "Run JavaScript/TypeScript, install packages, run tests (`bun`, `bunx`)."
45299
+ },
45300
+ bunx: {
45301
+ commodity: true,
45302
+ category: "development",
45303
+ pkg: "bunx",
45304
+ installer: "system",
45305
+ label: "Bun Package Runner",
45306
+ summary: "Run a JavaScript package without installing it.",
45307
+ versionCmd: "bunx --version 2>/dev/null",
45308
+ preinstallCloud: true,
45309
+ cloudVerifyCmd: "bunx --version",
45310
+ internal: true,
45311
+ description: "Run package binaries with Bun."
45312
+ },
45313
+ node: {
45314
+ commodity: true,
45315
+ category: "development",
45316
+ pkg: "node",
45317
+ installer: "system",
45318
+ label: "JavaScript Runtime",
45319
+ summary: "Run JavaScript code.",
45320
+ versionCmd: "node -p 'process.version' 2>/dev/null",
45321
+ preinstallCloud: true,
45322
+ cloudInstallCmd: "curl -fsSL https://deb.nodesource.com/setup_24.x | bash -",
45323
+ cloudAptPackages: ["nodejs"],
45324
+ cloudVerifyCmd: "node --version",
45325
+ internal: true,
45326
+ preinstallJarvis: true,
45327
+ description: "Run Node.js scripts."
45328
+ },
45329
+ python3: {
45330
+ commodity: true,
45331
+ category: "development",
45332
+ pkg: "python3",
45333
+ installer: "system",
45334
+ label: "Python",
45335
+ summary: "Run Python code.",
45336
+ versionCmd: "{ python3 --version || python --version || py --version; } 2>/dev/null | sed -n 1p",
45337
+ preinstallCloud: true,
45338
+ cloudAptPackages: ["python3"],
45339
+ cloudVerifyCmd: "python3 --version",
45340
+ internal: true,
45341
+ preinstallJarvis: true,
45342
+ description: "Run Python scripts."
45343
+ },
45344
+ npm: {
45345
+ commodity: true,
45346
+ category: "development",
45347
+ pkg: "npm",
45348
+ installer: "system",
45349
+ label: "Node Package Manager",
45350
+ summary: "Install JavaScript packages.",
45351
+ versionCmd: "npm --version 2>/dev/null",
45352
+ preinstallCloud: true,
45353
+ cloudVerifyCmd: "npm --version",
45354
+ internal: true,
45355
+ description: "Install Node packages and run package scripts."
45356
+ },
45357
+ npx: {
45358
+ commodity: true,
45359
+ category: "development",
45360
+ pkg: "npx",
45361
+ installer: "system",
45362
+ label: "Node Package Runner",
45363
+ summary: "Run a JavaScript package without installing it.",
45364
+ versionCmd: "npx --version 2>/dev/null",
45365
+ preinstallCloud: true,
45366
+ cloudVerifyCmd: "npx --version",
45367
+ internal: true,
45368
+ description: "Run npm package binaries."
45369
+ },
45370
+ pip3: {
45371
+ preinstallCloud: true,
45372
+ cloudAptPackages: ["python3-pip"],
45373
+ cloudVerifyCmd: "pip3 --version",
45374
+ commodity: true,
45375
+ category: "development",
45376
+ pkg: "pip3",
45377
+ installer: "system",
45378
+ label: "Python Package Installer",
45379
+ summary: "Install Python packages.",
45380
+ versionCmd: "pip3 --version 2>/dev/null | sed -n 1p | cut -d' ' -f1-2",
45381
+ internal: true,
45382
+ description: "Install Python packages."
45383
+ },
45384
+ uv: {
45385
+ commodity: true,
45386
+ category: "development",
45387
+ pkg: "uv",
45388
+ installer: "system",
45389
+ label: "Python Package Manager",
45390
+ summary: "Install Python packages and manage environments, fast.",
45391
+ versionCmd: "uv --version 2>/dev/null",
45392
+ preinstallCloud: true,
45393
+ cloudInstallCmd: "curl -LsSf https://astral.sh/uv/0.9.6/install.sh | env UV_INSTALL_DIR=/usr/local/bin sh",
45394
+ cloudVerifyCmd: "uv --version",
45395
+ internal: true,
45396
+ description: "Manage Python dependencies, environments, tools, and scripts (`uv`, `uvx`)."
45397
+ },
45398
+ uvx: {
45399
+ commodity: true,
45400
+ category: "development",
45401
+ pkg: "uvx",
45402
+ installer: "system",
45403
+ label: "Python Tool Runner",
45404
+ summary: "Run a Python tool without installing it.",
45405
+ versionCmd: "uvx --version 2>/dev/null",
45406
+ preinstallCloud: true,
45407
+ cloudVerifyCmd: "uvx --version",
45408
+ internal: true,
45409
+ description: "Run Python tools in isolated environments."
45410
+ },
45411
+ ripgrep: {
45412
+ commodity: true,
45413
+ category: "development",
45414
+ pkg: "ripgrep",
45415
+ installer: "binary",
45416
+ label: "Search",
45417
+ summary: "Search text across files, very fast.",
45418
+ description: "Fast recursive grep with regex, .gitignore-aware, parallel, unicode-correct",
45419
+ versionCmd: "rg --version 2>/dev/null | sed -n 1p",
45420
+ preinstallCloud: true,
45421
+ binary: {
45422
+ "linux-x86_64": {
45423
+ url: "https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz",
45424
+ archive: "tar.gz",
45425
+ extract: "ripgrep-14.1.1-x86_64-unknown-linux-musl/rg"
45426
+ },
45427
+ "linux-aarch64": {
45428
+ url: "https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep-14.1.1-aarch64-unknown-linux-gnu.tar.gz",
45429
+ archive: "tar.gz",
45430
+ extract: "ripgrep-14.1.1-aarch64-unknown-linux-gnu/rg"
45431
+ },
45432
+ "darwin-x86_64": {
45433
+ url: "https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep-14.1.1-x86_64-apple-darwin.tar.gz",
45434
+ archive: "tar.gz",
45435
+ extract: "ripgrep-14.1.1-x86_64-apple-darwin/rg"
45436
+ },
45437
+ "darwin-aarch64": {
45438
+ url: "https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep-14.1.1-aarch64-apple-darwin.tar.gz",
45439
+ archive: "tar.gz",
45440
+ extract: "ripgrep-14.1.1-aarch64-apple-darwin/rg"
45441
+ },
45442
+ "windows-x86_64": {
45443
+ url: "https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep-14.1.1-x86_64-pc-windows-msvc.zip",
45444
+ archive: "zip",
45445
+ extract: "ripgrep-14.1.1-x86_64-pc-windows-msvc/rg.exe"
45446
+ }
45447
+ },
45448
+ binName: "rg",
45449
+ internal: true,
45450
+ preinstallJarvis: true
45451
+ },
45452
+ "ast-grep": {
45453
+ category: "development",
45454
+ pkg: "ast-grep",
45455
+ installer: "binary",
45456
+ label: "Structural Search",
45457
+ summary: "Search and rewrite code by its structure, not just text.",
45458
+ description: 'Use when regex cannot distinguish syntax from text: `ast-grep -p "foo($$$)" -l ts` finds real calls while ignoring strings and comments; add `--rewrite "..."` for codemods. Prefer `rg` for literal or token search.',
45459
+ versionCmd: "ast-grep --version 2>/dev/null | sed -n 1p",
45460
+ preinstallCloud: true,
45461
+ binary: {
45462
+ "linux-x86_64": {
45463
+ url: "https://github.com/ast-grep/ast-grep/releases/download/0.44.1/app-x86_64-unknown-linux-gnu.zip",
45464
+ archive: "zip",
45465
+ extract: "ast-grep"
45466
+ },
45467
+ "linux-aarch64": {
45468
+ url: "https://github.com/ast-grep/ast-grep/releases/download/0.44.1/app-aarch64-unknown-linux-gnu.zip",
45469
+ archive: "zip",
45470
+ extract: "ast-grep"
45471
+ },
45472
+ "darwin-x86_64": {
45473
+ url: "https://github.com/ast-grep/ast-grep/releases/download/0.44.1/app-x86_64-apple-darwin.zip",
45474
+ archive: "zip",
45475
+ extract: "ast-grep"
45476
+ },
45477
+ "darwin-aarch64": {
45478
+ url: "https://github.com/ast-grep/ast-grep/releases/download/0.44.1/app-aarch64-apple-darwin.zip",
45479
+ archive: "zip",
45480
+ extract: "ast-grep"
45481
+ },
45482
+ "windows-x86_64": {
45483
+ url: "https://github.com/ast-grep/ast-grep/releases/download/0.44.1/app-x86_64-pc-windows-msvc.zip",
45484
+ archive: "zip",
45485
+ extract: "ast-grep.exe"
45486
+ }
45487
+ },
45488
+ binName: "ast-grep",
45489
+ internal: true,
45490
+ preinstallJarvis: true
45491
+ },
45492
+ fd: {
45493
+ commodity: true,
45494
+ category: "development",
45495
+ pkg: "fd-find",
45496
+ installer: "binary",
45497
+ label: "Find Files",
45498
+ summary: "Find files and folders by name.",
45499
+ description: "Fast user-friendly find replacement: regex by default, .gitignore-aware, parallel",
45500
+ versionCmd: "fd --version 2>/dev/null | sed -n 1p",
45501
+ preinstallCloud: true,
45502
+ binary: {
45503
+ "linux-x86_64": {
45504
+ url: "https://github.com/sharkdp/fd/releases/download/v10.2.0/fd-v10.2.0-x86_64-unknown-linux-musl.tar.gz",
45505
+ archive: "tar.gz",
45506
+ extract: "fd-v10.2.0-x86_64-unknown-linux-musl/fd"
45507
+ },
45508
+ "linux-aarch64": {
45509
+ url: "https://github.com/sharkdp/fd/releases/download/v10.2.0/fd-v10.2.0-aarch64-unknown-linux-gnu.tar.gz",
45510
+ archive: "tar.gz",
45511
+ extract: "fd-v10.2.0-aarch64-unknown-linux-gnu/fd"
45512
+ },
45513
+ "darwin-x86_64": {
45514
+ url: "https://github.com/sharkdp/fd/releases/download/v10.2.0/fd-v10.2.0-x86_64-apple-darwin.tar.gz",
45515
+ archive: "tar.gz",
45516
+ extract: "fd-v10.2.0-x86_64-apple-darwin/fd"
45517
+ },
45518
+ "darwin-aarch64": {
45519
+ url: "https://github.com/sharkdp/fd/releases/download/v10.2.0/fd-v10.2.0-aarch64-apple-darwin.tar.gz",
45520
+ archive: "tar.gz",
45521
+ extract: "fd-v10.2.0-aarch64-apple-darwin/fd"
45522
+ },
45523
+ "windows-x86_64": {
45524
+ url: "https://github.com/sharkdp/fd/releases/download/v10.2.0/fd-v10.2.0-x86_64-pc-windows-msvc.zip",
45525
+ archive: "zip",
45526
+ extract: "fd-v10.2.0-x86_64-pc-windows-msvc/fd.exe"
45527
+ }
45528
+ },
45529
+ binName: "fd",
45530
+ internal: true,
45531
+ preinstallJarvis: true
45532
+ },
45533
+ jq: {
45534
+ commodity: true,
45535
+ category: "development",
45536
+ pkg: "jq",
45537
+ installer: "binary",
45538
+ label: "JSON",
45539
+ summary: "Read and reshape JSON data.",
45540
+ description: "Command-line JSON processor: query, filter, transform, format JSON streams",
45541
+ versionCmd: "jq --version 2>/dev/null | sed -n 1p",
45542
+ preinstallCloud: true,
45543
+ binary: {
45544
+ "linux-x86_64": {
45545
+ url: "https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64",
45546
+ archive: "raw"
45547
+ },
45548
+ "linux-aarch64": {
45549
+ url: "https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-arm64",
45550
+ archive: "raw"
45551
+ },
45552
+ "darwin-x86_64": {
45553
+ url: "https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-macos-amd64",
45554
+ archive: "raw"
45555
+ },
45556
+ "darwin-aarch64": {
45557
+ url: "https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-macos-arm64",
45558
+ archive: "raw"
45559
+ },
45560
+ "windows-x86_64": {
45561
+ url: "https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-windows-amd64.exe",
45562
+ archive: "raw"
45563
+ }
45564
+ },
45565
+ binName: "jq",
45566
+ internal: true,
45567
+ preinstallJarvis: true
45568
+ },
45569
+ patch: {
45570
+ commodity: true,
45571
+ category: "development",
45572
+ pkg: "patch",
45573
+ installer: "system",
45574
+ preinstallCloud: true,
45575
+ label: "Apply Diff",
45576
+ summary: "Apply a diff to files.",
45577
+ description: "Apply unified/context diff patches to files; reverse, dry-run, fuzzy matching",
45578
+ versionCmd: "patch --version 2>/dev/null | sed -n 1p",
45579
+ binName: "patch",
45580
+ internal: true,
45581
+ preinstallJarvis: true
45582
+ },
45583
+ sed: {
45584
+ commodity: true,
45585
+ category: "development",
45586
+ pkg: "sed",
45587
+ installer: "system",
45588
+ preinstallCloud: true,
45589
+ label: "Replace Text",
45590
+ summary: "Find and replace text in files.",
45591
+ description: "Stream editor: in-place text substitution, line filtering, scripted edits",
45592
+ versionCmd: "sed --version 2>/dev/null | sed -n 1p",
45593
+ binName: "sed",
45594
+ internal: true,
45595
+ preinstallJarvis: true
45596
+ },
45597
+ grep: {
45598
+ commodity: true,
45599
+ category: "development",
45600
+ pkg: "grep",
45601
+ installer: "system",
45602
+ preinstallCloud: true,
45603
+ label: "Search Text",
45604
+ summary: "Search for text in files.",
45605
+ description: "Search plain text for lines matching a regex: recursive, fixed-string, context, invert, count",
45606
+ versionCmd: "grep --version 2>/dev/null | sed -n 1p",
45607
+ binName: "grep",
45608
+ internal: true,
45609
+ preinstallJarvis: true
45610
+ },
45611
+ "wordpress-api": {
45612
+ preinstallCloud: true,
45613
+ category: "development",
45614
+ pkg: "@todoforai/wordpress-api",
45615
+ installer: "npm",
45616
+ label: "WordPress",
45617
+ summary: "Read and publish WordPress posts and pages.",
45618
+ statusCmd: `wordpress-api whoami 2>/dev/null | sed -n 's/.*"slug"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' | grep -m10 .`,
45619
+ loginCmd: "wordpress-api auth --site <url> --user <username> --password <app_password>",
45620
+ credentialPaths: ["~/.config/wordpress-api/credentials.json"],
45621
+ description: "WordPress REST API CLI. `wordpress-api auth --site https://example.com --user <user> --password '<application password>'` (application password, not the login password). Reading published content needs no auth. `posts list|get|create|update|delete`, `pages`, `media`, `categories`. `posts create` saves a draft unless --status publish. On Elementor/JetEngine sites content.rendered comes back empty — fetch the post's public URL for the body.",
45622
+ versionCmd: "wordpress-api --version 2>/dev/null | sed -n 1p"
45623
+ },
45624
+ "facebook-pages-api": {
45625
+ preinstallCloud: true,
45626
+ category: "social",
45627
+ pkg: "@todoforai/facebook-pages-api",
45628
+ installer: "npm",
45629
+ label: "Facebook Pages",
45630
+ summary: "Post to your Facebook Page and handle comments.",
45631
+ statusCmd: `facebook-pages-api whoami 2>/dev/null | sed -n 's/.*"name"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' | grep -m10 .`,
45632
+ loginCmd: "facebook-pages-api auth --page-id <id> --page-token <token>",
45633
+ credentialPaths: ["~/.config/facebook-pages-api/credentials.json"],
45634
+ description: "Facebook Pages CLI (Meta Graph API). `auth --page-id <id> --page-token <token>` (or env FB_PAGE_ID/FB_PAGE_TOKEN). `feed`, `comments`, `insights`, `conversations`, `media`, and `request <GET|POST|DELETE> <path> [--data <json>]` = any Graph edge with the page token attached (use instead of curl). Writes are live: `feed create --published false` for tests; `comments reply` posts publicly — get human approval. Follower totals: `whoami --fields followers_count,fan_count` (many classic insights metrics are retired). `--help` for subcommands.",
45635
+ versionCmd: "facebook-pages-api --version 2>/dev/null | sed -n 1p"
45636
+ },
45637
+ "ews-api": {
45638
+ category: "email",
45639
+ pkg: "ews-api",
45640
+ installer: "pip",
45641
+ label: "Exchange (EWS)",
45642
+ summary: "Read and write mail on a company Exchange server.",
45643
+ statusCmd: `ews-api whoami 2>/dev/null | sed -n 's/.*"email"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' | grep -m10 .`,
45644
+ loginCmd: "ews-api auth --email <email> --username <username> --server <server> --password <password>",
45645
+ credentialPaths: ["~/.config/ews-api/credentials.json"],
45646
+ description: "On-prem Microsoft Exchange mail over EWS (cloud/M365 → outlook-api). `auth --email <e> --username 'DOMAIN\\\\user' --server <host>` (NTLM; password prompt or `--password-stdin`; --server required). Multi-mailbox: `whoami`, `--account <email>`/EWS_ACCOUNT, `default`, `logout`; EWS_EMAIL/EWS_USERNAME/EWS_PASSWORD/EWS_SERVER for unattended runs. `list`, `search <q>`, `read <id>`, `draft`, `reply <id>` — draft/reply stay in Drafts unless --send. Password stored plaintext in ~/.config/ews-api (0600); re-auth after rotation.",
45647
+ versionCmd: "ews-api --version 2>/dev/null | sed -n 1p",
45648
+ logoutCmd: "ews-api logout {{ACCOUNT}}"
45649
+ },
45650
+ "outlook-api": {
45651
+ preinstallCloud: true,
45652
+ category: "email",
45653
+ pkg: "@todoforai/outlook-api",
45654
+ installer: "npm",
45655
+ label: "Outlook (Graph)",
45656
+ summary: "Read and write your Outlook mail.",
45657
+ statusCmd: `outlook-api whoami 2>/dev/null | sed -n 's/.*"userPrincipalName"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' | grep -m10 .`,
45658
+ loginCmd: "outlook-api auth",
45659
+ credentialPaths: ["~/.config/outlook-api/config.json"],
45660
+ description: "Outlook/Hotmail/M365 mail over Microsoft Graph (on-prem Exchange → ews-api). `auth` = device-code flow; `--tenant consumers` (default, personal) | `organizations`. list/search/read/attachments are read-only; `draft`/`reply` stay in Drafts unless --send; `send <draftId>`. `get <path>` = raw Graph GET. JSON out.",
45661
+ versionCmd: "outlook-api --version 2>/dev/null | sed -n 1p",
45662
+ deviceCodePattern: "enter the code ([A-Z0-9]+)",
45663
+ oauthUrlPattern: "(https://(?:www\\.microsoft\\.com/link|login\\.microsoft\\.com/device))",
45664
+ canStreamLogin: true
45665
+ },
45666
+ neonctl: {
45667
+ category: "database",
45668
+ pkg: "neonctl",
45669
+ installer: "npm",
45670
+ label: "Neon Postgres",
45671
+ summary: "Manage your Neon Postgres databases and branches.",
45672
+ statusCmd: `test -f ~/.config/neon/credentials.json && neonctl me --output json 2>/dev/null | sed -n 's/.*"email"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' | grep -m10 .`,
45673
+ loginCmd: "neonctl auth",
45674
+ oauthUrlPattern: "(https://oauth2\\.neon\\.tech/oauth2/auth\\S+)",
45675
+ credentialPaths: ["~/.config/neon/credentials.json"],
45676
+ description: "Manage Neon Postgres branches and run SQL: `neonctl projects list`, `neonctl branches create`, `neonctl connection-string`. Aliased as `neon`.",
45677
+ versionCmd: "neonctl --version 2>/dev/null | sed -n 1p",
45678
+ aliases: ["neon"],
45679
+ canStreamLogin: true
45680
+ },
45681
+ m365: {
45682
+ preinstallCloud: true,
45683
+ category: "productivity",
45684
+ pkg: "@pnp/cli-microsoft365",
45685
+ installer: "npm",
45686
+ label: "Microsoft 365",
45687
+ summary: "Work with Microsoft 365 mail, Teams, SharePoint and users.",
45688
+ statusCmd: `m365 status --output text 2>/dev/null | tr -d '"' | grep -vi 'logged out' | grep -m1 .`,
45689
+ loginCmd: "m365 login --authType deviceCode --appId 04b07795-8ddb-461a-bbee-02f9e1bf7b46",
45690
+ deviceCodePattern: "enter the code ([A-Z0-9]{8,}) to authenticate",
45691
+ oauthUrlPattern: "open the page (https://\\S+) and enter",
45692
+ credentialPaths: ["~/.cli-m365-msal.json", "~/.cli-m365-connection.json"],
45693
+ description: "Microsoft 365 from the shell: `m365 outlook message list`, `m365 teams team list`, `m365 spo site list`. Device-code login; add `--output json` for parseable output.",
45694
+ versionCmd: `m365 version 2>/dev/null | tr -d '"' | sed -n 1p`
45695
+ },
45696
+ hubspot: {
45697
+ preinstallCloud: true,
45698
+ category: "marketing",
45699
+ pkg: "@hubspot/cli",
45700
+ installer: "npm",
45701
+ label: "HubSpot",
45702
+ summary: "Work with your HubSpot contacts, deals and content.",
45703
+ binName: "hs",
45704
+ statusCmd: "hs account list >/dev/null 2>&1 && echo authenticated",
45705
+ loginCmd: "hs account auth",
45706
+ oauthUrlPattern: "(https://app\\.hubspot\\.com/l/personal-access-key\\S+)",
45707
+ loginNeedsPty: true,
45708
+ credentialPaths: ["~/.hscli/config.yml"],
45709
+ description: "Invoked as `hs`. `hs api <endpoint>` makes authenticated calls to any HubSpot API (contacts, deals, companies) — prefer it over raw curl.",
45710
+ versionCmd: "hs --version 2>/dev/null | sed -n 1p",
45711
+ canStreamLogin: true
45712
+ },
45713
+ "posthog-cli": {
45714
+ preinstallCloud: true,
45715
+ category: "monitoring",
45716
+ pkg: "@posthog/cli",
45717
+ installer: "npm",
45718
+ label: "PostHog",
45719
+ summary: "Query PostHog product analytics and manage feature flags.",
45720
+ statusCmd: `grep -q '"token"' "\${POSTHOG_HOME:-$HOME/.posthog}/credentials.json" 2>/dev/null && echo authenticated`,
45721
+ loginCmd: "posthog-cli login",
45722
+ loginTerminalHint: "Interactive only: run `posthog-cli login` in a terminal on this host (it asks for host, project and a personal API key), or set `POSTHOG_CLI_API_KEY` + `POSTHOG_CLI_PROJECT_ID`.",
45723
+ credentialPaths: ["~/.posthog/credentials.json"],
45724
+ description: "Official PostHog CLI. `posthog-cli api tools` lists the agent-first API tool catalog, `posthog-cli api call <tool> '<json>'` runs one (SQL queries, insights, flags). Also uploads sourcemaps/dSYM. Env alternative: POSTHOG_CLI_API_KEY + POSTHOG_CLI_PROJECT_ID.",
45725
+ versionCmd: "posthog-cli --version 2>/dev/null | sed -n 1p"
45726
+ },
45727
+ canva: {
45728
+ category: "design",
45729
+ pkg: "@canva/cli",
45730
+ installer: "npm",
45731
+ label: "Canva",
45732
+ summary: "Build and deploy Canva apps.",
45733
+ statusCmd: "test -s ~/.canva-cli/credentials && echo authenticated",
45734
+ loginCmd: "canva login --mode manual --lite",
45735
+ loginNeedsPty: true,
45736
+ stdinPaste: true,
45737
+ credentialPaths: ["~/.canva-cli/credentials"],
45738
+ description: "Official Canva CLI — app-development focused: `canva apps create/list/preview`. `canva mcp` starts the Canva Developer MCP server. For design/export automation use the Canva Connect REST API.",
45739
+ versionCmd: "canva --version 2>/dev/null | sed -n 1p",
45740
+ oauthUrlPattern: "OPEN_URL: (https://www\\.canva\\.com/api/oauth/authorize\\S+)"
45741
+ },
45742
+ webflow: {
45743
+ category: "development",
45744
+ pkg: "@webflow/webflow-cli",
45745
+ installer: "npm",
45746
+ label: "Webflow",
45747
+ summary: "Manage Webflow sites, CMS content and assets.",
45748
+ statusCmd: "webflow auth status --skip-update-check >/dev/null 2>&1 && echo authenticated",
45749
+ loginCmd: "webflow auth login --skip-update-check",
45750
+ loginNeedsPty: true,
45751
+ oauthUrlPattern: "(https://webflow\\.com/oauth/webflow/initiate\\S+)",
45752
+ credentialPaths: ["~/.config/webflow"],
45753
+ description: "Manage Webflow sites from the shell: `webflow sites list`, `webflow cms ...`, `webflow assets ...`. Add `--skip-update-check` in scripts.",
45754
+ versionCmd: "webflow --version 2>/dev/null | sed -n 1p"
45755
+ },
45756
+ readwise: {
45757
+ category: "productivity",
45758
+ pkg: "@readwise/cli",
45759
+ installer: "npm",
45760
+ label: "Readwise",
45761
+ summary: "Search and save articles and highlights in Readwise.",
45762
+ statusCmd: "readwise whoami >/dev/null 2>&1 && readwise whoami 2>/dev/null | sed 's/\\x1b\\[[0-9;]*m//g' | grep -m1 .",
45763
+ loginCmd: "readwise login",
45764
+ credentialPaths: ["~/.readwise-cli.json"],
45765
+ description: "Official Readwise/Reader CLI. `readwise search <query>`, `readwise list`, `readwise save <url>`; supports JSON output for piping and a read-only mode for agents. Token login for CI: `readwise login-with-token <token>` (get one at readwise.io/access_token).",
45766
+ versionCmd: "readwise --version 2>/dev/null | sed -n 1p",
45767
+ oauthUrlPattern: "OPEN_URL: (https://readwise\\.io/o/authorize\\S+)",
45768
+ oauthCallbackPort: 6274
45769
+ },
45770
+ granola: {
45771
+ preinstallCloud: true,
45772
+ category: "productivity",
45773
+ pkg: "granola-cli",
45774
+ installer: "npm",
45775
+ binName: "granola",
45776
+ label: "Granola",
45777
+ summary: "Read your Granola meeting notes and transcripts.",
45778
+ statusCmd: `granola auth status -o json 2>/dev/null | sed -n 's/.*"authenticated": *true.*/authenticated/p' | grep -m1 .`,
45779
+ loginCmd: "granola auth login",
45780
+ loginTerminalHint: "Imports the Granola desktop app's session: install Granola on this host, sign in there, then run `granola auth login`.",
45781
+ credentialPaths: ["~/.config/keyring", "~/.config/granola"],
45782
+ versionCmd: "granola --version 2>/dev/null | sed -n 1p",
45783
+ description: "Community Granola CLI (read-only). `granola meeting list [-l N] [--since <date>] [-s <query>] [-a <attendee>] -o json`, `granola meeting notes|enhanced|transcript <id>`, `granola meeting export <id>`; `granola folder list`, `granola workspace list`. `-o json|yaml|toon` for piping. Login imports credentials from the installed Granola desktop app (`granola auth login`) — desktop app must be logged in on the same machine; transcripts need a paid Granola plan."
45784
+ },
45785
+ trello: {
45786
+ preinstallCloud: true,
45787
+ category: "productivity",
45788
+ pkg: "trello-cli",
45789
+ installer: "npm",
45790
+ binName: "trello",
45791
+ label: "Trello",
45792
+ summary: "Manage your Trello boards, lists and cards.",
45793
+ statusCmd: "trello board:list --format json </dev/null >/dev/null 2>&1 && echo authenticated",
45794
+ loginCmd: "trello auth:api-key <key> && trello auth:token <token>",
45795
+ credentialPaths: [
45796
+ "~/.trello-cli"
45797
+ ],
45798
+ versionCmd: "trello --version 2>/dev/null | sed -n 1p",
45799
+ description: "Community Trello CLI (mheap/trello-cli, no official one exists). Auth is a Trello API key + token from https://trello.com/app-key — `trello auth:api-key <key>` then `trello auth:token <token>`. `board:list`, `list:list --board <name>`, `card:list --board <b> --list <l>`, `card:create --board <b> --list <l> --name <n> [--description ..] [--due <date>]`, `card:move`, `card:comment`, `label:*`, `search <q>`. Add `--format json` for piping. Names resolve via a local cache; run `trello board:list` once after login to warm it."
45800
+ },
45801
+ mapps: {
45802
+ category: "development",
45803
+ pkg: "@mondaycom/apps-cli",
45804
+ installer: "npm",
45805
+ label: "monday.com",
45806
+ summary: "Build and deploy monday.com apps.",
45807
+ statusCmd: "grep -qsE '[A-Za-z0-9]' ~/.config/mapps/.mappsrc ~/.mappsrc && echo authenticated",
45808
+ loginCmd: "mapps init -t <api_token>",
45809
+ credentialPaths: ["~/.config/mapps/.mappsrc", "~/.mappsrc"],
45810
+ description: "Invoked as `mapps` — app development and monday-code deployments only; `mapps init -t <token>` stores a monday.com API token in ~/.mappsrc. To read/write board DATA use the monday.com GraphQL API (api.monday.com/v2) with that same token.",
45811
+ versionCmd: "mapps --version 2>/dev/null | sed -n 1p"
45812
+ },
45813
+ "exa-cli": {
45814
+ preinstallCloud: true,
45815
+ category: "utility",
45816
+ pkg: "exa-cli",
45817
+ installer: "npm",
45818
+ label: "Exa Search",
45819
+ summary: "Search the web and pull page contents, built for AI.",
45820
+ binName: "exa",
45821
+ statusCmd: 'test -n "$EXA_API_KEY" && echo authenticated',
45822
+ credentialPaths: [],
45823
+ description: "Invoked as `exa`. `exa search <query>`, `exa answer <question>`, `exa contents <url>`, `exa find-similar <url>` — JSON by default. Needs EXA_API_KEY in the environment (get one at dashboard.exa.ai); store it in the vault.",
45824
+ versionCmd: "exa --version 2>/dev/null | sed -n 1p"
45825
+ },
45826
+ mobbin: {
45827
+ category: "design",
45828
+ pkg: "mobbin",
45829
+ installer: "npm",
45830
+ label: "Mobbin",
45831
+ summary: "Browse real app and website design examples.",
45832
+ statusCmd: "mobbin auth print-cookie 2>/dev/null | grep -E '[A-Za-z0-9]' >/dev/null && echo authenticated",
45833
+ loginCmd: "mobbin auth save-cookie",
45834
+ loginTerminalHint: "Log in to mobbin.com in a browser on this host, then run `mobbin auth save-cookie`: it imports that browser's cookies.",
45835
+ credentialPaths: ["~/.config/mobbin/cookie.txt", "~/.config/mobbin/storage-state.json"],
45836
+ description: 'Unofficial CLI over Mobbin\'s undocumented web endpoints. `mobbin search --query "login" --platform ios`. Auth reuses browser cookies: log into mobbin.com in Chrome, then `mobbin auth save-cookie`.',
45837
+ versionCmd: "mobbin --version 2>/dev/null | sed -n '1,2p' | tr '\\n' ' '"
45838
+ },
45839
+ "blender-mcp": {
45840
+ preinstallCloud: true,
45841
+ packages: ["blender-mcp==1.9.1"],
45842
+ category: "design",
45843
+ pkg: "blender-mcp",
45844
+ installer: "pip",
45845
+ label: "Blender",
45846
+ summary: "Model, texture and render 3D scenes in a running Blender.",
45847
+ statusCmd: `command -v blender-mcp >/dev/null && mcporter call blender.get_addon_status --output json 2>/dev/null | grep -o '"blender_version": "[^"]*"' | grep -m1 .`,
45848
+ loginNoBrowser: true,
45849
+ loginTerminalHint: "Needs Blender (blender.org) open on this host: in Blender, Edit → Preferences → Add-ons, enable `Blender MCP`, then press N in the 3D view → BlenderMCP tab → Connect / Start MCP server. Then press Connect here.",
45850
+ loginCmd: `blender-mcp install-addon >/dev/null 2>&1; command -v mcporter >/dev/null 2>&1 || npm install -g --prefix "$HOME/.local" mcporter; mcporter config add blender --command blender-mcp --env BLENDER_MCP_DISABLE_TELEMETRY=1 --description 'Blender 3D via ahujasid/blender-mcp addon' --scope home`,
45851
+ logoutCmd: "cd ~ && mcporter config remove blender",
45852
+ credentialPaths: ["~/.mcporter/mcporter.json"],
45853
+ description: "Blender 3D via the ahujasid/blender-mcp server through mcporter: `mcporter list blender --schema`, `mcporter call blender.<tool> key=value` (`code=@file.py`; every tool needs `user_prompt`). Workhorse: execute_blender_code (any bpy Python); also scene/object info, viewport screenshot (`--save-images <dir>`), PolyHaven/Sketchfab import. Needs a GUI Blender with the addon server started (socket :9876; headless has none). 'Could not connect' → `blender-mcp install-addon`, enable in Preferences → Add-ons, N-panel → Start MCP Server (scripted: `blender --python s.py`, s.py registers a `bpy.app.timers` calling `bpy.ops.blendermcp.start_server()`). Code runs unguarded: save first (`bpy.ops.wm.save_mainfile()`), approve destructive ops.",
45854
+ versionCmd: `{ python -c 'from importlib.metadata import version; print(version("blender-mcp"))' || python3 -c 'from importlib.metadata import version; print(version("blender-mcp"))'; } 2>/dev/null`
45855
+ },
45856
+ "google-ads": {
45857
+ category: "marketing",
45858
+ pkg: "google-ads",
45859
+ packages: [
45860
+ "google-ads==32.0.0"
45861
+ ],
45862
+ installer: "pip",
45863
+ label: "Google Ads",
45864
+ summary: "Query and manage Google Ads campaigns, keywords and conversions.",
45865
+ loginCmd: `printf 'developer_token: %s\\nclient_id: %s\\nclient_secret: %s\\nrefresh_token: %s\\nlogin_customer_id: %s\\nuse_proto_plus: true\\n' <developer_token> <client_id> <client_secret> <refresh_token> <mcc_id_digits_only> > "$HOME/google-ads.yaml" && python3 -c 'from google.ads.googleads.client import GoogleAdsClient; GoogleAdsClient.load_from_storage(); print("ok")'`,
45866
+ credentialPaths: [
45867
+ "~/google-ads.yaml"
45868
+ ],
45869
+ statusCmd: `python3 -c 'from google.ads.googleads.client import GoogleAdsClient; c=GoogleAdsClient.load_from_storage(); print(c.login_customer_id or "authenticated")' 2>/dev/null | grep -m1 .`,
45870
+ versionCmd: "python3 -c 'import google.ads.googleads as g; print(g.VERSION)' 2>/dev/null",
45871
+ cloudVerifyCmd: "python3 -c 'import google.ads.googleads'",
45872
+ description: "Official Google Ads API client library (Python) — there is no official CLI, so call it via `python3 -c` or a script. Config `~/google-ads.yaml` (developer_token, client_id/secret, refresh_token, login_customer_id = MCC digits). Mint refresh_token with the bundled `google-oauthlib-tool --client-secrets <json> --scope https://www.googleapis.com/auth/adwords --save --headless` (or examples/authentication/generate_user_credentials.py). Read: `client.get_service('GoogleAdsService').search_stream(customer_id=<digits>, query=<GAQL>)`. Write: `<X>Service.mutate_<x>s(customer_id, operations, validate_only=True)` first, then real. Keyword ideas: `KeywordPlanIdeaService.generate_keyword_ideas`. Offline conversions: `ConversionUploadService.upload_click_conversions`. Customer ids are digits without dashes."
45873
+ },
45874
+ "cua-driver": {
45875
+ category: "development",
45876
+ pkg: "cua-driver",
45877
+ installer: "system",
45878
+ label: "Desktop Control",
45879
+ summary: "Click, type and read native desktop apps without taking over the mouse or focus.",
45880
+ installCmd: 'curl -fsSL https://cua.ai/driver/install.sh | CUA_DRIVER_NO_MODIFY_PATH=1 CUA_DRIVER_BIN_DIR="$HOME/.todoforai/tools/bin" bash',
45881
+ uninstallCmd: 'curl -fsSL https://cua.ai/driver/uninstall.sh | bash; rm -f "$HOME/.todoforai/tools/bin/cua-driver" "$HOME/.todoforai/tools/bin/cua-cursor-theme"; hash -r 2>/dev/null; ! command -v cua-driver >/dev/null 2>&1 || { echo "cua-driver is still on PATH: $(command -v cua-driver)"; exit 1; }',
45882
+ installCmdWindows: "powershell.exe -NoProfile -ExecutionPolicy Bypass -Command '$ProgressPreference='\\''SilentlyContinue'\\''; $ErrorActionPreference='\\''Stop'\\''; [Net.ServicePointManager]::SecurityProtocol=[Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12; $t=Join-Path $env:USERPROFILE '\\''.todoforai\\tools'\\''; $d=Join-Path $t '\\''bin'\\''; New-Item -ItemType Directory -Force -Path $d | Out-Null; $env:CUA_DRIVER_RS_INSTALL_DIR=Join-Path $t '\\''cua-driver\\bin'\\''; $f=Join-Path $env:TEMP '\\''cua-driver-install.ps1'\\''; Invoke-RestMethod -UseBasicParsing https://cua.ai/driver/install.ps1 | Set-Content -LiteralPath $f -Encoding UTF8; & $f -NoAutoStart -NoPathUpdate; if($LASTEXITCODE){exit $LASTEXITCODE}; $u=New-Object Text.UTF8Encoding $false; [IO.File]::WriteAllText((Join-Path $d '\\''cua-driver'\\''), \"#!/bin/sh`nexec `\"`$(dirname `\"`$0`\")/../cua-driver/bin/cua-driver.exe`\" `\"`$@`\"`n\", $u); [IO.File]::WriteAllText((Join-Path $d '\\''cua-driver.cmd'\\''), \"@echo off`r`n`\"%~dp0..\\cua-driver\\bin\\cua-driver.exe`\" %*`r`n\", $u)'",
45883
+ uninstallCmdWindows: "powershell.exe -NoProfile -ExecutionPolicy Bypass -Command '$ErrorActionPreference='\\''Continue'\\''; $t=Join-Path $env:USERPROFILE '\\''.todoforai\\tools'\\''; $d=Join-Path $t '\\''bin'\\''; $j=Join-Path $t '\\''cua-driver\\bin'\\''; $exe=Join-Path $j '\\''cua-driver.exe'\\''; if(Test-Path -LiteralPath $exe){& $exe stop 2>$null | Out-Null}; Stop-Process -Name cua-driver -Force -ErrorAction SilentlyContinue; $env:CUA_DRIVER_RS_UNINSTALL_FORCE='\\''1'\\''; $env:CUA_DRIVER_RS_INSTALL_DIR=$j; Invoke-RestMethod -UseBasicParsing https://cua.ai/driver/uninstall.ps1 | Invoke-Expression; Remove-Item -Force -ErrorAction SilentlyContinue (Join-Path $d '\\''cua-driver'\\''), (Join-Path $d '\\''cua-driver.cmd'\\''); if(Test-Path -LiteralPath $j){[IO.Directory]::Delete($j,$false)}; Remove-Item -Recurse -Force -ErrorAction SilentlyContinue (Join-Path $t '\\''cua-driver'\\'')'",
45884
+ versionCmd: "cua-driver --version 2>/dev/null | sed -n 1p",
45885
+ description: 'Native desktop automation (macOS/Windows/Linux X11; Wayland only via AT-SPI, no window screenshots) through the accessibility tree — actions land in the BACKGROUND without moving the user\'s focus or pointer. Needs a daemon: `cua-driver status || (cua-driver serve --no-overlay >/dev/null 2>&1 &)`; on macOS grant permissions first with `cua-driver permissions grant`; on Windows the daemon must run in the interactive desktop session (`cua-driver autostart enable` registers a logon task). Loop: `cua-driver call list_windows \'{}\'` → pid+window_id; `cua-driver call get_window_state \'{"pid":P,"window_id":W,"include_screenshot":false}\'` → `elements[]` with `element_token`; act with `cua-driver call click \'{"pid":P,"window_id":W,"element_token":"s..:N"}\'`, `type_text {..,"text":".."}`, `hotkey {..,"keys":["ctrl","s"]}`, `press_key`, `scroll`, `invoke_menu`. Tokens are snapshot-bound: re-run get_window_state after EVERY action (stale token = refusal). Window screenshot: add `--screenshot-out-file out.png` (`max_dimension` to shrink). Responses say `effect:unverifiable` — verify from a fresh tree, not from the click reply. `delivery_mode:"foreground"` steals focus; use only when asked. Chrome/Electron trees are near-empty — use agent-browser for web pages. `cua-driver list-tools` / `describe <tool>` for the full surface; `cua-driver mcp` exposes the same over stdio MCP.'
45886
+ }
45887
+ };
45888
+
45889
+ // ../packages/shared-fbe/src/toolCatalog.ts
45890
+ var TOOL_CATALOG = tool_catalog_default;
45891
+ // ../packages/shared-fbe/src/toolInstallCommand.ts
45892
+ var KEY_OF = new Map(Object.entries(TOOL_CATALOG).map(([k, e]) => [e, k]));
45893
+ var NODE_MIN_MAJOR = 24;
45894
+ var NODE_OK = `command -v npm >/dev/null 2>&1 && node -e 'process.exit(+process.versions.node.split(".")[0]>=${NODE_MIN_MAJOR}?0:1)' >/dev/null 2>&1`;
45895
+ // ../packages/shared-fbe/src/model_promos.json
44396
45896
  var model_promos_default = {
44397
45897
  _doc: [
44398
45898
  "Single source of truth for TODOforAI promotional model discounts.",
@@ -44434,10 +45934,10 @@ var model_promos_default = {
44434
45934
  ]
44435
45935
  };
44436
45936
 
44437
- // ../../../home/hm/repo/todoforai/packages/shared-fbe/src/modelPromos.ts
45937
+ // ../packages/shared-fbe/src/modelPromos.ts
44438
45938
  var BOOST_FACTOR = model_promos_default.boost_remaining_price_factor;
44439
45939
  var MODEL_PROMOS = model_promos_default.promos;
44440
- // ../../../home/hm/repo/todoforai/packages/shared-fbe/src/realtime/topics.ts
45940
+ // ../packages/shared-fbe/src/realtime/topics.ts
44441
45941
  var TOPICS = {
44442
45942
  ["todo:new" /* NEW_TODO */]: {
44443
45943
  channel: (p) => SERVER_TO_FRONTENDS.project.new_todo(p.projectId, p.privateOwnerId),
@@ -44604,12 +46104,12 @@ var TOPICS = {
44604
46104
  audience: "frontend"
44605
46105
  }
44606
46106
  };
44607
- // ../../../home/hm/repo/todoforai/packages/shared-fbe/src/modelNames.ts
46107
+ // ../packages/shared-fbe/src/modelNames.ts
44608
46108
  var LEGACY_MODEL_MAPPING = {
44609
46109
  claude: DEFAULT_MODEL.slice(DEFAULT_MODEL.indexOf(":") + 1),
44610
46110
  "gemini-pro": "google/gemini-3.1-pro-preview"
44611
46111
  };
44612
- // ../../../home/hm/repo/todoforai/packages/shared-credentials/src/index.ts
46112
+ // ../packages/shared-credentials/src/index.ts
44613
46113
  function normalizeApiUrl2(url) {
44614
46114
  if (url.startsWith("localhost"))
44615
46115
  return `http://${url}`;
@@ -44623,7 +46123,7 @@ import { parseArgs } from "util";
44623
46123
  // package.json
44624
46124
  var package_default = {
44625
46125
  name: "@todoforai/cli",
44626
- version: "0.1.55",
46126
+ version: "0.1.57",
44627
46127
  type: "module",
44628
46128
  bin: {
44629
46129
  "todoforai-cli": "bin/todoforai-cli.js",
@@ -44693,7 +46193,7 @@ Examples:
44693
46193
  tfa-cli recommend --template <id> # Add a template as a recommendation card
44694
46194
  tfa-cli inbox [needs-you|messages|activity] [--unread] # Read the notification feed (the bell)
44695
46195
  tfa-cli inbox seen [tab] # Mark notifications read
44696
- tfa-cli notify "Title" "message" [--href /t/<id>] # Note to YOUR OWN user only (Messages tab + phone push); no --to
46196
+ tfa-cli notify [--to <email>] "Title" "message" [--href /t/<id>] # Note to your own user, or --to a project member (Messages tab + phone push)
44697
46197
  tfa-cli claim mint --seed <projectId> [--emails a@x,b@y] [--ttl <sec>] # Mint /claim/<token> links for a project you own
44698
46198
  tfa-cli next [--direction "<text>"] # Analyzer growth recommendation cards
44699
46199
  tfa-cli <command> help # Details per command
@@ -44811,6 +46311,7 @@ function parseCliArgs() {
44811
46311
  ttl: { type: "string" },
44812
46312
  subject: { type: "string" },
44813
46313
  href: { type: "string" },
46314
+ to: { type: "string" },
44814
46315
  unread: { type: "boolean", default: false },
44815
46316
  limit: { type: "string" },
44816
46317
  resume: { type: "string", short: "r" },
@@ -48031,6 +49532,7 @@ tfa-cli project — edit the current project
48031
49532
 
48032
49533
  Usage:
48033
49534
  tfa-cli project list Projects you can access (* = default)
49535
+ tfa-cli project members Who is on it (notify --to <email> reaches them)
48034
49536
  tfa-cli project set <field=value>… name=… description=…
48035
49537
  (isPublic refused from an agent shell)
48036
49538
  tfa-cli project default Make it the project you land on at /
@@ -48132,6 +49634,17 @@ async function projectCommand(api, positionals, args, projectId) {
48132
49634
  }
48133
49635
  if (!projectId)
48134
49636
  fail(NO_PROJECT);
49637
+ if (sub === "members") {
49638
+ const members = await api.getProjectMembers(projectId);
49639
+ if (args.json) {
49640
+ console.log(JSON.stringify(members, null, 2));
49641
+ return;
49642
+ }
49643
+ for (const m of members)
49644
+ process.stderr.write(` ${m.name ?? ""} ${m.email ?? ""} ${DIM}${m.id}${RESET}
49645
+ `);
49646
+ return;
49647
+ }
48135
49648
  if (sub === "set") {
48136
49649
  if (!rest.length)
48137
49650
  fail("Usage: tfa-cli project set <field=value>…");
@@ -49064,16 +50577,19 @@ Filters (same as the bell's chips):
49064
50577
  }
49065
50578
  function printNotifyHelp() {
49066
50579
  process.stderr.write(`
49067
- tfa-cli notify — leave a note for YOUR user (their Messages tab + phone push)
50580
+ tfa-cli notify — leave a note in someone's Messages inbox (+ phone push)
49068
50581
 
49069
50582
  Usage:
49070
50583
  tfa-cli notify "Title" "message" [--href /t/<todo-id>] [--subject <key>] [--json]
50584
+ tfa-cli notify --to <email> "Title" "message" [--project <id>]
49071
50585
  echo "message" | tfa-cli notify "Title"
49072
50586
 
49073
- Recipient: ALWAYS the user this key belongs to (the person you work for). There is no --to:
49074
- it cannot message a teammate, a customer or any other account. Shown as from your agent.
50587
+ Recipient: your own user by default. --to <email> reaches a TEAMMATE: someone who is a member
50588
+ of the current project (--project, $TODOFORAI_PROJECT_ID or your default), shown as from you.
50589
+ Anyone else is refused. List them with: tfa-cli project members
49075
50590
  --subject dedupes: the same subject is delivered once (default: hash of title+message,
49076
- so a retry never double-notifies). --href must be an in-app path. Max 10 new notes/day.
50591
+ so a retry never double-notifies). --href must be an in-app path.
50592
+ Limits: 10 notes/day to yourself, 20/day to teammates.
49077
50593
  `);
49078
50594
  }
49079
50595
  function tierArg(name) {
@@ -49125,12 +50641,22 @@ async function inboxCommand(api, positionals, args) {
49125
50641
  console.log(` ${DIM}${e.href}${RESET}`);
49126
50642
  }
49127
50643
  }
49128
- var RECIPIENT_FLAGS = ["--to", "--email", "--user", "--recipient", "--cc"];
49129
- async function notifyCommand(api, positionals, args) {
49130
- const argv = process.argv.slice(2);
49131
- const bad = argv.find((a) => RECIPIENT_FLAGS.some((f) => a === f || a.startsWith(`${f}=`)));
50644
+ var OTHER_RECIPIENT_FLAGS = ["--email", "--user", "--recipient", "--cc"];
50645
+ async function notifyCommand(api, positionals, args, projectId) {
50646
+ const bad = process.argv.slice(2).find((a) => OTHER_RECIPIENT_FLAGS.some((f) => a === f || a.startsWith(`${f}=`)));
49132
50647
  if (bad) {
49133
- process.stderr.write(`${RED}notify has no recipient option (${bad.split("=")[0]}): it only reaches the user this key belongs to — your own user. Messaging other people is not supported.${RESET}
50648
+ process.stderr.write(`${RED}Unknown option ${bad.split("=")[0]}: use --to <email> for a project teammate${RESET}
50649
+ `);
50650
+ process.exit(2);
50651
+ }
50652
+ const to = args.to === undefined ? undefined : String(args.to).trim();
50653
+ if (to === "") {
50654
+ process.stderr.write(`${RED}--to is empty: pass a teammate's email, or drop --to to notify your own user${RESET}
50655
+ `);
50656
+ process.exit(2);
50657
+ }
50658
+ if (to && !projectId) {
50659
+ process.stderr.write(`${RED}--to needs a project: pass --project <id> (the recipient must be a member of it)${RESET}
49134
50660
  `);
49135
50661
  process.exit(2);
49136
50662
  }
@@ -49142,12 +50668,13 @@ async function notifyCommand(api, positionals, args) {
49142
50668
  }
49143
50669
  const subject = args.subject || createHash("sha256").update(`${title}
49144
50670
  ${message}`).digest("hex").slice(0, 16);
49145
- const row = await api.sendFeedMessage({ subject, title, message, ...args.href ? { href: String(args.href) } : {} });
50671
+ const msg = { subject, title, message, ...args.href ? { href: String(args.href) } : {} };
50672
+ const row = await (to ? api.sendMemberMessage({ ...msg, projectId, to }) : api.sendFeedMessage(msg));
49146
50673
  if (args.json) {
49147
50674
  console.log(JSON.stringify(row));
49148
50675
  return;
49149
50676
  }
49150
- process.stderr.write(row.created !== false ? `${GREEN}✅ Delivered to your own user's Messages inbox (not anyone else)${RESET} ${DIM}(subject ${subject})${RESET}
50677
+ process.stderr.write(row.created !== false ? `${GREEN}✅ Delivered to ${to ?? "your own user"}'s Messages inbox${RESET} ${DIM}(subject ${subject})${RESET}
49151
50678
  ` : `${DIM}Already sent (subject ${subject}) — nothing new delivered${RESET}
49152
50679
  `);
49153
50680
  }
@@ -73860,7 +75387,7 @@ async function main() {
73860
75387
  return;
73861
75388
  }
73862
75389
  if (positionals[0] === "notify") {
73863
- await notifyCommand(api2, positionals, args);
75390
+ await notifyCommand(api2, positionals, args, scopedProject);
73864
75391
  return;
73865
75392
  }
73866
75393
  if (positionals[0] === "delete") {