@todoforai/cli 0.1.47 → 0.1.49

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 +762 -74
  2. package/package.json +1 -1
@@ -42860,6 +42860,75 @@ class ApiClient {
42860
42860
  deleteTodo(todoId) {
42861
42861
  return this.request("DELETE", `/api/v1/todos/${todoId}`);
42862
42862
  }
42863
+ restoreTodo(todoId) {
42864
+ return this.request("POST", `/api/v1/todos/${todoId}/restore`, { todoId });
42865
+ }
42866
+ reorderAgentSettings(agentSettingsIds) {
42867
+ return this.request("PUT", "/api/v1/agents/reorder", { agentSettingsIds });
42868
+ }
42869
+ updateTodo(todoId, updates) {
42870
+ return this.request("PUT", `/api/v1/todos/${todoId}`, { todoId, ...updates });
42871
+ }
42872
+ updateScheduledMessage(todoId, messageId, agentSettingsId, updates) {
42873
+ return this.request("PATCH", `/api/v1/todos/${todoId}/messages/${messageId}`, { todoId, messageId, agentSettingsId, ...updates });
42874
+ }
42875
+ setDefaultProject(projectId) {
42876
+ return this.request("POST", "/api/v1/users/default-project", { projectId });
42877
+ }
42878
+ setProjectDefaultAgent(projectId, agentSettingsId) {
42879
+ return this.request("POST", "/api/v1/users/project-agent-mapping", { projectId, agentSettingsId });
42880
+ }
42881
+ renameDevice(id, name) {
42882
+ return this.request("PATCH", `/api/v1/devices/${id}/name`, { id, name });
42883
+ }
42884
+ setDeviceWallpaperMode(id, mode) {
42885
+ return this.request("POST", `/api/v1/devices/${id}/wallpaper-mode`, { id, mode });
42886
+ }
42887
+ listBusinessContexts() {
42888
+ return this.request("GET", "/api/v1/business-context");
42889
+ }
42890
+ createBusinessContext(name) {
42891
+ return this.request("POST", "/api/v1/business-context", { name });
42892
+ }
42893
+ renameBusinessContext(id, name) {
42894
+ return this.request("PUT", `/api/v1/business-context/${id}`, { id, name });
42895
+ }
42896
+ selectBusinessContext(contextId) {
42897
+ return this.request("POST", "/api/v1/business-context/select", { contextId });
42898
+ }
42899
+ listVoiceSources(businessContextId) {
42900
+ return this.request("GET", `/api/v1/business-context/voice-sources?id=${encodeURIComponent(businessContextId)}`);
42901
+ }
42902
+ collectVoiceSource(businessContextId, channel, opts) {
42903
+ return this.request("POST", "/api/v1/business-context/collect-voice-source", { businessContextId, channel, ...opts });
42904
+ }
42905
+ removeVoiceSource(businessContextId, channel) {
42906
+ return this.request("POST", "/api/v1/business-context/remove-voice-source", { businessContextId, channel });
42907
+ }
42908
+ refineBrandVoice(businessContextId, answers, source) {
42909
+ return this.request("POST", "/api/v1/business-context/refine-brand-voice", { businessContextId, answers, ...source && { source } });
42910
+ }
42911
+ getProfile() {
42912
+ return this.request("GET", "/api/v1/users/profile");
42913
+ }
42914
+ getOnboarding() {
42915
+ return this.request("GET", "/api/v1/onboarding");
42916
+ }
42917
+ patchOnboarding(patch) {
42918
+ return this.request("PUT", "/api/v1/onboarding", { patch });
42919
+ }
42920
+ updateProject(projectId, updates) {
42921
+ return this.request("PUT", `/api/v1/projects/${projectId}`, { projectId, ...updates });
42922
+ }
42923
+ listProjectGroups(projectId) {
42924
+ return this.request("GET", `/api/v1/projects/${projectId}/groups`);
42925
+ }
42926
+ upsertProjectGroup(projectId, slug, fields) {
42927
+ return this.request("PUT", `/api/v1/projects/${projectId}/groups/${encodeURIComponent(slug)}`, { projectId, slug, ...fields });
42928
+ }
42929
+ reorderProjectGroups(projectId, slugs) {
42930
+ return this.request("PUT", `/api/v1/projects/${projectId}/groups-order`, { projectId, slugs });
42931
+ }
42863
42932
  listModels() {
42864
42933
  return this.request("GET", "/api/v1/models");
42865
42934
  }
@@ -43669,6 +43738,9 @@ var SERVER_TO_FRONTENDS = {
43669
43738
  notification: {
43670
43739
  changed: (userId) => `${"notification:changed" /* NOTIFICATION_CHANGED */}:${userId}`
43671
43740
  },
43741
+ frontend: {
43742
+ presence: (userId) => `${"frontend:presence" /* FRONTEND_PRESENCE */}:${userId}`
43743
+ },
43672
43744
  agent: {
43673
43745
  status: (userId) => `${"agent:status" /* AGENT_STATUS */}:${userId}`,
43674
43746
  mcpList: () => `${"agent:mcp_list" /* AGENT_MCP_LIST */}:ALL`
@@ -44334,6 +44406,10 @@ var TOPICS = {
44334
44406
  channel: (p) => SERVER_TO_FRONTENDS.notification.changed(p.userId),
44335
44407
  audience: "frontend"
44336
44408
  },
44409
+ ["frontend:presence" /* FRONTEND_PRESENCE */]: {
44410
+ channel: (p) => SERVER_TO_FRONTENDS.frontend.presence(p.userId),
44411
+ audience: "frontend"
44412
+ },
44337
44413
  ["NOTIFY" /* NOTIFY */]: {
44338
44414
  channel: (p) => p.userId ? SERVER_TO_FRONTENDS.notify.user(p.userId) : SERVER_TO_FRONTENDS.todo.notify(p.todoId),
44339
44415
  audience: "frontend"
@@ -44450,7 +44526,7 @@ import { parseArgs } from "util";
44450
44526
  // package.json
44451
44527
  var package_default = {
44452
44528
  name: "@todoforai/cli",
44453
- version: "0.1.47",
44529
+ version: "0.1.49",
44454
44530
  type: "module",
44455
44531
  bin: {
44456
44532
  "todoforai-cli": "bin/todoforai-cli.js",
@@ -44492,41 +44568,35 @@ function getEnv(name) {
44492
44568
  }
44493
44569
  function printUsage() {
44494
44570
  process.stderr.write(`
44495
- tfa-cli — TODOforAI CLI. Legacy aliases: todoforai-cli, todoai.
44571
+ tfa-cli — TODOforAI CLI
44496
44572
 
44497
- Usage:
44573
+ Usage: tfa-cli [OPTIONS] ["prompt"]
44574
+ tfa-cli [OPTIONS] <COMMAND> [ARGS]
44575
+
44576
+ Examples:
44498
44577
  tfa-cli login # Browser-based device auth
44499
- tfa-cli "prompt text" # Prompt as argument
44500
- tfa-cli -n "Quick task" # Non-interactive (run and exit)
44501
- echo "content" | tfa-cli # Pipe from stdin
44502
- tfa-cli --path /my/project "Fix bug" # Explicit workspace path
44503
- tfa-cli -c ["prompt"] # Resume last todo (optional prompt sent on attach)
44504
- tfa-cli --resume <todo-id> ["prompt"] # Resume specific todo (optional prompt sent on attach)
44505
- tfa-cli --inspect <todo-id>[@<slice>] # Read chat log. <slice> = -3:, :1, 5:10, 7 (Python-style)
44506
- tfa-cli start <id> # Start a TODO from the registry (todoregistry.com)
44507
- tfa-cli acp # Agent Client Protocol server on stdio (Zed, JetBrains, …; see README)
44508
- tfa-cli --list-agents # List available agents and exit
44509
- tfa-cli --list-models [filter] # List models usable with --model and exit
44510
- tfa-cli agent update <agent> model=<model> # Update agent settings (see 'agent --help')
44511
- tfa-cli list [-n 30] [--cursor N] [--all] [--status S] # List todos (paginated); see 'list --help'
44512
- tfa-cli status <todo-id> <STATUS> # Update a todo's status (run 'status --help' for the full list)
44578
+ tfa-cli "prompt text" # New todo
44579
+ echo "content" | tfa-cli # From stdin
44580
+ tfa-cli start <id> # Registry template
44581
+ tfa-cli acp # Agent Client Protocol (stdio)
44582
+ tfa-cli agents # List agents
44583
+ tfa-cli models [filter] # Available models
44584
+ tfa-cli agent list|get|update|create # Agents (update <agent> model=…)
44585
+ tfa-cli todo set <todo-id|-> title=… group=… star=true # Edit a todo's fields
44586
+ tfa-cli project list|set|settings|groups|default|agent # Projects; edit the current one
44587
+ tfa-cli brand list|create|select|voice … # Brands (business contexts) + voice learning
44588
+ tfa-cli device list|rename|wallpaper # Paired machines
44589
+ tfa-cli list [-n 30] [--cursor N] [--all] [--status S] # List todos (paginated)
44590
+ tfa-cli status <todo-id> <STATUS> # Update a todo's status
44513
44591
  tfa-cli delete <todo-id> # Permanently delete a todo
44514
- tfa-cli addmessage <todo-id> "text" # Add a message to an existing todo
44515
- tfa-cli show <file|-> [todo-id] # Show a file in the chat (rendered by mimetype; - reads stdin)
44516
- # [--title T] [--alias A] [--mime M] [--card <name>] [--link] [--json]
44517
- # --link = compact chip (name+size+download) instead of inline render
44518
- # Prints "<todoId>:<alias|id> <public url>". Re-showing with the same
44519
- # --alias updates that block in place and pushes a new version to the
44520
- # same url (older versions stay reachable at /<id>/<version>).
44521
- tfa-cli show rm <ref|alias> # Take a shown file down (block, url, every version)
44522
- tfa-cli show list [todo-id] # List show blocks (ref, title, mime/url, card)
44523
- # [--project <id>] [--card <name>] [--json]
44524
- # no todo-id + --project (or $TODOFORAI_PROJECT_ID) = every todo
44525
- tfa-cli open <url> [todo-id] # Show a live http(s) url in the chat as a preview
44526
- # [--title T] [--alias A] [--json]
44527
- tfa-cli recommend --template <id> # Add a template as a recommendation card (see 'todoregistry-cli create')
44528
- tfa-cli claim mint --seed <projectId> [--emails a@x,b@y] [--ttl <sec>] # Mint /claim/<token> ownership links for a project you own
44529
- tfa-cli next [--direction "<text>"] # Ask the analyzer for growth recommendation cards (optional free-text steer)
44592
+ tfa-cli addmessage <todo-id> "text" # Send a message and exit (like -r, no watch/bridge)
44593
+ tfa-cli show <file|-> [todo-id] # Render a local file (image/pdf/html…) in the chat; - = stdin
44594
+ tfa-cli show list|rm … # List / take down shown files
44595
+ tfa-cli open <url> [todo-id] # Live url preview in the chat
44596
+ tfa-cli recommend --template <id> # Add a template as a recommendation card
44597
+ tfa-cli claim mint --seed <projectId> [--emails a@x,b@y] [--ttl <sec>] # Mint /claim/<token> links for a project you own
44598
+ tfa-cli next [--direction "<text>"] # Analyzer growth recommendation cards
44599
+ tfa-cli <command> help # Details per command
44530
44600
 
44531
44601
  Options:
44532
44602
  --path <dir> Workspace path (default: cwd)
@@ -44534,36 +44604,46 @@ Options:
44534
44604
  --agent, -a <name> Agent name (partial match)
44535
44605
  --group <slug> Group new todo; omitted inherits TODOFORAI_GROUP_ID
44536
44606
  --group-name <name> Display name for --group (last write wins)
44537
- --model <model> Override the agent's model for this todo
44538
- (e.g. anthropic:anthropic/claude-opus-5, openai:openai/gpt-5.6-sol)
44539
- --list-agents List available agents (name, id, workspace paths) and exit
44540
- --list-models List models usable with --model (optional substring filter) and exit
44541
- --api-url <url> API URL
44542
- --api-key <key> API key
44607
+ --model <model> Model for this todo only (see 'models')
44608
+ --api-url <url>
44609
+ --api-key <key>
44543
44610
  --user-id <id> Admin HTTP impersonation; requires --no-watch
44544
- --inspect, -i <todo-id>[@<slice>] Print chat log (read-only)
44545
- --template, -t <id> ["prompt"] Start from a registry template (alias: start <id>); prompt overrides the template task
44546
- --resume, -r [todo-id] Resume existing todo
44547
- --continue, -c Continue most recent todo
44548
- --non-interactive, -n Run to completion and exit without interactive prompt
44549
- --dangerously-skip-permissions Auto-approve all blocks (for CI/benchmarks)
44550
- --allow-all Set permissions to allow all tools (no approval needed)
44611
+ --inspect, -i <todo-id>[@<slice>] Read chat log; slices: -3:, :1, 5:10, 7
44612
+ --template, -t <id> ["prompt"] Same as start; prompt overrides template task
44613
+ --resume, -r <todo-id> ["prompt"] Resume; optional follow-up
44614
+ --continue, -c ["prompt"] Resume last; optional follow-up
44615
+ --non-interactive, -n Run to completion, then exit
44616
+ --dangerously-skip-permissions CLI auto-approves every block
44617
+ --allow-all Agent permissions allow:*:* nothing ever asks
44551
44618
  --raw-sysmsg <file> Use file contents verbatim as system prompt (new TODO only)
44552
44619
  --no-watch Create todo and exit
44553
- --isolated Spawn an ephemeral, task-scoped bridge: the agent sees ONLY
44554
- this machine (workspace = --path/cwd); session dies with the CLI
44620
+ --isolated Agent sees ONLY this machine + dir (no cloud VM/other devices); ends with CLI
44555
44621
  --no-bridge Do not auto-spawn bridge
44556
- --no-edge Deprecated alias for --no-bridge
44557
- --json Output as JSON
44558
- --detailed 'inspect --json': keep ids, timestamps, agentSettingsId, scheduledTimestamp
44559
- --format-anthropic 'inspect --json': Anthropic-style shape (tool_result in next user msg); attachment sources are uri-typed, so not a 1:1 messages.create input
44622
+ --json
44623
+ --detailed | --format-anthropic inspect --json: keep ids/timestamps | Anthropic messages shape
44560
44624
  --safe Validate API key upfront
44561
- --debug, -d Debug output
44625
+ --debug, -d
44562
44626
  --debug-dump Attach LLM request debug info per turn (requires server grant)
44563
- --show-config Show config
44564
- --reset-config Reset config file
44565
- --version, -v Print version and exit
44566
- --help, -h Show this help
44627
+ help | version | config = -h | -v | --show-config (--reset-config wipes it)
44628
+ `);
44629
+ }
44630
+ function printShowHelp() {
44631
+ process.stderr.write(`
44632
+ tfa-cli show — put a file or url into the chat
44633
+
44634
+ Usage:
44635
+ tfa-cli show <file|-> [todo-id] Render a file by mimetype (- reads stdin)
44636
+ [--title T] [--alias A] [--mime M] [--card <name>] [--link] [--json]
44637
+ --link: compact download chip instead of inline render
44638
+ Prints "<todoId>:<alias|id> <public url>". Re-showing with the same
44639
+ --alias updates the block in place; old versions stay at /<id>/<version>
44640
+ tfa-cli show rm <ref|alias> Take it down (block, url, every version)
44641
+ tfa-cli show list [todo-id] List show blocks (ref, title, mime/url, card)
44642
+ [--project <id>] [--card <name>] [--json]
44643
+ no todo-id + --project (or $TODOFORAI_PROJECT_ID) = every todo
44644
+ tfa-cli open <url> [todo-id] Live http(s) url as a preview [--title T] [--alias A] [--json]
44645
+
44646
+ todo-id defaults to $TODOFORAI_TODO_ID (agent shell) or the last todo this CLI touched.
44567
44647
  `);
44568
44648
  }
44569
44649
  var STATUS_HELP = {
@@ -44587,6 +44667,14 @@ All valid statuses:
44587
44667
  ${Object.values(TodoStatus).join(", ")}
44588
44668
  `);
44589
44669
  }
44670
+ var WORD_FLAGS = {
44671
+ help: "help",
44672
+ version: "version",
44673
+ models: "list-models",
44674
+ agents: "list-agents",
44675
+ config: "show-config"
44676
+ };
44677
+ var HELP_SUBCOMMANDS = ["agent", "todo", "project", "brand", "device", "list", "ls", "status", "show", "open"];
44590
44678
  function parseCliArgs() {
44591
44679
  const { values, positionals } = parseArgs({
44592
44680
  args: process.argv.slice(2),
@@ -44614,6 +44702,10 @@ function parseCliArgs() {
44614
44702
  link: { type: "boolean", default: false },
44615
44703
  direction: { type: "string" },
44616
44704
  "business-context": { type: "string" },
44705
+ brand: { type: "string" },
44706
+ url: { type: "string" },
44707
+ "pasted-file": { type: "string" },
44708
+ "from-company": { type: "boolean", default: false },
44617
44709
  seed: { type: "string" },
44618
44710
  emails: { type: "string" },
44619
44711
  ttl: { type: "string" },
@@ -44644,6 +44736,15 @@ function parseCliArgs() {
44644
44736
  });
44645
44737
  if (values["no-edge"])
44646
44738
  values["no-bridge"] = true;
44739
+ const asFlag = !values["list-models"] && !values["list-agents"] && !values["show-config"] ? WORD_FLAGS[positionals[0]] : undefined;
44740
+ if (asFlag) {
44741
+ values[asFlag] = true;
44742
+ positionals.shift();
44743
+ }
44744
+ if (positionals[1] === "help" && HELP_SUBCOMMANDS.includes(positionals[0])) {
44745
+ values.help = true;
44746
+ positionals.splice(1, 1);
44747
+ }
44647
44748
  return { values, positionals };
44648
44749
  }
44649
44750
 
@@ -47290,6 +47391,9 @@ Usage:
47290
47391
  tfa-cli agent list List agents (name, model, id, paths)
47291
47392
  tfa-cli agent get <agent> Show a single agent's settings
47292
47393
  tfa-cli agent update <agent> <field=value>… Update one or more settings
47394
+ tfa-cli agent create [<field=value>…] Create an agent (defaults, then apply fields)
47395
+ tfa-cli agent global [<field=value>…] Show / update the user's global agent defaults
47396
+ tfa-cli agent reorder <agent>… Set the sidebar order (names or ids)
47293
47397
  tfa-cli agent delete <agent> Delete an agent configuration (asks to confirm)
47294
47398
 
47295
47399
  <agent> is a name or id (unique partial name also works).
@@ -47306,6 +47410,10 @@ Examples:
47306
47410
  tfa-cli agent update <agent> model=claude
47307
47411
  tfa-cli agent update <agent> model=anthropic:anthropic/claude-opus-5 temperature=0.5
47308
47412
  tfa-cli agent update <agent> sysmsg="You are a terse video editor."
47413
+ tfa-cli agent create name=Reviewer model=claude sysmsg="Review diffs only."
47414
+
47415
+ From an agent shell (dst_ token) permissions, mcpConfigs, edgesMcpConfigs,
47416
+ devicesConfig and delete are refused by the backend.
47309
47417
  `);
47310
47418
  }
47311
47419
  var FIELD_ALIASES = {
@@ -47314,7 +47422,7 @@ var FIELD_ALIASES = {
47314
47422
  temp: "temperature",
47315
47423
  thinking: "thinkingLevel"
47316
47424
  };
47317
- function parseAssignment(arg) {
47425
+ function parseAssignment(arg, aliases = {}) {
47318
47426
  const eq = arg.indexOf("=");
47319
47427
  if (eq < 1) {
47320
47428
  process.stderr.write(`${RED}Invalid field assignment '${arg}', expected field=value${RESET}
@@ -47322,7 +47430,7 @@ function parseAssignment(arg) {
47322
47430
  process.exit(2);
47323
47431
  }
47324
47432
  const rawKey = arg.slice(0, eq);
47325
- const key = FIELD_ALIASES[rawKey] || rawKey;
47433
+ const key = aliases[rawKey] || rawKey;
47326
47434
  const raw = arg.slice(eq + 1);
47327
47435
  try {
47328
47436
  return [key, JSON.parse(raw)];
@@ -47330,6 +47438,9 @@ function parseAssignment(arg) {
47330
47438
  return [key, raw];
47331
47439
  }
47332
47440
  }
47441
+ function parseAssignments(args, aliases = {}) {
47442
+ return Object.fromEntries(args.map((a) => parseAssignment(a, aliases)));
47443
+ }
47333
47444
  function resolveAgent(agents, query) {
47334
47445
  const { match, ambiguous } = resolveAgentMatch2(agents, query);
47335
47446
  if (match)
@@ -47377,7 +47488,7 @@ async function agentCommand(api, positionals, args, formatPath) {
47377
47488
  `);
47378
47489
  process.exit(2);
47379
47490
  }
47380
- const updates = Object.fromEntries(assignments.map(parseAssignment));
47491
+ const updates = parseAssignments(assignments, FIELD_ALIASES);
47381
47492
  const agents = await api.listAgentSettings();
47382
47493
  const agent = resolveAgent(agents, query);
47383
47494
  const id = getItemId2(agent);
@@ -47388,6 +47499,45 @@ async function agentCommand(api, positionals, args, formatPath) {
47388
47499
  }
47389
47500
  const summary = Object.keys(updates).map((k) => `${k}=${JSON.stringify(updated[k])}`).join(" ");
47390
47501
  process.stderr.write(`${GREEN}✅ ${getDisplayName2(agent)} updated: ${summary}${RESET}
47502
+ `);
47503
+ return;
47504
+ }
47505
+ if (sub === "global") {
47506
+ const updates = parseAssignments(positionals.slice(2), FIELD_ALIASES);
47507
+ const global = Object.keys(updates).length ? await api.updateGlobalAgentSettings(updates) : await api.getGlobalAgentSettings();
47508
+ if (args.json) {
47509
+ console.log(JSON.stringify(global, null, 2));
47510
+ return;
47511
+ }
47512
+ for (const [k, v] of Object.entries(global))
47513
+ process.stderr.write(` ${DIM}${k}:${RESET} ${typeof v === "string" ? v : JSON.stringify(v)}
47514
+ `);
47515
+ return;
47516
+ }
47517
+ if (sub === "reorder") {
47518
+ const queries = positionals.slice(2);
47519
+ if (!queries.length) {
47520
+ process.stderr.write(`${RED}Usage: tfa-cli agent reorder <agent>…${RESET}
47521
+ `);
47522
+ process.exit(2);
47523
+ }
47524
+ const agents = await api.listAgentSettings();
47525
+ const ids = queries.map((q) => getItemId2(resolveAgent(agents, q)));
47526
+ await api.reorderAgentSettings(ids);
47527
+ process.stderr.write(`${GREEN}✅ Agents reordered${RESET}
47528
+ `);
47529
+ return;
47530
+ }
47531
+ if (sub === "create") {
47532
+ const updates = parseAssignments(positionals.slice(2), FIELD_ALIASES);
47533
+ const created = await api.createAgent();
47534
+ const id = getItemId2(created);
47535
+ const agent = Object.keys(updates).length ? await api.updateAgentSettings(id, id, updates) : created;
47536
+ if (args.json) {
47537
+ console.log(JSON.stringify(agent, null, 2));
47538
+ return;
47539
+ }
47540
+ process.stderr.write(`${GREEN}✅ Created agent ${getDisplayName2(agent)}${RESET} ${DIM}${id}${RESET}
47391
47541
  `);
47392
47542
  return;
47393
47543
  }
@@ -47423,6 +47573,507 @@ async function agentCommand(api, positionals, args, formatPath) {
47423
47573
  process.exit(2);
47424
47574
  }
47425
47575
 
47576
+ // src/agent-command.ts
47577
+ function parseAssignment2(arg, aliases = {}) {
47578
+ const eq = arg.indexOf("=");
47579
+ if (eq < 1) {
47580
+ process.stderr.write(`${RED}Invalid field assignment '${arg}', expected field=value${RESET}
47581
+ `);
47582
+ process.exit(2);
47583
+ }
47584
+ const rawKey = arg.slice(0, eq);
47585
+ const key = aliases[rawKey] || rawKey;
47586
+ const raw = arg.slice(eq + 1);
47587
+ try {
47588
+ return [key, JSON.parse(raw)];
47589
+ } catch {
47590
+ return [key, raw];
47591
+ }
47592
+ }
47593
+ function parseAssignments2(args, aliases = {}) {
47594
+ return Object.fromEntries(args.map((a) => parseAssignment2(a, aliases)));
47595
+ }
47596
+ function resolveAgent2(agents, query) {
47597
+ const { match, ambiguous } = resolveAgentMatch2(agents, query);
47598
+ if (match)
47599
+ return match;
47600
+ if (ambiguous) {
47601
+ process.stderr.write(`${RED}Ambiguous agent '${query}' — ${ambiguous.length} matches. Re-run with the exact id:${RESET}
47602
+ `);
47603
+ for (const a of ambiguous)
47604
+ process.stderr.write(` ${getDisplayName2(a)} ${DIM}${getItemId2(a)}${RESET}
47605
+ `);
47606
+ process.exit(2);
47607
+ }
47608
+ process.stderr.write(`${RED}No agent matching '${query}'${RESET}
47609
+ `);
47610
+ process.exit(2);
47611
+ }
47612
+
47613
+ // src/args.ts
47614
+ var VERSION2 = package_default.version;
47615
+ function getEnv2(name) {
47616
+ return (process.env[`TODOFORAI_${name}`] || process.env[`TODO4AI_${name}`] || "").trim();
47617
+ }
47618
+ var STATUS_HELP2 = {
47619
+ ["READY" /* READY */]: "AI finished the work on the TODO",
47620
+ ["READY_CHECKED" /* READY_CHECKED */]: "AI finished and the user reviewed it",
47621
+ ["DONE" /* DONE */]: "Completed and finalized",
47622
+ ["REVIEW_REQUESTED" /* REVIEW_REQUESTED */]: "Asks the user to review",
47623
+ ["POSTPONED" /* POSTPONED */]: "Put off for later",
47624
+ ["ARCHIVED" /* ARCHIVED */]: "Archived (hidden from active list)",
47625
+ ["DELETED" /* DELETED */]: "Marked for deletion"
47626
+ };
47627
+
47628
+ // src/manage-command.ts
47629
+ function printTodoHelp() {
47630
+ process.stderr.write(`
47631
+ tfa-cli todo — edit a todo's fields
47632
+
47633
+ Usage:
47634
+ tfa-cli todo set <todo-id|-> <field=value>… Update fields ("-" = $TODOFORAI_TODO_ID)
47635
+ tfa-cli todo restore <todo-id> Bring a trashed todo back (undo of 'delete')
47636
+
47637
+ Fields (PUT /todos/{id}):
47638
+ title first-line content (alias: content)
47639
+ group group slug; "" makes it ungrouped (alias: groupTag)
47640
+ star true|false (starredAt now / 0)
47641
+ schedule unix ms timestamp, 0 = immediate (alias: scheduledTimestamp)
47642
+ agent agentSettingsId (alias: agentSettingsId)
47643
+ brand businessContextId (alias: businessContextId)
47644
+ public true|false — refused from an agent shell (isPublic)
47645
+
47646
+ tfa-cli todo message <todo-id|-> <message-id> <field=value>…
47647
+ Edit a SCHEDULED (not yet run) message: content, schedule (unix ms;
47648
+ 0 = run now). Runs with the todo's own agent.
47649
+
47650
+ Examples:
47651
+ tfa-cli todo set - title="Ship v2 landing" group=marketing
47652
+ tfa-cli todo set 3f2a91 star=true
47653
+ tfa-cli todo message - 8c1d… schedule=1760000000000
47654
+ tfa-cli status <todo-id> DONE (status has its own command)
47655
+ `);
47656
+ }
47657
+ function printProjectHelp() {
47658
+ process.stderr.write(`
47659
+ tfa-cli project — edit the current project
47660
+
47661
+ --project <id> or $TODOFORAI_PROJECT_ID selects the project.
47662
+
47663
+ Usage:
47664
+ tfa-cli project list Projects you can access (* = default)
47665
+ tfa-cli project set <field=value>… name=… description=… brand=<businessContextId>
47666
+ (isPublic refused from an agent shell)
47667
+ tfa-cli project default Make it the project you land on at /
47668
+ tfa-cli project agent <agent> Default agent for new todos in this project
47669
+ tfa-cli project settings <field=value>… Project settings (PUT /settings/projects/{id})
47670
+ tfa-cli project groups List todo groups
47671
+ tfa-cli project groups set <slug> <field=value>… Create/update a group: name description pinned archived order
47672
+ tfa-cli project groups reorder <slug>… Set wall order
47673
+
47674
+ Examples:
47675
+ tfa-cli project set name="Q4 launch"
47676
+ tfa-cli project groups set marketing name=Marketing description="Top-of-funnel work"
47677
+ tfa-cli project groups reorder marketing sales ops
47678
+ `);
47679
+ }
47680
+ var TODO_ALIASES = {
47681
+ title: "content",
47682
+ group: "groupTag",
47683
+ schedule: "scheduledTimestamp",
47684
+ agent: "agentSettingsId",
47685
+ brand: "businessContextId",
47686
+ public: "isPublic"
47687
+ };
47688
+ var PROJECT_ALIASES = { brand: "businessContextId" };
47689
+ function fail(msg) {
47690
+ process.stderr.write(`${RED}${msg}${RESET}
47691
+ `);
47692
+ process.exit(2);
47693
+ }
47694
+ async function todoCommand(api, positionals, args) {
47695
+ const [, sub, idArg, ...rest] = positionals;
47696
+ if (sub === "restore") {
47697
+ if (!idArg)
47698
+ fail("Usage: tfa-cli todo restore <todo-id>");
47699
+ await api.restoreTodo(idArg);
47700
+ process.stderr.write(`${GREEN}✅ Restored ${idArg}${RESET}
47701
+ `);
47702
+ return;
47703
+ }
47704
+ const todoId = !idArg || idArg === "-" ? getEnv2("TODO_ID") : idArg;
47705
+ if (sub === "message") {
47706
+ const [messageId, ...fields] = rest;
47707
+ if (!todoId || !messageId || !fields.length)
47708
+ fail("Usage: tfa-cli todo message <todo-id|-> <message-id> <field=value>…");
47709
+ const updates2 = parseAssignments2(fields, { schedule: "scheduledTimestamp" });
47710
+ const agentSettingsId = (await api.getTodo(todoId))?.agentSettingsId;
47711
+ if (!agentSettingsId)
47712
+ fail("Todo has no agent assigned");
47713
+ await api.updateScheduledMessage(todoId, messageId, agentSettingsId, updates2);
47714
+ process.stderr.write(`${GREEN}✅ message ${messageId} updated: ${Object.keys(updates2).join(", ")}${RESET}
47715
+ `);
47716
+ return;
47717
+ }
47718
+ if (sub !== "set") {
47719
+ printTodoHelp();
47720
+ process.exit(sub ? 2 : 0);
47721
+ }
47722
+ if (!todoId || !rest.length)
47723
+ fail("Usage: tfa-cli todo set <todo-id|-> <field=value>…");
47724
+ const updates = parseAssignments2(rest, TODO_ALIASES);
47725
+ if ("star" in updates) {
47726
+ if (typeof updates.star !== "boolean")
47727
+ fail("star must be true or false");
47728
+ updates.starredAt = updates.star ? Date.now() : 0;
47729
+ delete updates.star;
47730
+ }
47731
+ await api.updateTodo(todoId, updates);
47732
+ if (args.json) {
47733
+ console.log(JSON.stringify({ todoId, ...updates }));
47734
+ return;
47735
+ }
47736
+ process.stderr.write(`${GREEN}✅ ${todoId} updated: ${Object.keys(updates).join(", ")}${RESET}
47737
+ `);
47738
+ }
47739
+ async function projectCommand(api, positionals, args) {
47740
+ const [, sub, ...rest] = positionals;
47741
+ const projectId = args.project || getEnv2("PROJECT_ID");
47742
+ if (!sub) {
47743
+ printProjectHelp();
47744
+ process.exit(0);
47745
+ }
47746
+ if (sub === "list" || sub === "ls") {
47747
+ const projects = await api.listProjects();
47748
+ if (args.json) {
47749
+ console.log(JSON.stringify(projects, null, 2));
47750
+ return;
47751
+ }
47752
+ for (const p of projects) {
47753
+ const pr = p.project ?? p;
47754
+ process.stderr.write(`${pr.isDefault ? "*" : " "} ${pr.name ?? ""} ${DIM}${pr.id}${RESET}
47755
+ `);
47756
+ }
47757
+ return;
47758
+ }
47759
+ if (!projectId)
47760
+ fail("No project — pass --project <id> or set TODOFORAI_PROJECT_ID");
47761
+ if (sub === "set") {
47762
+ if (!rest.length)
47763
+ fail("Usage: tfa-cli project set <field=value>…");
47764
+ const updates = parseAssignments2(rest, PROJECT_ALIASES);
47765
+ await api.updateProject(projectId, updates);
47766
+ process.stderr.write(`${GREEN}✅ project updated: ${Object.keys(updates).join(", ")}${RESET}
47767
+ `);
47768
+ return;
47769
+ }
47770
+ if (sub === "default") {
47771
+ await api.setDefaultProject(projectId);
47772
+ process.stderr.write(`${GREEN}✅ ${projectId} is now the default project${RESET}
47773
+ `);
47774
+ return;
47775
+ }
47776
+ if (sub === "agent") {
47777
+ if (!rest[0])
47778
+ fail("Usage: tfa-cli project agent <agent>");
47779
+ const agent = resolveAgent2(await api.listAgentSettings(), rest[0]);
47780
+ await api.setProjectDefaultAgent(projectId, getItemId2(agent));
47781
+ process.stderr.write(`${GREEN}✅ default agent for ${projectId}: ${getDisplayName2(agent)}${RESET}
47782
+ `);
47783
+ return;
47784
+ }
47785
+ if (sub === "settings") {
47786
+ if (!rest.length)
47787
+ fail("Usage: tfa-cli project settings <field=value>…");
47788
+ const settings = parseAssignments2(rest);
47789
+ await api.updateProjectSettings(projectId, settings);
47790
+ process.stderr.write(`${GREEN}✅ project settings updated: ${Object.keys(settings).join(", ")}${RESET}
47791
+ `);
47792
+ return;
47793
+ }
47794
+ if (sub === "groups") {
47795
+ const [verb, ...gargs] = rest;
47796
+ if (!verb) {
47797
+ const groups = await api.listProjectGroups(projectId);
47798
+ if (args.json) {
47799
+ console.log(JSON.stringify(groups, null, 2));
47800
+ return;
47801
+ }
47802
+ for (const g of groups)
47803
+ process.stderr.write(`${getItemId2(g) || g.slug} ${g.name ?? ""} ${DIM}${g.description ?? ""}${RESET}
47804
+ `);
47805
+ return;
47806
+ }
47807
+ if (verb === "set") {
47808
+ const [slug, ...fields] = gargs;
47809
+ if (!slug)
47810
+ fail("Usage: tfa-cli project groups set <slug> <field=value>…");
47811
+ const group = await api.upsertProjectGroup(projectId, slug, parseAssignments2(fields));
47812
+ if (args.json) {
47813
+ console.log(JSON.stringify(group, null, 2));
47814
+ return;
47815
+ }
47816
+ process.stderr.write(`${GREEN}✅ group ${slug} saved${RESET}
47817
+ `);
47818
+ return;
47819
+ }
47820
+ if (verb === "reorder") {
47821
+ if (!gargs.length)
47822
+ fail("Usage: tfa-cli project groups reorder <slug>…");
47823
+ await api.reorderProjectGroups(projectId, gargs);
47824
+ process.stderr.write(`${GREEN}✅ groups reordered${RESET}
47825
+ `);
47826
+ return;
47827
+ }
47828
+ fail(`Unknown 'project groups' verb: ${verb}`);
47829
+ }
47830
+ fail(`Unknown 'project' subcommand: ${sub}`);
47831
+ }
47832
+ function printBrandHelp() {
47833
+ process.stderr.write(`
47834
+ tfa-cli brand — business contexts (brands) and the learned writing voice
47835
+
47836
+ The brand page text itself is a file: read/write todoforai:business-context.
47837
+ These commands manage the contexts and the voice learned for them.
47838
+
47839
+ Usage:
47840
+ tfa-cli brand list Contexts (* = selected)
47841
+ tfa-cli brand create <name> New brand (empty .md page)
47842
+ tfa-cli brand rename <brand> <name>
47843
+ tfa-cli brand select <brand|none> Active brand for the account
47844
+ tfa-cli brand voice Learned profile + sources
47845
+ tfa-cli brand voice answers [<q>=<a>…] Show / set the brand-voice answers (strings)
47846
+ tfa-cli brand voice collect <channel> [--url U | --pasted-file <F|->]
47847
+ Add a writing sample source
47848
+ channels: X LinkedIn Facebook Instagram
47849
+ Gmail Outlook Slack Teams
47850
+ tfa-cli brand voice remove <channel>
47851
+ tfa-cli brand voice learn [--from-company] Run the refinement loop, store the profile
47852
+
47853
+ <brand> is an id or name (unique partial works). Voice subcommands use the
47854
+ selected brand unless --brand <id|name> is given (--pasted-file - reads stdin).
47855
+ Delete / reset are UI-only.
47856
+
47857
+ Questions the UI asks (keys for 'answers'):
47858
+ "What makes you different from competitors?"
47859
+ "What are you working on right now?"
47860
+ "A frustrated customer says things aren't working. How do you reply?"
47861
+ `);
47862
+ }
47863
+ async function resolveBrand(api, query, selectedId) {
47864
+ const contexts = await api.listBusinessContexts();
47865
+ if (!query) {
47866
+ const sel = contexts.find((c3) => c3.id === selectedId);
47867
+ if (sel)
47868
+ return sel;
47869
+ if (contexts.length === 1)
47870
+ return contexts[0];
47871
+ fail("No brand selected — pass --brand <id|name> or 'tfa-cli brand select'");
47872
+ }
47873
+ const q = query.toLowerCase();
47874
+ const exact = contexts.find((c3) => c3.id === query || c3.name.toLowerCase() === q);
47875
+ if (exact)
47876
+ return exact;
47877
+ const partial = contexts.filter((c3) => c3.id.startsWith(query) || c3.name.toLowerCase().includes(q));
47878
+ if (partial.length === 1)
47879
+ return partial[0];
47880
+ fail(partial.length ? `Ambiguous brand '${query}'` : `No brand matching '${query}'`);
47881
+ }
47882
+ async function brandCommand(api, positionals, args) {
47883
+ const [, sub, ...rest] = positionals;
47884
+ if (!sub || sub === "list") {
47885
+ const contexts = await api.listBusinessContexts();
47886
+ const selected = (await api.getProfile()).user?.selectedBusinessContextId;
47887
+ if (args.json) {
47888
+ console.log(JSON.stringify(contexts.map((c3) => ({ ...c3, selected: c3.id === selected })), null, 2));
47889
+ return;
47890
+ }
47891
+ for (const c3 of contexts)
47892
+ process.stderr.write(`${c3.id === selected ? "*" : " "} ${c3.name} ${DIM}${c3.id}${RESET}
47893
+ `);
47894
+ return;
47895
+ }
47896
+ if (sub === "create") {
47897
+ if (!rest[0])
47898
+ fail("Usage: tfa-cli brand create <name>");
47899
+ const c3 = await api.createBusinessContext(rest[0]);
47900
+ if (args.json) {
47901
+ console.log(JSON.stringify(c3, null, 2));
47902
+ return;
47903
+ }
47904
+ process.stderr.write(`${GREEN}✅ brand ${c3.name} created ${DIM}${c3.id}${RESET}
47905
+ `);
47906
+ return;
47907
+ }
47908
+ if (sub === "rename") {
47909
+ if (!rest[1])
47910
+ fail("Usage: tfa-cli brand rename <brand> <name>");
47911
+ const c3 = await resolveBrand(api, rest[0]);
47912
+ await api.renameBusinessContext(c3.id, rest[1]);
47913
+ process.stderr.write(`${GREEN}✅ renamed to ${rest[1]}${RESET}
47914
+ `);
47915
+ return;
47916
+ }
47917
+ if (sub === "select") {
47918
+ if (!rest[0])
47919
+ fail("Usage: tfa-cli brand select <brand|none>");
47920
+ const id = rest[0] === "none" ? null : (await resolveBrand(api, rest[0])).id;
47921
+ await api.selectBusinessContext(id);
47922
+ process.stderr.write(`${GREEN}✅ selected brand: ${id ?? "none"}${RESET}
47923
+ `);
47924
+ return;
47925
+ }
47926
+ if (sub === "voice")
47927
+ return voiceCommand(api, rest, args);
47928
+ fail(`Unknown 'brand' subcommand: ${sub}`);
47929
+ }
47930
+ async function voiceCommand(api, rest, args) {
47931
+ const [verb, ...vargs] = rest;
47932
+ const onboarding = await api.getOnboarding();
47933
+ if (verb === "answers") {
47934
+ if (vargs.length) {
47935
+ const given = Object.fromEntries(vargs.map((a) => {
47936
+ const i = a.indexOf("=");
47937
+ if (i < 1)
47938
+ fail(`Expected question=answer, got '${a}'`);
47939
+ return [a.slice(0, i), a.slice(i + 1)];
47940
+ }));
47941
+ const styleAnswers = { ...onboarding.styleAnswers ?? {}, ...given };
47942
+ await api.patchOnboarding({ styleAnswers });
47943
+ process.stderr.write(`${GREEN}✅ ${vargs.length} answer(s) saved${RESET}
47944
+ `);
47945
+ return;
47946
+ }
47947
+ const answers = onboarding.styleAnswers ?? {};
47948
+ if (args.json) {
47949
+ console.log(JSON.stringify(answers, null, 2));
47950
+ return;
47951
+ }
47952
+ for (const [q, a] of Object.entries(answers))
47953
+ process.stderr.write(`${q}
47954
+ ${DIM}${a || "(empty)"}${RESET}
47955
+ `);
47956
+ return;
47957
+ }
47958
+ const brand = await resolveBrand(api, args.brand, (await api.getProfile()).user?.selectedBusinessContextId);
47959
+ if (!verb || verb === "show") {
47960
+ const profile = onboarding.voiceProfiles?.[brand.id];
47961
+ const { sources } = await api.listVoiceSources(brand.id);
47962
+ if (args.json) {
47963
+ console.log(JSON.stringify({ brand, profile, sources }, null, 2));
47964
+ return;
47965
+ }
47966
+ process.stderr.write(`${brand.name} ${DIM}${brand.id}${RESET}
47967
+ `);
47968
+ process.stderr.write(profile ? `
47969
+ ${profile.profile}
47970
+ ${DIM}match ${profile.match}/100 · source ${profile.source}${RESET}
47971
+ ` : `${DIM}(no voice learned yet)${RESET}
47972
+ `);
47973
+ for (const s of sources)
47974
+ process.stderr.write(` ${s.channel} ${DIM}${s.posts ?? "?"} posts · ${s.chars ?? "?"} chars${RESET}
47975
+ `);
47976
+ return;
47977
+ }
47978
+ if (verb === "collect") {
47979
+ const channel = vargs[0];
47980
+ if (!channel)
47981
+ fail("Usage: tfa-cli brand voice collect <channel> [--url U | --pasted-file F|-]");
47982
+ let pasted;
47983
+ if (args["pasted-file"]) {
47984
+ const { readFileSync: readFileSync3 } = await import("node:fs");
47985
+ pasted = readFileSync3(args["pasted-file"] === "-" ? 0 : args["pasted-file"], "utf8");
47986
+ }
47987
+ const { sources } = await api.collectVoiceSource(brand.id, channel, { url: args.url, pasted });
47988
+ process.stderr.write(`${GREEN}✅ ${channel} collected (${sources.length} source(s))${RESET}
47989
+ `);
47990
+ return;
47991
+ }
47992
+ if (verb === "remove") {
47993
+ if (!vargs[0])
47994
+ fail("Usage: tfa-cli brand voice remove <channel>");
47995
+ await api.removeVoiceSource(brand.id, vargs[0]);
47996
+ process.stderr.write(`${GREEN}✅ ${vargs[0]} removed${RESET}
47997
+ `);
47998
+ return;
47999
+ }
48000
+ if (verb === "learn") {
48001
+ process.stderr.write(`${DIM}learning voice for ${brand.name}…${RESET}
48002
+ `);
48003
+ const res = await api.refineBrandVoice(brand.id, onboarding.styleAnswers ?? {}, args["from-company"] ? "company" : undefined);
48004
+ if (!res.profile)
48005
+ fail("Nothing to learn from — add answers or collect a source first");
48006
+ const { [brand.id]: _stale, ...styleAiAnswers } = onboarding.styleAiAnswers ?? {};
48007
+ await api.patchOnboarding({
48008
+ voiceProfiles: { ...onboarding.voiceProfiles ?? {}, [brand.id]: { ...res, updatedAt: Date.now() } },
48009
+ styleAiAnswers
48010
+ });
48011
+ if (args.json) {
48012
+ console.log(JSON.stringify(res, null, 2));
48013
+ return;
48014
+ }
48015
+ process.stderr.write(`${GREEN}✅ voice learned (match ${res.match}/100, source ${res.source})${RESET}
48016
+ ${res.profile}
48017
+ `);
48018
+ return;
48019
+ }
48020
+ fail(`Unknown 'brand voice' verb: ${verb}`);
48021
+ }
48022
+ function printDeviceHelp() {
48023
+ process.stderr.write(`
48024
+ tfa-cli device — the user's paired machines
48025
+
48026
+ Usage:
48027
+ tfa-cli device list
48028
+ tfa-cli device rename <device> <name> name: [a-zA-Z0-9][a-zA-Z0-9_]*, ≤64
48029
+ tfa-cli device wallpaper <device> auto|off desktop-wallpaper capture (off deletes stored images)
48030
+
48031
+ <device> is an id, name or hostname (unique partial works).
48032
+ exec / reboot / update / unpair / workspace paths are UI-only from an agent shell.
48033
+ `);
48034
+ }
48035
+ async function deviceCommand(api, positionals, args) {
48036
+ const [, sub, query, value] = positionals;
48037
+ const devices = await api.listDevices();
48038
+ if (!sub || sub === "list") {
48039
+ if (args.json) {
48040
+ console.log(JSON.stringify(devices, null, 2));
48041
+ return;
48042
+ }
48043
+ for (const d of devices)
48044
+ process.stderr.write(`${d.status === "ONLINE" ? GREEN + "●" : DIM + "○"}${RESET} ${d.name} ${DIM}${d.deviceType} ${d.metadata?.identity?.hostname ?? ""} ${d.id}${RESET}
48045
+ `);
48046
+ return;
48047
+ }
48048
+ if (!query)
48049
+ fail(`Usage: tfa-cli device ${sub} <device> …`);
48050
+ const q = query.toLowerCase();
48051
+ const hit = devices.filter((d) => d.id === query || d.name.toLowerCase() === q || d.metadata?.identity?.hostname?.toLowerCase() === q);
48052
+ const found = hit.length === 1 ? hit[0] : (() => {
48053
+ const p = devices.filter((d) => d.id.startsWith(query) || d.name.toLowerCase().includes(q) || d.metadata?.identity?.hostname?.toLowerCase().includes(q));
48054
+ if (p.length === 1)
48055
+ return p[0];
48056
+ fail(p.length ? `Ambiguous device '${query}'` : `No device matching '${query}'`);
48057
+ })();
48058
+ if (sub === "rename") {
48059
+ if (!value)
48060
+ fail("Usage: tfa-cli device rename <device> <name>");
48061
+ await api.renameDevice(found.id, value);
48062
+ process.stderr.write(`${GREEN}✅ ${found.name} → ${value}${RESET}
48063
+ `);
48064
+ return;
48065
+ }
48066
+ if (sub === "wallpaper") {
48067
+ if (value !== "auto" && value !== "off")
48068
+ fail("Usage: tfa-cli device wallpaper <device> auto|off");
48069
+ await api.setDeviceWallpaperMode(found.id, value);
48070
+ process.stderr.write(`${GREEN}✅ ${found.name} wallpaper: ${value}${RESET}
48071
+ `);
48072
+ return;
48073
+ }
48074
+ fail(`Unknown 'device' subcommand: ${sub}`);
48075
+ }
48076
+
47426
48077
  // src/list-todos.ts
47427
48078
  import { parseArgs as parseArgs2 } from "util";
47428
48079
  var STATUS_COLOR = {
@@ -71932,6 +72583,16 @@ function formatPathWithTilde(path5) {
71932
72583
  const home = homedir3();
71933
72584
  return path5.startsWith(home) ? path5.replace(home, "~") : path5;
71934
72585
  }
72586
+ function promptArg(words) {
72587
+ if (words.length === 0)
72588
+ return;
72589
+ if (words.length > 1 || !/\s/.test(words[0])) {
72590
+ process.stderr.write(`${RED3}Error: "${words.join(" ")}" is not a subcommand, and a prompt must be ONE quoted argument, e.g. tfa-cli "fix the login bug" (or pipe it on stdin). Run tfa-cli --help for subcommands.${RESET2}
72591
+ `);
72592
+ process.exit(2);
72593
+ }
72594
+ return words[0];
72595
+ }
71935
72596
  async function interactiveLoop(ws, api2, todoId, projectId, agent2, json2, autoApprove, cfg) {
71936
72597
  while (true) {
71937
72598
  try {
@@ -72033,15 +72694,35 @@ Cancelled by user (Ctrl+C)
72033
72694
  printStatusHelp();
72034
72695
  process.exit(0);
72035
72696
  }
72697
+ if ((positionals[0] === "show" || positionals[0] === "open") && args.help) {
72698
+ printShowHelp();
72699
+ process.exit(0);
72700
+ }
72036
72701
  if (positionals[0] === "agent" && args.help) {
72037
72702
  printAgentHelp();
72038
72703
  process.exit(0);
72039
72704
  }
72705
+ if (positionals[0] === "todo" && args.help) {
72706
+ printTodoHelp();
72707
+ process.exit(0);
72708
+ }
72709
+ if (positionals[0] === "project" && args.help) {
72710
+ printProjectHelp();
72711
+ process.exit(0);
72712
+ }
72713
+ if (positionals[0] === "brand" && args.help) {
72714
+ printBrandHelp();
72715
+ process.exit(0);
72716
+ }
72717
+ if (positionals[0] === "device" && args.help) {
72718
+ printDeviceHelp();
72719
+ process.exit(0);
72720
+ }
72040
72721
  if ((positionals[0] === "list" || positionals[0] === "ls") && args.help) {
72041
72722
  printListTodosHelp();
72042
72723
  process.exit(0);
72043
72724
  }
72044
- if (args.help && !["list", "ls", "agent"].includes(positionals[0])) {
72725
+ if (args.help && !["list", "ls", "agent", "todo", "project", "brand", "device"].includes(positionals[0])) {
72045
72726
  printUsage();
72046
72727
  process.exit(0);
72047
72728
  }
@@ -72153,6 +72834,22 @@ Cancelled by user (Ctrl+C)
72153
72834
  `);
72154
72835
  return;
72155
72836
  }
72837
+ if (positionals[0] === "todo") {
72838
+ await todoCommand(api2, positionals, args);
72839
+ return;
72840
+ }
72841
+ if (positionals[0] === "project") {
72842
+ await projectCommand(api2, positionals, args);
72843
+ return;
72844
+ }
72845
+ if (positionals[0] === "brand") {
72846
+ await brandCommand(api2, positionals, args);
72847
+ return;
72848
+ }
72849
+ if (positionals[0] === "device") {
72850
+ await deviceCommand(api2, positionals, args);
72851
+ return;
72852
+ }
72156
72853
  if (positionals[0] === "delete") {
72157
72854
  const todoId = positionals[1];
72158
72855
  if (!todoId) {
@@ -72491,7 +73188,7 @@ Cancelled by user (Ctrl+C)
72491
73188
  process.exit(1);
72492
73189
  }
72493
73190
  const promptWords = positionals[0] === "start" ? positionals.slice(2) : positionals;
72494
- const content2 = promptWords.join(" ").trim();
73191
+ const content2 = (promptArg(promptWords) ?? "").trim();
72495
73192
  const todo2 = await api2.startFromSpec(projectId2, templateId, {
72496
73193
  ...content2 ? { content: content2 } : {},
72497
73194
  ...groupTag ? { groupTag } : {},
@@ -72566,7 +73263,7 @@ Resumed: ${CYAN2}${getFrontendUrl(apiUrl, todoId)}${RESET2}
72566
73263
  const ws2 = new FrontendWebSocket(apiUrl, apiKey);
72567
73264
  await ws2.connect();
72568
73265
  const autoApprove = !!args["dangerously-skip-permissions"];
72569
- const followUp = positionals.length > 0 ? positionals.join(" ") : process.stdin.isTTY ? "" : await readStdin();
73266
+ const followUp = promptArg(positionals) ?? (process.stdin.isTTY ? "" : await readStdin());
72570
73267
  if (followUp) {
72571
73268
  cfg.addToHistory(followUp);
72572
73269
  await api2.addMessage(projectId2, followUp, agent3, todoId);
@@ -72584,11 +73281,7 @@ Resumed: ${CYAN2}${getFrontendUrl(apiUrl, todoId)}${RESET2}
72584
73281
  `);
72585
73282
  process.exit(2);
72586
73283
  }
72587
- if (positionals.length === 1 && !/\s/.test(positionals[0])) {
72588
- process.stderr.write(`${RED3}Error: "${positionals[0]}" is not a subcommand, and a prompt needs more than one word (or pipe it on stdin).${RESET2}
72589
- `);
72590
- process.exit(2);
72591
- }
73284
+ const argPrompt = promptArg(positionals);
72592
73285
  const ws = args["no-watch"] ? null : new FrontendWebSocket(apiUrl, apiKey);
72593
73286
  const wsReady = ws ? ws.connect() : null;
72594
73287
  const bridgeReady = !args["no-bridge"] && !args["no-watch"] && !args.isolated ? ensureBridgeRunning2(apiUrl, apiKey) : null;
@@ -72648,12 +73341,7 @@ Resumed: ${CYAN2}${getFrontendUrl(apiUrl, todoId)}${RESET2}
72648
73341
  }
72649
73342
  process.stderr.write(`${DIM2}Tip: ${randomTip()}${RESET2}
72650
73343
  `);
72651
- let content;
72652
- if (positionals.length > 0) {
72653
- content = positionals.join(" ");
72654
- } else {
72655
- content = await readStdin();
72656
- }
73344
+ const content = argPrompt ?? await readStdin();
72657
73345
  const envProjectId = getEnv("PROJECT_ID");
72658
73346
  const hasProject = args.project || envProjectId || cfgScope.data.default_project_id;
72659
73347
  const storedAgent = cfgScope.data.default_agent_settings;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@todoforai/cli",
3
- "version": "0.1.47",
3
+ "version": "0.1.49",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "todoforai-cli": "bin/todoforai-cli.js",