@todoforai/cli 0.1.73 → 0.1.74

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/todoforai-cli.js +295 -92
  2. package/package.json +1 -1
@@ -43767,14 +43767,15 @@ var mimetypes_default = {
43767
43767
  to_jpeg_90: { toMime: "image/jpeg", toExt: "jpg", quality: 0.9 }
43768
43768
  },
43769
43769
  types: {
43770
- image: { icon: "mdi:file-image", color: "yellow", prefixRule: "image/", needsObjectUrl: true },
43771
- audio: { icon: "mdi:file-music", color: "grey", prefixRule: "audio/", needsObjectUrl: true },
43772
- video: { icon: "mdi:file-video", color: "yellow", prefixRule: "video/", needsObjectUrl: true },
43770
+ image: { icon: "vscode-icons:file-type-image", color: "green", prefixRule: "image/", needsObjectUrl: true },
43771
+ audio: { icon: "mdi:file-music", color: "blue", prefixRule: "audio/", needsObjectUrl: true },
43772
+ video: { icon: "vscode-icons:file-type-video", color: "red", prefixRule: "video/", needsObjectUrl: true },
43773
43773
  pdf: { icon: "vscode-icons:file-type-pdf2", color: "red", needsObjectUrl: true },
43774
43774
  document: { icon: "vscode-icons:file-type-word", color: "lightblue" },
43775
43775
  spreadsheet: { icon: "vscode-icons:file-type-excel", color: "green" },
43776
- text: { icon: "mdi:file", color: "white", prefixRule: "text/" },
43777
- archive: { icon: "mdi:folder-zip", color: "purple" },
43776
+ presentation: { icon: "vscode-icons:file-type-powerpoint", color: "red" },
43777
+ text: { icon: "vscode-icons:file-type-text", color: "white", prefixRule: "text/" },
43778
+ archive: { icon: "vscode-icons:file-type-zip", color: "yellow" },
43778
43779
  unknown: { icon: "mdi:file", color: "grey" },
43779
43780
  mcp_text: { icon: "mdi:robot", color: "purple" },
43780
43781
  mcp_image: { icon: "mdi:robot", color: "purple", needsObjectUrl: true },
@@ -43825,8 +43826,8 @@ var mimetypes_default = {
43825
43826
  pdf: { mime: "application/pdf", type: "pdf" },
43826
43827
  doc: { mime: "application/msword", type: "document" },
43827
43828
  docx: { mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", type: "document", tags: ["docx"] },
43828
- ppt: { mime: "application/vnd.ms-powerpoint", type: "document" },
43829
- pptx: { mime: "application/vnd.openxmlformats-officedocument.presentationml.presentation", type: "document", tags: ["pptx"] },
43829
+ ppt: { mime: "application/vnd.ms-powerpoint", type: "presentation" },
43830
+ pptx: { mime: "application/vnd.openxmlformats-officedocument.presentationml.presentation", type: "presentation", tags: ["pptx"] },
43830
43831
  xls: { mime: "application/vnd.ms-excel", type: "spreadsheet", tags: ["xls"] },
43831
43832
  xlsx: { mime: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", type: "spreadsheet", tags: ["xlsx"] },
43832
43833
  zip: { mime: "application/zip", type: "archive" },
@@ -44074,7 +44075,7 @@ var FILE_TOOL_TYPES = new Set([
44074
44075
  "modify_file" /* ModifyFile */
44075
44076
  ]);
44076
44077
  // ../packages/shared-fbe/src/channels.ts
44077
- var privateSuffix = (privateOwnerId) => privateOwnerId ? `:private:${privateOwnerId}` : "";
44078
+ var privateSuffix = (userId) => userId ? `:private:${userId}` : "";
44078
44079
  var SERVER_TO_FRONTENDS = {
44079
44080
  task: {
44080
44081
  actionUpdate: (userId) => `${"task_action:update" /* TASK_ACTION_UPDATE */}:${userId}`
@@ -44122,9 +44123,9 @@ var SERVER_TO_FRONTENDS = {
44122
44123
  writeFileResult: (edgeId) => `${"edge:write_file_response" /* EDGE_WRITE_FILE_RESPONSE */}:${edgeId}`
44123
44124
  },
44124
44125
  project: {
44125
- new_message: (projectId, privateOwnerId) => `${"task:new_update" /* TASK_NEW */}:${projectId}${privateSuffix(privateOwnerId)}`,
44126
- new_todo: (projectId, privateOwnerId) => `${"todo:new" /* NEW_TODO */}:${projectId}${privateSuffix(privateOwnerId)}`,
44127
- status: (projectId, privateOwnerId) => `${"project:status" /* PROJECT_STATUS */}:${projectId}${privateSuffix(privateOwnerId)}`
44126
+ new_message: (projectId, memberId) => `${"task:new_update" /* TASK_NEW */}:${projectId}${privateSuffix(memberId)}`,
44127
+ new_todo: (projectId, memberId) => `${"todo:new" /* NEW_TODO */}:${projectId}${privateSuffix(memberId)}`,
44128
+ status: (projectId, memberId) => `${"project:status" /* PROJECT_STATUS */}:${projectId}${privateSuffix(memberId)}`
44128
44129
  },
44129
44130
  todo: {
44130
44131
  msgStart: (todoId) => `${"todo:msg_start" /* TODO_MSG_START */}:${todoId}`,
@@ -44710,6 +44711,137 @@ var OUTPUT_POLICIES = {
44710
44711
  full: { firstLimit: Infinity, lastLimit: 0, hardCap: RUN_OUTPUT_CAP, lineLimit: MAX_LINE_LEN },
44711
44712
  raw: { firstLimit: Infinity, lastLimit: 0, hardCap: Infinity, lineLimit: Infinity }
44712
44713
  };
44714
+ // ../packages/shared-fbe/src/agentRoles.ts
44715
+ var SAFE = "Prepare drafts and lists; never send, publish, pay or delete anything without my OK.";
44716
+ var AGENT_ROLES = [
44717
+ {
44718
+ key: "assistant",
44719
+ name: "Assistant",
44720
+ icon: "inbox",
44721
+ color: "#6366f1",
44722
+ tagline: "Inbox, calendar, meetings",
44723
+ outcomes: ["Inbox sorted, replies drafted", "A brief before every meeting", "Your week planned by Monday"],
44724
+ tools: ["zele", "zele-calendar", "outlook-api"],
44725
+ systemMessage: `You are my executive assistant. Keep my inbox and calendar under control. ${SAFE}
44726
+
44727
+ How you work:
44728
+ 1. Read new emails and sort them: needs me / FYI / ignore.
44729
+ 2. Draft replies for the ones that need me, in my voice.
44730
+ 3. For meetings in the next 2 days, write a short brief from the related emails.
44731
+ 4. Show me the drafts before anything is sent.`
44732
+ },
44733
+ {
44734
+ key: "bookkeeper",
44735
+ name: "Bookkeeping assistant",
44736
+ icon: "receipt",
44737
+ color: "#10b981",
44738
+ tagline: "Invoices, receipts, accountant",
44739
+ outcomes: ["Invoices from the inbox in one table", "Unpaid invoices reminded", "Month-end email to the accountant"],
44740
+ tools: ["zele", "stripe", "gdrive"],
44741
+ systemMessage: `You are my bookkeeping assistant. Keep invoices and receipts complete and organised. ${SAFE}
44742
+
44743
+ How you work:
44744
+ 1. Find invoices and receipts from the last month (inbox, Stripe, Drive).
44745
+ 2. Put them in a table: date, vendor, amount, VAT, attachment.
44746
+ 3. List unpaid invoices and draft polite reminders.
44747
+ 4. Draft the email to my accountant with the table; ask me before sending.`
44748
+ },
44749
+ {
44750
+ key: "sales",
44751
+ name: "Sales rep",
44752
+ icon: "target",
44753
+ color: "#f97316",
44754
+ tagline: "Leads, outreach, follow-ups",
44755
+ outcomes: ["Lead replies drafted in minutes", "Personal outreach at scale", "CRM always up to date"],
44756
+ tools: ["hubspot", "linkedin-api", "zele"],
44757
+ systemMessage: `You are my sales development rep. Research leads and write short, personal outreach. ${SAFE}
44758
+
44759
+ How you work:
44760
+ 1. Research each lead: company, role, a relevant recent fact.
44761
+ 2. Draft a short personal first message or reply.
44762
+ 3. Update the CRM with the lead and a follow-up date.
44763
+ 4. Ask me before sending any message.`
44764
+ },
44765
+ {
44766
+ key: "account-manager",
44767
+ name: "Account manager",
44768
+ icon: "briefcase",
44769
+ color: "#f59e0b",
44770
+ tagline: "Client updates, renewals",
44771
+ outcomes: ["Weekly status per client", "Recap after every call", "Silent clients flagged"],
44772
+ tools: ["zele", "slack-cli", "zele-calendar"],
44773
+ systemMessage: `You are my account manager. Keep every client relationship warm and on track. ${SAFE}
44774
+
44775
+ How you work:
44776
+ 1. Find clients who have not replied to my last message for 5+ days.
44777
+ 2. Draft a short, friendly follow-up for each.
44778
+ 3. After calls, write a recap with decisions and next steps.
44779
+ 4. Ask me before sending.`
44780
+ },
44781
+ {
44782
+ key: "social",
44783
+ name: "Social media manager",
44784
+ icon: "megaphone",
44785
+ color: "#ec4899",
44786
+ tagline: "Weekly posts in your voice",
44787
+ outcomes: ["Posts per channel in your style", "A full weekly calendar", "Reply drafts for comments"],
44788
+ tools: ["linkedin-api", "xurl", "facebook-pages-api"],
44789
+ systemMessage: `You are my social media manager. Write in my brand voice for each channel. ${SAFE}
44790
+
44791
+ How you work:
44792
+ 1. Read my brand page and the voice for each channel.
44793
+ 2. Draft 3 posts for this week, one idea each.
44794
+ 3. Draft replies to new comments worth answering.
44795
+ 4. Ask me before publishing anything.`
44796
+ },
44797
+ {
44798
+ key: "ads",
44799
+ name: "Ads manager",
44800
+ icon: "trending-up",
44801
+ color: "#3b82f6",
44802
+ tagline: "Meta and Google ads",
44803
+ outcomes: ["Wasted spend flagged daily", "Budget moved to the best ROAS", "Creative fatigue caught early"],
44804
+ tools: ["meta-ads", "google-ads"],
44805
+ systemMessage: `You are my ads manager. Watch performance and propose concrete changes. ${SAFE}
44806
+
44807
+ How you work:
44808
+ 1. Pull yesterday's results per campaign and ad.
44809
+ 2. Flag the worst cost per result and tired creatives.
44810
+ 3. Propose budget moves with the numbers behind them.
44811
+ 4. Ask me before changing any budget or pausing anything.`
44812
+ },
44813
+ {
44814
+ key: "developer",
44815
+ name: "Developer",
44816
+ icon: "code",
44817
+ color: "#0ea5e9",
44818
+ tagline: "Errors, PRs, deploys",
44819
+ outcomes: ["New errors grouped by cause each morning", "PR reviews with fixes", "Deploys after your OK"],
44820
+ tools: ["gh", "sentry-cli", "vercel"],
44821
+ systemMessage: `You are my senior developer. Keep the code healthy: small, tested, reviewed changes. Never deploy or merge without my OK.
44822
+
44823
+ How you work:
44824
+ 1. Check new errors since yesterday and group them by cause.
44825
+ 2. Fix the most important one on a branch, with a test.
44826
+ 3. Open a pull request with a short explanation.
44827
+ 4. Ask me before merging or deploying.`
44828
+ },
44829
+ {
44830
+ key: "researcher",
44831
+ name: "Market researcher",
44832
+ icon: "search",
44833
+ color: "#8b5cf6",
44834
+ tagline: "Competitors, markets, trends",
44835
+ outcomes: ["Weekly competitor watch", "Market brief before a pitch", "Brand mentions caught"],
44836
+ tools: ["perplexity-api", "agent-browser"],
44837
+ systemMessage: `You are my market researcher. Cite sources, separate facts from guesses, keep it short.
44838
+
44839
+ How you work:
44840
+ 1. Check what my competitors shipped or announced this week.
44841
+ 2. Search for new mentions of my brand.
44842
+ 3. Write a one-page brief with sources.`
44843
+ }
44844
+ ];
44713
44845
  // ../packages/shared-fbe/src/tool_catalog.json
44714
44846
  var tool_catalog_default = {
44715
44847
  zele: {
@@ -44798,6 +44930,22 @@ var tool_catalog_default = {
44798
44930
  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.",
44799
44931
  versionCmd: "figma-api --version 2>/dev/null | sed -n 1p"
44800
44932
  },
44933
+ "slack-api": {
44934
+ oauthBroker: true,
44935
+ category: "communication",
44936
+ pkg: "@todoforai/slack-api",
44937
+ installer: "npm",
44938
+ label: "Slack",
44939
+ summary: "Read and answer your Slack as yourself: DMs and mentions, search, history, threads.",
44940
+ loginCmd: "slack-api auth --token <user_token>",
44941
+ statusCmd: `slack-api whoami 2>/dev/null | sed -n 's/.*"user"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' | grep -m1 .`,
44942
+ logoutCmd: "slack-api logout",
44943
+ credentialPaths: [
44944
+ "~/.config/slack-api/credentials.json"
44945
+ ],
44946
+ versionCmd: "slack-api --version 2>/dev/null | sed -n 1p",
44947
+ description: "Slack Web API as the signed-in user (user token xoxp-, sees exactly what the user sees), JSON out. Sign-in: Connect (TODOforAI Slack app, user scopes) or `slack-api auth --stdin` / env SLACK_USER_TOKEN. `slack-api inbox [--days 3]` = DMs to you + @mentions, newest first, one JSON line each {id, thread, from, chat, subject, text, url, date} — a ready Feed source: `tfa-wait slack --feed --draft --repeat --forever --watch 'slack-api inbox' --every 2m --name Slack \"{{payload.text}}\"` (send-ready reply drafts in the Feed). Also `search '<slack query>'`, `history <C…|D…>`, `thread <channel> <ts>`, `conversations`, `user <U…>`, `dm <U…>`, `send <channel> <text> [--thread ts] --yes` (posts AS the user; without --yes only previews), `api <method> --param k=v` for any Web API method. search.messages is Tier 2 (~20/min); on ratelimited the error has retry_after. For Slack app development use slack-cli instead."
44948
+ },
44801
44949
  "slack-cli": {
44802
44950
  preinstallCloud: true,
44803
44951
  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",
@@ -45231,7 +45379,7 @@ var tool_catalog_default = {
45231
45379
  installer: "npm",
45232
45380
  label: "TODOforAI",
45233
45381
  summary: "Create and follow up on your TODOs, and show results in the chat.",
45234
- 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]` (your own todos; `--project-wide` for every member's), `--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, private to the todo's readers; `--card <name>` via project renderer; `--alias <name>` updates in place, `show ref <alias|todoId:alias>` re-posts it live at the chat bottom, `--alias <todoId>:<name>` updates another todo's artifact; `show share <ref>` makes it public and prints the link, `show unshare <ref>` makes it private again), `open <url>` embeds a live preview. Show viewer chrome: `--title` appears in the header; a kebab menu holds file actions, versions and sharing when available. Desktop floating previews add minimize (park in a tab), maximize/restore and close (X); inline blocks have no window controls. For authored HTML, do not duplicate the header/controls or add a rounded, bordered root card: the floating viewer owns the rounded frame. Keep content edge-to-edge; internal layout padding is fine. Pick by where the output lives: read once → plain reply; interactive, this chat only → `html_snippet`; kept/revised later → `show --alias`, then `show share` only if someone outside needs a link; format the user asked for (pptx, docx…) → `show` the file. 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>] [--url <link>: tap opens it on the phone, e.g. a Google Maps navigation link]` 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. Project files: `memory sync|path [--project <id>]` requires a project and checks out only projects/<id>/{memory,context} and projects/<id>/me/{memory,context}; legacy global files are read-only recovery. Voice commands use `--scope mine|project` (default mine, private within this project; project is shared).",
45382
+ 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]` (your own todos; `--contributor all` for every member's), `--inspect <id>`. Edit any todo without opening it: `status <id> DONE|READY|CANCELLED|ARCHIVED|DELETED` (close, reopen, stop, archive, 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=…` (also permissions=… and judgeSysmsg=…; MCP/device wiring and delete are refused from an agent shell). Surface results into chat: `show <file|-> [todo-id]` (renders by mimetype, private to the todo's readers; `--card <name>` via project renderer; `--alias <name>` updates in place, `show ref <alias|todoId:alias>` re-posts it live at the chat bottom, `--alias <todoId>:<name>` updates another todo's artifact; `show share <ref>` makes it public and prints the link, `show unshare <ref>` makes it private again), `open <url>` embeds a live preview. Show viewer chrome: `--title` appears in the header; a kebab menu holds file actions, versions and sharing when available. Desktop floating previews add minimize (park in a tab), maximize/restore and close (X); inline blocks have no window controls. For authored HTML, do not duplicate the header/controls or add a rounded, bordered root card: the floating viewer owns the rounded frame. Keep content edge-to-edge; internal layout padding is fine. Pick by where the output lives: read once → plain reply; interactive, this chat only → `html_snippet`; kept/revised later → `show --alias`, then `show share` only if someone outside needs a link; format the user asked for (pptx, docx…) → `show` the file. 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>] [--url <link>: tap opens it on the phone, e.g. a Google Maps navigation link]` 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. Project files: `memory sync|path [--project <id>]` requires a project and checks out only projects/<id>/{memory,context} and projects/<id>/me/{memory,context}; legacy global files are read-only recovery. Voice commands use `--scope mine|project` (default mine, private within this project; project is shared).",
45235
45383
  installCmd: "npm install -g @todoforai/cli",
45236
45384
  versionCmd: "tfa-cli --version 2>/dev/null | sed -n 1p",
45237
45385
  preinstallCloud: true,
@@ -45435,12 +45583,9 @@ var tool_catalog_default = {
45435
45583
  binName: "az",
45436
45584
  statusCmd: "az account show --query user.name -o tsv 2>/dev/null | tr -d '\\r' | grep -m1 .",
45437
45585
  logoutCmd: "az logout 2>&1; az account clear 2>&1",
45438
- loginCmd: "az login --use-device-code",
45439
- oauthUrlPattern: "(https://login\\.microsoft\\.com/device\\S*)",
45440
- deviceCodePattern: "enter the code ([A-Z0-9]+)",
45441
- canStreamLogin: true,
45586
+ loginCmd: "az login --allow-no-subscriptions",
45442
45587
  credentialPaths: ["~/.azure"],
45443
- description: 'Official Azure CLI. Device-code login: prints login.microsoft.com/device + a code, then polls — no callback port. Azure SQL: `az sql server list -o json`, `az sql db list -s <server> -g <rg>`, `az sql server firewall-rule create …`; run queries with go-sqlcmd (github.com/microsoft/go-sqlcmd): `sqlcmd -S <server>.database.windows.net -d <db> --authentication-method ActiveDirectoryDefault -Q "SELECT …"` (reuses the az login) or `-U user -P pass` for SQL auth.',
45588
+ description: 'Official Azure CLI. Browser login with a localhost callback (device code is blocked by Entra Security Defaults on new tenants); `--allow-no-subscriptions` so tenants without a subscription still sign in. Azure SQL: `az sql server list -o json`, `az sql db list -s <server> -g <rg>`, `az sql server firewall-rule create …`; run queries with go-sqlcmd (github.com/microsoft/go-sqlcmd): `sqlcmd -S <server>.database.windows.net -d <db> --authentication-method ActiveDirectoryDefault -Q "SELECT …"` (reuses the az login) or `-U user -P pass` for SQL auth.',
45444
45589
  versionCmd: "az version -o tsv 2>/dev/null | cut -f1 | sed -n 1p"
45445
45590
  },
45446
45591
  doctl: {
@@ -46132,11 +46277,12 @@ var tool_catalog_default = {
46132
46277
  statusCmd: `outlook-api whoami 2>/dev/null | sed -n 's/.*"userPrincipalName"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' | grep -m10 .`,
46133
46278
  logoutCmd: "outlook-api logout",
46134
46279
  loginCmd: "outlook-api auth --tenant common",
46280
+ oauthBroker: true,
46135
46281
  credentialPaths: ["~/.config/outlook-api/config.json"],
46136
- description: "Outlook/Hotmail/M365 mail and calendar 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>`. Calendar: `events [--days N|--start|--end] [--tz Europe/Budapest]` (calendarView, recurrences expanded), `event <id>`, `calendars`, `freebusy <emails…>`; writes: `event-create`, `event-respond <id> accept|tentativelyAccept|decline`, `event-delete`. `get <path>` = raw Graph GET. JSON out.",
46282
+ description: "Outlook/Hotmail/M365 mail and calendar over Microsoft Graph (on-prem Exchange → ews-api). `auth --tenant common` = browser login, redirect to localhost:8644 (`--device` = device code for headless hosts). list/search/read/attachments are read-only; `draft`/`reply` stay in Drafts unless --send; `send <draftId>`. Calendar: `events [--days N|--start|--end] [--tz Europe/Budapest]` (calendarView, recurrences expanded), `event <id>`, `calendars`, `freebusy <emails…>`; writes: `event-create`, `event-respond <id> accept|tentativelyAccept|decline`, `event-delete`. `get <path>` = raw Graph GET. JSON out.",
46137
46283
  versionCmd: "outlook-api --version 2>/dev/null | sed -n 1p",
46138
- deviceCodePattern: "enter the code ([A-Z0-9]+)",
46139
- oauthUrlPattern: "(https://(?:www\\.microsoft\\.com/link|login\\.microsoft\\.com/device))",
46284
+ oauthUrlPattern: "(https://login\\.microsoftonline\\.com/\\S+/oauth2/v2\\.0/authorize\\S+)",
46285
+ oauthCallbackPort: 8644,
46140
46286
  canStreamLogin: true
46141
46287
  },
46142
46288
  "outlook-calendar": {
@@ -46151,11 +46297,12 @@ var tool_catalog_default = {
46151
46297
  statusCmd: `outlook-api whoami 2>/dev/null | sed -n 's/.*"userPrincipalName"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' | grep -m10 .`,
46152
46298
  logoutCmd: "outlook-api logout",
46153
46299
  loginCmd: "outlook-api auth --tenant common",
46300
+ oauthBroker: true,
46154
46301
  credentialPaths: ["~/.config/outlook-api/config.json"],
46155
46302
  description: "Outlook/M365 calendar via `outlook-api`: `events [--days N|--start|--end] [--tz Europe/Budapest]` (calendarView, recurrences expanded), `event <id>`, `calendars`, `freebusy <emails…>` (getSchedule; work/school accounts only); writes: `event-create --subject --start --end [--tz] [--attendee …] [--online]`, `event-respond <id> accept|tentativelyAccept|decline`, `event-delete <id>`. Same sign-in as Outlook mail (`auth`, device code).",
46156
46303
  versionCmd: "outlook-api --version 2>/dev/null | sed -n 1p",
46157
- deviceCodePattern: "enter the code ([A-Z0-9]+)",
46158
- oauthUrlPattern: "(https://(?:www\\.microsoft\\.com/link|login\\.microsoft\\.com/device))",
46304
+ oauthUrlPattern: "(https://login\\.microsoftonline\\.com/\\S+/oauth2/v2\\.0/authorize\\S+)",
46305
+ oauthCallbackPort: 8644,
46159
46306
  canStreamLogin: true
46160
46307
  },
46161
46308
  neonctl: {
@@ -46183,11 +46330,9 @@ var tool_catalog_default = {
46183
46330
  summary: "Work with Microsoft 365 mail, Teams, SharePoint and users.",
46184
46331
  statusCmd: `m365 status --output text 2>/dev/null | tr -d '"' | grep -vi 'logged out' | grep -m1 .`,
46185
46332
  logoutCmd: "m365 logout",
46186
- loginCmd: "m365 login --authType deviceCode --appId 14d82eec-204b-4c2f-b7e8-296a70dab67e",
46187
- deviceCodePattern: "enter the code ([A-Z0-9]{8,}) to authenticate",
46188
- oauthUrlPattern: "open the page (https://\\S+) and enter",
46333
+ loginCmd: "m365 login --authType browser --appId 14d82eec-204b-4c2f-b7e8-296a70dab67e",
46189
46334
  credentialPaths: ["~/.cli-m365-msal.json", "~/.cli-m365-connection.json"],
46190
- 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.",
46335
+ description: "Microsoft 365 from the shell: `m365 outlook message list`, `m365 teams team list`, `m365 spo site list`. Browser login with a localhost callback (device code is blocked by Entra Security Defaults); add `--output json` for parseable output.",
46191
46336
  versionCmd: `m365 version 2>/dev/null | tr -d '"' | sed -n 1p`
46192
46337
  },
46193
46338
  gws: {
@@ -46361,7 +46506,7 @@ var tool_catalog_default = {
46361
46506
  installer: "pip",
46362
46507
  label: "Blender",
46363
46508
  summary: "Model, texture and render 3D scenes in a running Blender.",
46364
- statusCmd: `command -v blender-mcp >/dev/null && mcporter call blender.get_addon_status --output json 2>/dev/null | grep -o '"blender_version": "[^"]*"' | grep -m1 .`,
46509
+ statusCmd: "command -v blender-mcp >/dev/null && mcporter call blender.get_addon_status --output json 2>/dev/null | grep -o 'blender_version[^,}]*' | grep -m1 .",
46365
46510
  loginNoBrowser: true,
46366
46511
  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.",
46367
46512
  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`,
@@ -50180,7 +50325,56 @@ var PUBLIC_EMAIL_DOMAINS = new Set([
50180
50325
  "gmx.com",
50181
50326
  "gmx.net",
50182
50327
  "mail.com",
50183
- "zoho.com"
50328
+ "zoho.com",
50329
+ "aol.com",
50330
+ "msn.com",
50331
+ "yahoo.co.uk",
50332
+ "yahoo.de",
50333
+ "yahoo.fr",
50334
+ "yahoo.co.jp",
50335
+ "ymail.com",
50336
+ "pm.me",
50337
+ "tutanota.com",
50338
+ "tuta.io",
50339
+ "gmx.de",
50340
+ "gmx.at",
50341
+ "web.de",
50342
+ "t-online.de",
50343
+ "freenet.de",
50344
+ "orange.fr",
50345
+ "free.fr",
50346
+ "laposte.net",
50347
+ "libero.it",
50348
+ "virgilio.it",
50349
+ "freemail.hu",
50350
+ "citromail.hu",
50351
+ "indamail.hu",
50352
+ "vipmail.hu",
50353
+ "t-email.hu",
50354
+ "seznam.cz",
50355
+ "centrum.cz",
50356
+ "wp.pl",
50357
+ "o2.pl",
50358
+ "onet.pl",
50359
+ "interia.pl",
50360
+ "yandex.ru",
50361
+ "yandex.com",
50362
+ "mail.ru",
50363
+ "rambler.ru",
50364
+ "bk.ru",
50365
+ "inbox.ru",
50366
+ "list.ru",
50367
+ "ukr.net",
50368
+ "qq.com",
50369
+ "foxmail.com",
50370
+ "163.com",
50371
+ "126.com",
50372
+ "yeah.net",
50373
+ "sina.com",
50374
+ "naver.com",
50375
+ "daum.net",
50376
+ "hanmail.net",
50377
+ "rediffmail.com"
50184
50378
  ]);
50185
50379
  // ../packages/shared-fbe/src/toolInstallCommand.ts
50186
50380
  var NPM_HOME = '"${TFA_NPM_HOME:-$HOME}"';
@@ -50197,38 +50391,52 @@ var model_promos_default = {
50197
50391
  "`family` is a regex on the normalized model id (last path segment, lowercase, dots→dashes, '(level)' suffix stripped);",
50198
50392
  "'(-\\\\d+)*' trailing point versions are appended by the consumer, so 'claude-opus-5' covers claude-opus-5.2 too.",
50199
50393
  "`provider` matches the provider name instead (TODO GO / Ollama Cloud, subscription-wide).",
50200
- "`rate` = fraction off. `boostable:false` = the Ultra/Team promo boost (remaining price × boost_remaining_price_factor: +50% usage, 85%→90%, 80%→86.6%) is NOT applied.",
50394
+ "`rate` = fraction off. `boostable:false` = the Ultra/Team promo boost (remaining price × boost_remaining_price_factor ≈ 2/3: +50% usage, 70%→80%, 55%→70%; 0.6666 not 0.667 so the floored display hits 80/70) is NOT applied.",
50201
50395
  "`ends` is the last inclusive day (UTC).",
50202
- "Overlapping rows are fine: the consumer takes the highest live rate. Launch pricing = 85% until 2026-09-13 (Anthropic quota halves after), then 80% until 2026-11-21. Astra is never boosted.",
50396
+ "Overlapping rows are fine: the consumer takes the highest live rate. Launch pricing = 85% until 2026-09-13, 80% until 2026-10-28; from 2026-10-29 to 2026-11-21: OpenAI 70%, Anthropic 55%, Astra 50%, Fable 30%; TODO GO/Ollama no promo (ended 2026-10-28). Astra/Fable/subscription providers are never boosted.",
50203
50397
  "A change here goes live with a backend + frontend deploy; agent/src/assets/model_promos.json is only the agent's offline fallback, refresh it occasionally."
50204
50398
  ],
50205
- boost_remaining_price_factor: 0.667,
50399
+ boost_remaining_price_factor: 0.6666,
50206
50400
  promos: [
50207
- { family: "claude-fable-5", rate: 0.5, starts: "2026-06-29", ends: "2026-11-21", boostable: false },
50401
+ { family: "claude-fable-5", rate: 0.5, starts: "2026-06-29", ends: "2026-10-28", boostable: false },
50208
50402
  { family: "claude-opus-5", rate: 0.85, starts: "2026-06-29", ends: "2026-09-13" },
50209
- { family: "claude-opus-5", rate: 0.8, starts: "2026-09-14", ends: "2026-11-21" },
50403
+ { family: "claude-opus-5", rate: 0.8, starts: "2026-09-14", ends: "2026-10-28" },
50210
50404
  { family: "claude-opus-4-[78]", rate: 0.85, starts: "2026-06-29", ends: "2026-09-13" },
50211
- { family: "claude-opus-4-[78]", rate: 0.8, starts: "2026-09-14", ends: "2026-11-21" },
50405
+ { family: "claude-opus-4-[78]", rate: 0.8, starts: "2026-09-14", ends: "2026-10-28" },
50212
50406
  { family: "claude-sonnet-5", rate: 0.85, starts: "2026-06-29", ends: "2026-09-13" },
50213
- { family: "claude-sonnet-5", rate: 0.8, starts: "2026-09-14", ends: "2026-11-21" },
50407
+ { family: "claude-sonnet-5", rate: 0.8, starts: "2026-09-14", ends: "2026-10-28" },
50214
50408
  { family: "claude-haiku-4-5", rate: 0.85, starts: "2026-06-29", ends: "2026-09-13" },
50215
- { family: "claude-haiku-4-5", rate: 0.8, starts: "2026-09-14", ends: "2026-11-21" },
50409
+ { family: "claude-haiku-4-5", rate: 0.8, starts: "2026-09-14", ends: "2026-10-28" },
50216
50410
  { family: "claude-haiku-5", rate: 0.85, starts: "2026-06-29", ends: "2026-09-13" },
50217
- { family: "claude-haiku-5", rate: 0.8, starts: "2026-09-14", ends: "2026-11-21" },
50411
+ { family: "claude-haiku-5", rate: 0.8, starts: "2026-09-14", ends: "2026-10-28" },
50218
50412
  { family: "gpt-5-5(-pro)?", rate: 0.85, starts: "2026-06-29", ends: "2026-09-13" },
50219
- { family: "gpt-5-5(-pro)?", rate: 0.8, starts: "2026-09-14", ends: "2026-11-21" },
50413
+ { family: "gpt-5-5(-pro)?", rate: 0.8, starts: "2026-09-14", ends: "2026-10-28" },
50220
50414
  { family: "gpt-5-6-sol", rate: 0.85, starts: "2026-08-26", ends: "2026-09-13" },
50221
- { family: "gpt-5-6-sol", rate: 0.8, starts: "2026-09-14", ends: "2026-11-21" },
50222
- { family: "gpt-6-sol", rate: 0.8, starts: "2026-09-22", ends: "2026-11-21" },
50223
- { family: "gpt-6-1-sol", rate: 0.8, starts: "2026-09-29", ends: "2026-11-21" },
50224
- { family: "gpt-6-luna", rate: 0.8, starts: "2026-09-22", ends: "2026-11-21" },
50415
+ { family: "gpt-5-6-sol", rate: 0.8, starts: "2026-09-14", ends: "2026-10-28" },
50416
+ { family: "gpt-6-sol", rate: 0.8, starts: "2026-09-22", ends: "2026-10-28" },
50417
+ { family: "gpt-6-1-sol", rate: 0.8, starts: "2026-09-29", ends: "2026-10-28" },
50418
+ { family: "gpt-6-luna", rate: 0.8, starts: "2026-09-22", ends: "2026-10-28" },
50225
50419
  { family: "gpt-6-astra", rate: 0.85, starts: "2026-09-05", ends: "2026-09-13", boostable: false },
50226
- { family: "gpt-6-astra", rate: 0.8, starts: "2026-09-14", ends: "2026-11-21", boostable: false },
50420
+ { family: "gpt-6-astra", rate: 0.8, starts: "2026-09-14", ends: "2026-10-28", boostable: false },
50227
50421
  { family: "gpt-5-6-terra", rate: 0.85, starts: "2026-06-29", ends: "2026-09-13" },
50228
- { family: "gpt-5-6-terra", rate: 0.8, starts: "2026-09-14", ends: "2026-11-21" },
50422
+ { family: "gpt-5-6-terra", rate: 0.8, starts: "2026-09-14", ends: "2026-10-28" },
50229
50423
  { family: "gpt-5-6-luna", rate: 0.85, starts: "2026-06-29", ends: "2026-09-13" },
50230
- { family: "gpt-5-6-luna", rate: 0.8, starts: "2026-09-14", ends: "2026-11-21" },
50231
- { provider: "opencode[_ -]?go|ollama[_ -]?cloud", rate: 0.5, starts: "2026-06-29", ends: "2026-11-21", boostable: false }
50424
+ { family: "gpt-5-6-luna", rate: 0.8, starts: "2026-09-14", ends: "2026-10-28" },
50425
+ { provider: "opencode[_ -]?go|ollama[_ -]?cloud", rate: 0.5, starts: "2026-06-29", ends: "2026-10-28", boostable: false },
50426
+ { family: "claude-opus-5", rate: 0.55, starts: "2026-10-29", ends: "2026-11-21" },
50427
+ { family: "claude-opus-4-[78]", rate: 0.55, starts: "2026-10-29", ends: "2026-11-21" },
50428
+ { family: "claude-sonnet-5", rate: 0.55, starts: "2026-10-29", ends: "2026-11-21" },
50429
+ { family: "claude-haiku-4-5", rate: 0.55, starts: "2026-10-29", ends: "2026-11-21" },
50430
+ { family: "claude-haiku-5", rate: 0.55, starts: "2026-10-29", ends: "2026-11-21" },
50431
+ { family: "claude-fable-5", rate: 0.3, starts: "2026-10-29", ends: "2026-11-21", boostable: false },
50432
+ { family: "gpt-5-5(-pro)?", rate: 0.7, starts: "2026-10-29", ends: "2026-11-21" },
50433
+ { family: "gpt-5-6-sol", rate: 0.7, starts: "2026-10-29", ends: "2026-11-21" },
50434
+ { family: "gpt-6-sol", rate: 0.7, starts: "2026-10-29", ends: "2026-11-21" },
50435
+ { family: "gpt-6-1-sol", rate: 0.7, starts: "2026-10-29", ends: "2026-11-21" },
50436
+ { family: "gpt-6-luna", rate: 0.7, starts: "2026-10-29", ends: "2026-11-21" },
50437
+ { family: "gpt-5-6-terra", rate: 0.7, starts: "2026-10-29", ends: "2026-11-21" },
50438
+ { family: "gpt-5-6-luna", rate: 0.7, starts: "2026-10-29", ends: "2026-11-21" },
50439
+ { family: "gpt-6-astra", rate: 0.5, starts: "2026-10-29", ends: "2026-11-21", boostable: false }
50232
50440
  ]
50233
50441
  };
50234
50442
 
@@ -50238,11 +50446,11 @@ var MODEL_PROMOS = model_promos_default.promos;
50238
50446
  // ../packages/shared-fbe/src/realtime/topics.ts
50239
50447
  var TOPICS = {
50240
50448
  ["todo:new" /* NEW_TODO */]: {
50241
- channel: (p) => SERVER_TO_FRONTENDS.project.new_todo(p.projectId, p.privateOwnerId),
50449
+ channel: (p) => SERVER_TO_FRONTENDS.project.new_todo(p.projectId, p.memberId),
50242
50450
  audience: "frontend"
50243
50451
  },
50244
50452
  ["todo:new_message" /* TODO_NEW_MESSAGE */]: {
50245
- channel: (p) => SERVER_TO_FRONTENDS.project.new_message(p.projectId, p.privateOwnerId),
50453
+ channel: (p) => SERVER_TO_FRONTENDS.project.new_message(p.projectId, p.memberId),
50246
50454
  audience: "frontend"
50247
50455
  },
50248
50456
  ["todo:new_message_created" /* TODO_NEW_MESSAGE_CREATED */]: {
@@ -50390,7 +50598,7 @@ var TOPICS = {
50390
50598
  audience: "frontend"
50391
50599
  },
50392
50600
  ["project:status" /* PROJECT_STATUS */]: {
50393
- channel: (p) => SERVER_TO_FRONTENDS.project.status(p.projectId, p.privateOwnerId),
50601
+ channel: (p) => SERVER_TO_FRONTENDS.project.status(p.projectId, p.memberId),
50394
50602
  audience: "frontend"
50395
50603
  },
50396
50604
  ["todo:status" /* TODO_STATUS */]: {
@@ -50432,7 +50640,7 @@ var package_default = {
50432
50640
  type: "git",
50433
50641
  url: "git+https://github.com/todoforai/cli.git"
50434
50642
  },
50435
- version: "0.1.73",
50643
+ version: "0.1.74",
50436
50644
  type: "module",
50437
50645
  bin: {
50438
50646
  "todoforai-cli": "bin/todoforai-cli.js",
@@ -50587,7 +50795,7 @@ var STATUS_HELP = {
50587
50795
  ["READY_CHECKED" /* READY_CHECKED */]: "AI finished and the user reviewed it",
50588
50796
  ["DONE" /* DONE */]: "Completed and finalized",
50589
50797
  ["REVIEW_REQUESTED" /* REVIEW_REQUESTED */]: "Asks the user to review",
50590
- ["POSTPONED" /* POSTPONED */]: "Put off for later",
50798
+ ["CANCELLED" /* CANCELLED */]: "Stop a running TODO",
50591
50799
  ["ARCHIVED" /* ARCHIVED */]: "Archived (hidden from active list)",
50592
50800
  ["DELETED" /* DELETED */]: "Marked for deletion"
50593
50801
  };
@@ -50600,7 +50808,7 @@ ${Object.entries(STATUS_HELP).map(([s, d]) => ` ${s.padEnd(18)}${d}`).join(`
50600
50808
  `)}
50601
50809
 
50602
50810
  All valid statuses:
50603
- ${Object.values(TodoStatus).join(", ")}
50811
+ ${Object.values(TodoStatus).filter((s) => s !== "POSTPONED" /* POSTPONED */).join(", ")}
50604
50812
  `);
50605
50813
  }
50606
50814
  var WORD_FLAGS = {
@@ -53466,8 +53674,9 @@ Examples:
53466
53674
  tfa-cli agent update <agent> sysmsg="You are a terse video editor."
53467
53675
  tfa-cli agent create name=Reviewer model=claude sysmsg="Review diffs only."
53468
53676
  tfa-cli agent update <agent> judgeSysmsg="Ask before sending email." (approval checker; ""=off, null=inherit global)
53677
+ tfa-cli agent update <agent> permissions='{"allow":[…],"ask":[…],"deny":[…]}' (replaces all three lists; read them with get --json first)
53469
53678
 
53470
- From an agent shell (dst_ token) permissions, edgesMcpConfigs, devicesConfig and delete are refused by the backend.
53679
+ From an agent shell (dst_ token) edgesMcpConfigs, devicesConfig, toolProfiles and delete are refused by the backend.
53471
53680
  `);
53472
53681
  }
53473
53682
  var FIELD_ALIASES = {
@@ -53674,7 +53883,7 @@ var STATUS_HELP2 = {
53674
53883
  ["READY_CHECKED" /* READY_CHECKED */]: "AI finished and the user reviewed it",
53675
53884
  ["DONE" /* DONE */]: "Completed and finalized",
53676
53885
  ["REVIEW_REQUESTED" /* REVIEW_REQUESTED */]: "Asks the user to review",
53677
- ["POSTPONED" /* POSTPONED */]: "Put off for later",
53886
+ ["CANCELLED" /* CANCELLED */]: "Stop a running TODO",
53678
53887
  ["ARCHIVED" /* ARCHIVED */]: "Archived (hidden from active list)",
53679
53888
  ["DELETED" /* DELETED */]: "Marked for deletion"
53680
53889
  };
@@ -53891,7 +54100,7 @@ tfa-cli project — edit the current project
53891
54100
  Usage:
53892
54101
  tfa-cli project list Projects you can access (* = default)
53893
54102
  tfa-cli project members Who is on it (notify --to <email> reaches them)
53894
- tfa-cli project set <field=value>… name=… description=…
54103
+ tfa-cli project set <field=value>… name=… description=… logo=<https image url|"">
53895
54104
  (isPublic refused from an agent shell)
53896
54105
  tfa-cli project default Make it the project you land on at /
53897
54106
  tfa-cli project agent <agent> Default agent for new todos in this project
@@ -53901,11 +54110,15 @@ Usage:
53901
54110
  tfa-cli project groups intake <slug> [on|off|regenerate] [field=value]…
53902
54111
  Public intake: autoStart maxSubmissions postIpLimit placeholder agentSettingsId
53903
54112
  tfa-cli project groups reorder <slug>… Set wall order
53904
- tfa-cli project surfaces List the board's card surfaces (live refs shown as cards)
53905
- tfa-cli project surfaces set <id> ref=<ref> [title=…] [group=<slug>] [order=N]
54113
+ tfa-cli project surfaces List the board's cards you can see: id, title, ref|note, group, color
54114
+ tfa-cli project surfaces set <id> ref=<ref> [title=…] [group=<slug>] [color=#hex] [order=N]
53906
54115
  Show a ref on the board: a todo id, <todoId>:<alias>
53907
54116
  (shown artifact) or an http(s) url. group= puts it
53908
54117
  inside that group's card; group="" makes it standalone.
54118
+ tfa-cli project surfaces set <id> body="…" [group=<slug>] [color=#hex]
54119
+ A note (markdown text). Seen by
54120
+ whoever sees where it sits: standalone = the project,
54121
+ group = its members, your My Private group = only you.
53909
54122
  tfa-cli project surfaces rm <id> Take it off the board
53910
54123
 
53911
54124
  Examples:
@@ -53913,6 +54126,7 @@ Examples:
53913
54126
  tfa-cli project groups set marketing name=Marketing description="Top-of-funnel work"
53914
54127
  tfa-cli project groups reorder marketing sales ops
53915
54128
  tfa-cli project surfaces set weekly-kpis ref=$TODOFORAI_TODO_ID:kpi title="Weekly KPIs" group=marketing
54129
+ tfa-cli project surfaces set sales-rules body="Quote prices in EUR, net of VAT." group=sales
53916
54130
  `);
53917
54131
  }
53918
54132
  var TODO_ALIASES = {
@@ -54101,21 +54315,23 @@ async function projectCommand(api, positionals, args, projectId) {
54101
54315
  console.log(JSON.stringify(surfaces, null, 2));
54102
54316
  return;
54103
54317
  }
54318
+ const titleOf = (s) => s.title || (s.body ?? "").split(`
54319
+ `).find((l) => l.trim())?.trim().slice(0, 60) || "";
54104
54320
  for (const s of surfaces)
54105
- process.stderr.write(`${s.id} ${s.ref} ${DIM}${[s.title, s.group && `in ${s.group}`].filter(Boolean).join(" ")}${RESET}
54321
+ process.stderr.write(`${s.id} ${titleOf(s)} ${DIM}${[s.ref ?? "note", s.group && `in ${s.group}`, s.color].filter(Boolean).join(" ")}${RESET}
54106
54322
  `);
54107
54323
  return;
54108
54324
  }
54109
54325
  if (verb === "set") {
54110
54326
  const [id, ...fields] = sargs;
54111
54327
  if (!id)
54112
- fail("Usage: tfa-cli project surfaces set <id> ref=<ref> [title=…] [group=<slug>] [order=N]");
54328
+ fail("Usage: tfa-cli project surfaces set <id> [ref=<ref>|body=…] [title=…] [group=<slug>] [color=#hex] [order=N]");
54113
54329
  const surface = await api.upsertProjectSurface(projectId, id, parseAssignments2(fields));
54114
54330
  if (args.json) {
54115
54331
  console.log(JSON.stringify(surface, null, 2));
54116
54332
  return;
54117
54333
  }
54118
- process.stderr.write(`${GREEN}✅ surface ${id} shows ${surface.ref}${surface.group ? ` in ${surface.group}` : ""}${RESET}
54334
+ process.stderr.write(`${GREEN}✅ surface ${id} ${surface.ref ? `shows ${surface.ref}` : "is a note"}${surface.group ? ` in ${surface.group}` : ""}${RESET}
54119
54335
  `);
54120
54336
  return;
54121
54337
  }
@@ -54487,40 +54703,26 @@ var VALID_STATUS = new Set(Object.values(TodoStatus));
54487
54703
  var isOpen = (s) => !CLOSED.has(s);
54488
54704
  function printListTodosHelp() {
54489
54705
  process.stderr.write(`
54490
- todoforai-cli list — list YOUR todos in a project (recent first)
54706
+ todoforai-cli list [flags] — todos of a project, newest first (lastActivityAt)
54491
54707
 
54492
- Default: only todos you opened or wrote in. --project-wide shows everyone's.
54708
+ Scope: yours only (todos you opened or wrote in) → --contributor all = every member's
54709
+ open only (hides DONE, ARCHIVED, DELETED) → -A / --all = closed too
54710
+ Output: STATUS YYYY-MM-DD HH:MM <todoId> <first line of content>
54711
+ --json → { items: [{ id, status, content, lastActivityAt, createdAt, contributorIds, … }], nextCursor }
54493
54712
 
54494
- Usage:
54495
- todoforai-cli list [flags]
54713
+ Filters (all combinable):
54714
+ -s, --status <S,S2> exact statuses, union (closed ones included); OPEN = any non-closed
54715
+ --search <text> content/status substring (backend)
54716
+ --contributor <u> id or email, comma = any; "all" = no filter (default: you)
54717
+ --since/--until <t> window on the sort field: epoch ms | YYYY-MM-DD | ISO | 12h / 3d (ago)
54718
+ --sort created by createdAt instead of activity
54719
+ --project <id> other project (default: the current one)
54496
54720
 
54497
- Flags:
54498
- --project-wide Every member's todos, not just yours
54499
- -n, --limit <n> Max rows to show (default: 30)
54500
- --cursor <n> Fetch todos older than this cursor (sort field, epoch ms)
54501
- --since <t> Only todos with sort field >= t (inclusive)
54502
- --until <t> Only todos with sort field < t (exclusive)
54503
- t = epoch ms | YYYY-MM-DD (local midnight) | ISO datetime | 12h / 3d ago
54504
- --sort <key> activity (lastActivityAt, default) | created (createdAt)
54505
- --contributor <u> Only todos a member opened/wrote in (id or email; comma = any; replaces the "mine" default)
54506
- --page-size <n> Backend page size per request (default: min(limit, 400), max: 400)
54507
- --search <text> Search content/status on the backend
54508
- -s, --status <S[,S2]> Filter by status (comma-separated, union).
54509
- -A, --all Include closed (DONE, ARCHIVED, DELETED)
54510
- --project <id> Project ID (default: current default project)
54511
- --json Output { items, nextCursor } as JSON
54512
- -h, --help Show this help
54721
+ Paging: -n <limit> (default 30). "Next cursor: N" on stderr / nextCursor in JSON → re-run with --cursor N.
54513
54722
 
54514
54723
  Examples:
54515
- todoforai-cli list # 30 most recent open todos of yours
54516
- todoforai-cli list --project-wide # … of everyone in the project
54517
- todoforai-cli list -n 50 # last 50 open
54518
- todoforai-cli list --cursor 1719234567890 # next page
54519
- todoforai-cli list --all # include DONE
54520
54724
  todoforai-cli list -s RUNNING,REVIEW_REQUESTED
54521
- todoforai-cli list -A --since 2026-10-11 --until 2026-10-12 # touched that day
54522
- todoforai-cli list -A --sort created --since 7d --contributor a@b.com
54523
- todoforai-cli list --search bug --json | jq '.items[].id'
54725
+ todoforai-cli list --contributor all -A --since 7d --json | jq -r '.items[] | [.id, .status] | @tsv'
54524
54726
  `);
54525
54727
  }
54526
54728
  function positiveInt(value, name) {
@@ -54617,7 +54819,6 @@ async function listTodosCommand(api, defaultProjectId, argv, resolveUserId) {
54617
54819
  until: { type: "string" },
54618
54820
  sort: { type: "string" },
54619
54821
  contributor: { type: "string" },
54620
- "project-wide": { type: "boolean", default: false },
54621
54822
  status: { type: "string", short: "s" },
54622
54823
  all: { type: "boolean", short: "A", default: false },
54623
54824
  project: { type: "string" },
@@ -54650,9 +54851,11 @@ async function listTodosCommand(api, defaultProjectId, argv, resolveUserId) {
54650
54851
  if (until !== undefined)
54651
54852
  cursor = cursor === undefined ? until : Math.min(cursor, until);
54652
54853
  const search = values.search ? String(values.search) : undefined;
54653
- let contributorIds = values.contributor ? await resolveContributors(api, projectId, String(values.contributor)) : undefined;
54854
+ const contributorArg = values.contributor === undefined ? undefined : String(values.contributor);
54855
+ const everyone = contributorArg?.trim().toLowerCase() === "all";
54856
+ let contributorIds = contributorArg && !everyone ? await resolveContributors(api, projectId, contributorArg) : undefined;
54654
54857
  let mineOnly = false;
54655
- if (!contributorIds && !values["project-wide"] && resolveUserId) {
54858
+ if (!contributorIds && !everyone && resolveUserId) {
54656
54859
  const me = await resolveUserId();
54657
54860
  if (me) {
54658
54861
  contributorIds = [me];
@@ -54714,7 +54917,7 @@ async function listTodosCommand(api, defaultProjectId, argv, resolveUserId) {
54714
54917
  `);
54715
54918
  return;
54716
54919
  }
54717
- const scopeHint = mineOnly ? " of yours (--project-wide for everyone's)" : "";
54920
+ const scopeHint = mineOnly ? " of yours (--contributor all for everyone's)" : "";
54718
54921
  if (!visible.length) {
54719
54922
  process.stderr.write(`${DIM}(no todos${scopeHint})${RESET}
54720
54923
  `);
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/todoforai/cli.git"
9
9
  },
10
- "version": "0.1.73",
10
+ "version": "0.1.74",
11
11
  "type": "module",
12
12
  "bin": {
13
13
  "todoforai-cli": "bin/todoforai-cli.js",