@todoforai/cli 0.1.51 → 0.1.53

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 +79 -116
  2. package/package.json +1 -1
@@ -42884,29 +42884,26 @@ class ApiClient {
42884
42884
  setDeviceWallpaperMode(id, mode) {
42885
42885
  return this.request("POST", `/api/v1/devices/${id}/wallpaper-mode`, { id, mode });
42886
42886
  }
42887
- listBusinessContexts() {
42888
- return this.request("GET", "/api/v1/business-context");
42887
+ getBusinessContext(projectId) {
42888
+ return this.request("GET", `/api/v1/projects/${projectId}/business-context`);
42889
42889
  }
42890
- createBusinessContext(name) {
42891
- return this.request("POST", "/api/v1/business-context", { name });
42890
+ createBusinessContext(projectId, name) {
42891
+ return this.request("POST", `/api/v1/projects/${projectId}/business-context`, { projectId, name });
42892
42892
  }
42893
- renameBusinessContext(id, name) {
42894
- return this.request("PUT", `/api/v1/business-context/${id}`, { id, name });
42893
+ renameBusinessContext(projectId, name) {
42894
+ return this.request("PUT", `/api/v1/projects/${projectId}/business-context`, { projectId, name });
42895
42895
  }
42896
- selectBusinessContext(contextId) {
42897
- return this.request("POST", "/api/v1/business-context/select", { contextId });
42896
+ listVoiceSources(projectId) {
42897
+ return this.request("GET", `/api/v1/projects/${projectId}/voice-sources`);
42898
42898
  }
42899
- listVoiceSources(businessContextId) {
42900
- return this.request("GET", `/api/v1/business-context/voice-sources?id=${encodeURIComponent(businessContextId)}`);
42899
+ collectVoiceSource(projectId, channel, opts) {
42900
+ return this.request("POST", "/api/v1/business-context/collect-voice-source", { projectId, channel, ...opts });
42901
42901
  }
42902
- collectVoiceSource(businessContextId, channel, opts) {
42903
- return this.request("POST", "/api/v1/business-context/collect-voice-source", { businessContextId, channel, ...opts });
42902
+ removeVoiceSource(projectId, channel, opts = {}) {
42903
+ return this.request("POST", "/api/v1/business-context/remove-voice-source", { projectId, channel, ...opts });
42904
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, correction) {
42909
- return this.request("POST", "/api/v1/business-context/refine-brand-voice", { businessContextId, answers, ...source && { source }, ...correction && { correction } });
42905
+ refineBrandVoice(projectId, source, correction) {
42906
+ return this.request("POST", "/api/v1/business-context/refine-brand-voice", { projectId, ...source && { source }, ...correction && { correction } });
42910
42907
  }
42911
42908
  getProfile() {
42912
42909
  return this.request("GET", "/api/v1/users/profile");
@@ -43450,7 +43447,7 @@ var TodoStatus;
43450
43447
  TodoStatus2["DELETED"] = "DELETED";
43451
43448
  })(TodoStatus ||= {});
43452
43449
  // ../packages/shared-fbe/src/models.ts
43453
- var DEFAULT_MODEL = "anthropic:anthropic/claude-opus-5";
43450
+ var DEFAULT_MODEL = "anthropic:anthropic/claude-opus-5.5";
43454
43451
  var MODEL_ALIASES = {
43455
43452
  claude: DEFAULT_MODEL
43456
43453
  };
@@ -43742,7 +43739,7 @@ var SERVER_TO_FRONTENDS = {
43742
43739
  updated: (projectId) => `${"project:surfaces_updated" /* PROJECT_SURFACES_UPDATED */}:${projectId}`
43743
43740
  },
43744
43741
  killSwitch: {
43745
- changed: (projectId) => `${"project:kill_switch" /* KILL_SWITCH */}:${projectId}`
43742
+ changed: (projectId, userId) => `${"project:kill_switch" /* KILL_SWITCH */}:${projectId}${privateSuffix(userId)}`
43746
43743
  },
43747
43744
  sandbox: {
43748
43745
  state: (userId) => `${"sandbox:state" /* SANDBOX_STATE */}:${userId}`
@@ -43812,6 +43809,17 @@ var SERVER_TO_FRONTENDS = {
43812
43809
  function_call_request: (userId) => `${"FUNCTION_CALL_REQUEST_AGENT" /* FUNCTION_CALL_REQUEST_AGENT */}:frontend:${userId}`
43813
43810
  }
43814
43811
  };
43812
+ // ../packages/shared-fbe/src/REST_types.ts
43813
+ var MANUAL_CHANNEL = "Manual";
43814
+ function manualAnswersOf(sources) {
43815
+ const manual = sources?.find((s) => s.channel === MANUAL_CHANNEL);
43816
+ const out = {};
43817
+ for (const x of manual?.items ?? [])
43818
+ if (typeof x !== "string" && x.prompt)
43819
+ out[x.prompt] = x.text;
43820
+ return out;
43821
+ }
43822
+ var manualSamples = (answers) => Object.entries(answers).filter(([, a]) => a.trim()).map(([prompt, text]) => ({ prompt, text: text.trim() }));
43815
43823
  // ../packages/shared-fbe/src/templateBody.ts
43816
43824
  var USER_HEADINGS = new Set([
43817
43825
  "your task",
@@ -44269,7 +44277,6 @@ Exactly ONE \`api_recommend_template\` call per proposal: it carries both what t
44269
44277
  It runs in-process and needs no machine or shell. Never shell out to \`todoregistry-cli\` or \`todoforai-cli\` for this.
44270
44278
 
44271
44279
  3. Propose only todos you genuinely believe fit this business, highest priority first. There is no quota: a weak card costs the user more than no card, so if you can't find good ones, propose fewer or none and say why. Favour the trivial, high-probability moves for this company, the strategic directions its owner would nod at instantly, alongside any cleverer bets. Then reply with a one-line summary per proposal grouped by workstream, and tell the user they're waiting on the board for review (or, if you proposed none, what you'd need to make a confident recommendation).`,
44272
- mcpConfigs: {},
44273
44280
  edgesMcpConfigs: {},
44274
44281
  permissions: {
44275
44282
  allow: [
@@ -44292,7 +44299,6 @@ var JARVIS_AGENT = {
44292
44299
  ownerId: "SYSTEM",
44293
44300
  systemMessage: "",
44294
44301
  model: "",
44295
- mcpConfigs: {},
44296
44302
  edgesMcpConfigs: {},
44297
44303
  createdAt: 0,
44298
44304
  updatedAt: 0
@@ -44302,7 +44308,6 @@ var ONBOARDING_AGENT = {
44302
44308
  name: "Setup",
44303
44309
  ownerId: "SYSTEM",
44304
44310
  systemMessage: "Workspace setup — this todo is a guided stepper and never runs an agent.",
44305
- mcpConfigs: {},
44306
44311
  edgesMcpConfigs: {},
44307
44312
  createdAt: 0,
44308
44313
  updatedAt: 0
@@ -44354,6 +44359,8 @@ var model_promos_default = {
44354
44359
  { family: "gpt-5-5(-pro)?", rate: 0.8, starts: "2026-09-14", ends: "2026-11-21" },
44355
44360
  { family: "gpt-5-6-sol", rate: 0.85, starts: "2026-08-26", ends: "2026-09-13" },
44356
44361
  { family: "gpt-5-6-sol", rate: 0.8, starts: "2026-09-14", ends: "2026-11-21" },
44362
+ { family: "gpt-6-sol", rate: 0.8, starts: "2026-09-22", ends: "2026-11-21" },
44363
+ { family: "gpt-6-luna", rate: 0.8, starts: "2026-09-22", ends: "2026-11-21" },
44357
44364
  { family: "gpt-6-astra", rate: 0.85, starts: "2026-09-05", ends: "2026-09-13", boostable: false },
44358
44365
  { family: "gpt-6-astra", rate: 0.8, starts: "2026-09-14", ends: "2026-11-21", boostable: false },
44359
44366
  { family: "gpt-5-6-terra", rate: 0.85, starts: "2026-06-29", ends: "2026-09-13" },
@@ -44410,7 +44417,7 @@ var TOPICS = {
44410
44417
  audience: "frontend"
44411
44418
  },
44412
44419
  ["project:kill_switch" /* KILL_SWITCH */]: {
44413
- channel: (p) => SERVER_TO_FRONTENDS.killSwitch.changed(p.projectId),
44420
+ channel: (p) => SERVER_TO_FRONTENDS.killSwitch.changed(p.projectId, p.userId),
44414
44421
  audience: "frontend"
44415
44422
  },
44416
44423
  ["sandbox:state" /* SANDBOX_STATE */]: {
@@ -44549,7 +44556,7 @@ import { parseArgs } from "util";
44549
44556
  // package.json
44550
44557
  var package_default = {
44551
44558
  name: "@todoforai/cli",
44552
- version: "0.1.51",
44559
+ version: "0.1.53",
44553
44560
  type: "module",
44554
44561
  bin: {
44555
44562
  "todoforai-cli": "bin/todoforai-cli.js",
@@ -44607,7 +44614,7 @@ Examples:
44607
44614
  tfa-cli agent list|get|update|create … # Agents (update <agent> model=…)
44608
44615
  tfa-cli todo set <todo-id|-> title=… group=… star=true # Edit a todo's fields
44609
44616
  tfa-cli project list|set|settings|groups|default|agent … # Projects; edit the current one
44610
- tfa-cli brand list|create|select|voice … # Brands (business contexts) + voice learning
44617
+ tfa-cli brand [show|create|rename|voice …] # The project's brand page + voice learning
44611
44618
  tfa-cli device list|rename|wallpaper … # Paired machines
44612
44619
  tfa-cli list [-n 30] [--cursor N] [--all] [--status S] # List todos (paginated)
44613
44620
  tfa-cli status <todo-id> <STATUS> # Update a todo's status
@@ -44724,8 +44731,6 @@ function parseCliArgs() {
44724
44731
  card: { type: "string" },
44725
44732
  link: { type: "boolean", default: false },
44726
44733
  direction: { type: "string" },
44727
- "business-context": { type: "string" },
44728
- brand: { type: "string" },
44729
44734
  url: { type: "string" },
44730
44735
  "pasted-file": { type: "string" },
44731
44736
  "from-company": { type: "boolean", default: false },
@@ -47437,8 +47442,7 @@ Examples:
47437
47442
  tfa-cli agent update <agent> sysmsg="You are a terse video editor."
47438
47443
  tfa-cli agent create name=Reviewer model=claude sysmsg="Review diffs only."
47439
47444
 
47440
- From an agent shell (dst_ token) permissions, mcpConfigs, edgesMcpConfigs,
47441
- devicesConfig and delete are refused by the backend.
47445
+ From an agent shell (dst_ token) permissions, edgesMcpConfigs, devicesConfig and delete are refused by the backend.
47442
47446
  `);
47443
47447
  }
47444
47448
  var FIELD_ALIASES = {
@@ -47839,7 +47843,6 @@ Fields (PUT /todos/{id}):
47839
47843
  star true|false (starredAt now / 0)
47840
47844
  schedule unix ms timestamp, 0 = immediate (alias: scheduledTimestamp)
47841
47845
  agent agentSettingsId (alias: agentSettingsId)
47842
- brand businessContextId (alias: businessContextId)
47843
47846
  public true|false — refused from an agent shell (isPublic)
47844
47847
 
47845
47848
  tfa-cli todo message <todo-id|-> <message-id> <field=value>…
@@ -47861,7 +47864,7 @@ tfa-cli project — edit the current project
47861
47864
 
47862
47865
  Usage:
47863
47866
  tfa-cli project list Projects you can access (* = default)
47864
- tfa-cli project set <field=value>… name=… description=… brand=<businessContextId>
47867
+ tfa-cli project set <field=value>… name=… description=…
47865
47868
  (isPublic refused from an agent shell)
47866
47869
  tfa-cli project default Make it the project you land on at /
47867
47870
  tfa-cli project agent <agent> Default agent for new todos in this project
@@ -47888,10 +47891,8 @@ var TODO_ALIASES = {
47888
47891
  group: "groupTag",
47889
47892
  schedule: "scheduledTimestamp",
47890
47893
  agent: "agentSettingsId",
47891
- brand: "businessContextId",
47892
47894
  public: "isPublic"
47893
47895
  };
47894
- var PROJECT_ALIASES = { brand: "businessContextId" };
47895
47896
  function fail(msg) {
47896
47897
  process.stderr.write(`${RED}${msg}${RESET}
47897
47898
  `);
@@ -47967,7 +47968,7 @@ async function projectCommand(api, positionals, args) {
47967
47968
  if (sub === "set") {
47968
47969
  if (!rest.length)
47969
47970
  fail("Usage: tfa-cli project set <field=value>…");
47970
- const updates = parseAssignments2(rest, PROJECT_ALIASES);
47971
+ const updates = parseAssignments2(rest);
47971
47972
  await api.updateProject(projectId, updates);
47972
47973
  process.stderr.write(`${GREEN}✅ project updated: ${Object.keys(updates).join(", ")}${RESET}
47973
47974
  `);
@@ -48074,18 +48075,17 @@ async function projectCommand(api, positionals, args) {
48074
48075
  }
48075
48076
  function printBrandHelp() {
48076
48077
  process.stderr.write(`
48077
- tfa-cli brand — business contexts (brands) and the learned writing voice
48078
+ tfa-cli brand — the project's brand page and its learned writing voice
48078
48079
 
48080
+ The project IS the brand. --project <id> or $TODOFORAI_PROJECT_ID selects it.
48079
48081
  The brand page text itself is a file: read/write todoforai:business-context.
48080
- These commands manage the contexts and the voice learned for them.
48081
48082
 
48082
48083
  Usage:
48083
- tfa-cli brand list Contexts (* = selected)
48084
- tfa-cli brand create <name> New brand (empty .md page)
48085
- tfa-cli brand rename <brand> <name>
48086
- tfa-cli brand select <brand|none> Active brand for the account
48084
+ tfa-cli brand [show] This project's brand (none = personal board)
48085
+ tfa-cli brand create <name> Give the project a brand page (empty .md); the board turns business
48086
+ tfa-cli brand rename <name>
48087
48087
  tfa-cli brand voice Learned profile + sources
48088
- tfa-cli brand voice answers [<q>=<a>…] Show / set the brand-voice answers (strings)
48088
+ tfa-cli brand voice answers [<q>=<a>…] Show / set the Manual source: answers to the style questions
48089
48089
  tfa-cli brand voice collect <channel> [--url U | --pasted-file <F|-> | --max N]
48090
48090
  Add a writing sample source.
48091
48091
  On this device (signed-in CLI, reply pairs):
@@ -48099,9 +48099,7 @@ Usage:
48099
48099
  tfa-cli brand voice correct "<what is off>" Tell the learner what it got wrong; profile is updated
48100
48100
  in one pass and the correction is kept for every re-learn
48101
48101
 
48102
- <brand> is an id or name (unique partial works). Voice subcommands use the
48103
- selected brand unless --brand <id|name> is given (--pasted-file - reads stdin).
48104
- Delete / reset are UI-only.
48102
+ --pasted-file - reads stdin. Delete is UI-only.
48105
48103
 
48106
48104
  Questions the UI asks (keys for 'answers'):
48107
48105
  "What makes you different from competitors?"
@@ -48109,43 +48107,30 @@ Questions the UI asks (keys for 'answers'):
48109
48107
  "A frustrated customer says things aren't working. How do you reply?"
48110
48108
  `);
48111
48109
  }
48112
- async function resolveBrand(api, query, selectedId) {
48113
- const contexts = await api.listBusinessContexts();
48114
- if (!query) {
48115
- const sel = contexts.find((c3) => c3.id === selectedId);
48116
- if (sel)
48117
- return sel;
48118
- if (contexts.length === 1)
48119
- return contexts[0];
48120
- fail("No brand selected — pass --brand <id|name> or 'tfa-cli brand select'");
48121
- }
48122
- const q = query.toLowerCase();
48123
- const exact = contexts.find((c3) => c3.id === query || c3.name.toLowerCase() === q);
48124
- if (exact)
48125
- return exact;
48126
- const partial = contexts.filter((c3) => c3.id.startsWith(query) || c3.name.toLowerCase().includes(q));
48127
- if (partial.length === 1)
48128
- return partial[0];
48129
- fail(partial.length ? `Ambiguous brand '${query}'` : `No brand matching '${query}'`);
48110
+ function requireProject(args) {
48111
+ const projectId = args.project || getEnv2("PROJECT_ID");
48112
+ if (!projectId)
48113
+ fail("No project — pass --project <id> or set TODOFORAI_PROJECT_ID");
48114
+ return projectId;
48130
48115
  }
48131
48116
  async function brandCommand(api, positionals, args) {
48132
48117
  const [, sub, ...rest] = positionals;
48133
- if (!sub || sub === "list") {
48134
- const contexts = await api.listBusinessContexts();
48135
- const selected = (await api.getProfile()).user?.selectedBusinessContextId;
48118
+ const projectId = requireProject(args);
48119
+ if (!sub || sub === "show") {
48120
+ const brand = await api.getBusinessContext(projectId);
48136
48121
  if (args.json) {
48137
- console.log(JSON.stringify(contexts.map((c3) => ({ ...c3, selected: c3.id === selected })), null, 2));
48122
+ console.log(JSON.stringify(brand, null, 2));
48138
48123
  return;
48139
48124
  }
48140
- for (const c3 of contexts)
48141
- process.stderr.write(`${c3.id === selected ? "*" : " "} ${c3.name} ${DIM}${c3.id}${RESET}
48125
+ process.stderr.write(brand ? `${brand.name} ${DIM}${brand.id}${RESET}
48126
+ ` : `${DIM}(personal board — no brand)${RESET}
48142
48127
  `);
48143
48128
  return;
48144
48129
  }
48145
48130
  if (sub === "create") {
48146
48131
  if (!rest[0])
48147
48132
  fail("Usage: tfa-cli brand create <name>");
48148
- const c3 = await api.createBusinessContext(rest[0]);
48133
+ const c3 = await api.createBusinessContext(projectId, rest[0]);
48149
48134
  if (args.json) {
48150
48135
  console.log(JSON.stringify(c3, null, 2));
48151
48136
  return;
@@ -48155,25 +48140,15 @@ async function brandCommand(api, positionals, args) {
48155
48140
  return;
48156
48141
  }
48157
48142
  if (sub === "rename") {
48158
- if (!rest[1])
48159
- fail("Usage: tfa-cli brand rename <brand> <name>");
48160
- const c3 = await resolveBrand(api, rest[0]);
48161
- await api.renameBusinessContext(c3.id, rest[1]);
48162
- process.stderr.write(`${GREEN}✅ renamed to ${rest[1]}${RESET}
48163
- `);
48164
- return;
48165
- }
48166
- if (sub === "select") {
48167
48143
  if (!rest[0])
48168
- fail("Usage: tfa-cli brand select <brand|none>");
48169
- const id = rest[0] === "none" ? null : (await resolveBrand(api, rest[0])).id;
48170
- await api.selectBusinessContext(id);
48171
- process.stderr.write(`${GREEN}✅ selected brand: ${id ?? "none"}${RESET}
48144
+ fail("Usage: tfa-cli brand rename <name>");
48145
+ await api.renameBusinessContext(projectId, rest[0]);
48146
+ process.stderr.write(`${GREEN}✅ renamed to ${rest[0]}${RESET}
48172
48147
  `);
48173
48148
  return;
48174
48149
  }
48175
48150
  if (sub === "voice")
48176
- return voiceCommand(api, rest, args);
48151
+ return voiceCommand(api, projectId, rest, args);
48177
48152
  fail(`Unknown 'brand' subcommand: ${sub}`);
48178
48153
  }
48179
48154
  function dryCollect(channel, max) {
@@ -48227,26 +48202,24 @@ async function voiceDeviceCommand(rest, args) {
48227
48202
  }
48228
48203
  return false;
48229
48204
  }
48230
- async function voiceCommand(api, rest, args) {
48205
+ async function voiceCommand(api, projectId, rest, args) {
48231
48206
  const [verb, ...vargs] = rest;
48232
48207
  if (verb === "collect" && !vargs[0])
48233
- fail("Usage: tfa-cli brand voice collect <channel> [--url U | --pasted-file F|- | --max N] [--dry-run]");
48234
- const onboarding = await api.getOnboarding();
48208
+ fail("Usage: tfa-cli brand voice collect <channel> [--url U | --pasted-file F|- | --account <page-id> | --max N] [--dry-run]");
48235
48209
  if (verb === "answers") {
48210
+ const answers = manualAnswersOf((await api.listVoiceSources(projectId)).sources);
48236
48211
  if (vargs.length) {
48237
- const given = Object.fromEntries(vargs.map((a) => {
48212
+ for (const a of vargs) {
48238
48213
  const i = a.indexOf("=");
48239
48214
  if (i < 1)
48240
48215
  fail(`Expected question=answer, got '${a}'`);
48241
- return [a.slice(0, i), a.slice(i + 1)];
48242
- }));
48243
- const styleAnswers = { ...onboarding.styleAnswers ?? {}, ...given };
48244
- await api.patchOnboarding({ styleAnswers });
48216
+ answers[a.slice(0, i)] = a.slice(i + 1);
48217
+ }
48218
+ await api.collectVoiceSource(projectId, MANUAL_CHANNEL, { samples: manualSamples(answers) });
48245
48219
  process.stderr.write(`${GREEN}✅ ${vargs.length} answer(s) saved${RESET}
48246
48220
  `);
48247
48221
  return;
48248
48222
  }
48249
- const answers = onboarding.styleAnswers ?? {};
48250
48223
  if (args.json) {
48251
48224
  console.log(JSON.stringify(answers, null, 2));
48252
48225
  return;
@@ -48257,15 +48230,14 @@ async function voiceCommand(api, rest, args) {
48257
48230
  `);
48258
48231
  return;
48259
48232
  }
48260
- const brand = await resolveBrand(api, args.brand, (await api.getProfile()).user?.selectedBusinessContextId);
48261
48233
  if (!verb || verb === "show") {
48262
- const profile = onboarding.voiceProfiles?.[brand.id];
48263
- const { sources } = await api.listVoiceSources(brand.id);
48234
+ const [{ sources, profile }, brand] = await Promise.all([api.listVoiceSources(projectId), api.getBusinessContext(projectId)]);
48264
48235
  if (args.json) {
48265
48236
  console.log(JSON.stringify({ brand, profile, sources }, null, 2));
48266
48237
  return;
48267
48238
  }
48268
- process.stderr.write(`${brand.name} ${DIM}${brand.id}${RESET}
48239
+ if (brand)
48240
+ process.stderr.write(`${brand.name} ${DIM}${brand.id}${RESET}
48269
48241
  `);
48270
48242
  process.stderr.write(profile ? `
48271
48243
  ${profile.profile}
@@ -48273,7 +48245,7 @@ ${DIM}match ${profile.match}/100 · source ${profile.source}${RESET}
48273
48245
  ` : `${DIM}(no voice learned yet)${RESET}
48274
48246
  `);
48275
48247
  for (const s of sources)
48276
- process.stderr.write(` ${s.channel} ${DIM}${s.posts ?? "?"} posts · ${s.chars ?? "?"} chars${RESET}
48248
+ process.stderr.write(` ${s.channel}${s.account ? ` ${s.label} ${DIM}(${s.account})${RESET}` : ""} ${DIM}${s.posts ?? "?"} posts · ${s.chars ?? "?"} chars${RESET}
48277
48249
  `);
48278
48250
  return;
48279
48251
  }
@@ -48294,35 +48266,30 @@ ${DIM}match ${profile.match}/100 · source ${profile.source}${RESET}
48294
48266
  const { samples, note } = collectChannel(channel, Number(args.max ?? 40));
48295
48267
  if (!samples.length)
48296
48268
  fail(`${channel}: nothing usable came back${note ? ` (${note})` : ""}`);
48297
- const { sources: sources2 } = await api.collectVoiceSource(brand.id, channel, { samples });
48269
+ const { sources: sources2 } = await api.collectVoiceSource(projectId, channel, { samples });
48298
48270
  process.stderr.write(`${GREEN}✅ ${channel}: ${samples.length} reply pairs stored (${sources2.length} source(s))${RESET}
48299
48271
  `);
48300
48272
  return;
48301
48273
  }
48302
- const { sources } = await api.collectVoiceSource(brand.id, channel, { url: args.url, pasted });
48274
+ const { sources } = await api.collectVoiceSource(projectId, channel, { url: args.url, pasted, account: args.account });
48303
48275
  process.stderr.write(`${GREEN}✅ ${channel} collected (${sources.length} source(s))${RESET}
48304
48276
  `);
48305
48277
  return;
48306
48278
  }
48307
48279
  if (verb === "remove") {
48308
48280
  if (!vargs[0])
48309
- fail("Usage: tfa-cli brand voice remove <channel>");
48310
- await api.removeVoiceSource(brand.id, vargs[0]);
48281
+ fail("Usage: tfa-cli brand voice remove <channel> [--account <id>] (no --account: every source of the channel)");
48282
+ await api.removeVoiceSource(projectId, vargs[0], args.account ? { account: args.account } : { all: true });
48311
48283
  process.stderr.write(`${GREEN}✅ ${vargs[0]} removed${RESET}
48312
48284
  `);
48313
48285
  return;
48314
48286
  }
48315
48287
  if (verb === "learn") {
48316
- process.stderr.write(`${DIM}learning voice for ${brand.name}…${RESET}
48288
+ process.stderr.write(`${DIM}learning voice…${RESET}
48317
48289
  `);
48318
- const res = await api.refineBrandVoice(brand.id, onboarding.styleAnswers ?? {}, args["from-company"] ? "company" : undefined);
48290
+ const res = await api.refineBrandVoice(projectId, args["from-company"] ? "company" : undefined);
48319
48291
  if (!res.profile)
48320
- fail("Nothing to learn from — add answers or collect a source first");
48321
- const { [brand.id]: _stale, ...styleAiAnswers } = onboarding.styleAiAnswers ?? {};
48322
- await api.patchOnboarding({
48323
- voiceProfiles: { ...onboarding.voiceProfiles ?? {}, [brand.id]: { ...res, updatedAt: Date.now() } },
48324
- styleAiAnswers
48325
- });
48292
+ fail(args["from-company"] ? "No brand page to learn from — 'tfa-cli brand create <name>' and fill it first" : "Nothing to learn from — add answers, collect a source, or fill the brand page first");
48326
48293
  if (args.json) {
48327
48294
  console.log(JSON.stringify(res, null, 2));
48328
48295
  return;
@@ -48336,16 +48303,13 @@ ${res.profile}
48336
48303
  const text = vargs.join(" ").trim();
48337
48304
  if (!text)
48338
48305
  fail('Usage: tfa-cli brand voice correct "<what is off>"');
48339
- const stored = onboarding.voiceProfiles?.[brand.id];
48340
- if (!stored?.profile)
48306
+ if (!(await api.listVoiceSources(projectId)).profile?.profile)
48341
48307
  fail("No voice learned yet — 'tfa-cli brand voice learn' first");
48342
48308
  process.stderr.write(`${DIM}applying correction…${RESET}
48343
48309
  `);
48344
- const res = await api.refineBrandVoice(brand.id, onboarding.styleAnswers ?? {}, undefined, text);
48310
+ const res = await api.refineBrandVoice(projectId, undefined, text);
48345
48311
  if (!res.profile)
48346
48312
  fail("The correction pass returned nothing — try rewording it");
48347
- const { [brand.id]: _stale, ...styleAiAnswers } = onboarding.styleAiAnswers ?? {};
48348
- await api.patchOnboarding({ voiceProfiles: { ...onboarding.voiceProfiles ?? {}, [brand.id]: { ...res, updatedAt: Date.now() } }, styleAiAnswers });
48349
48313
  if (args.json) {
48350
48314
  console.log(JSON.stringify(res, null, 2));
48351
48315
  return;
@@ -73382,7 +73346,6 @@ Cancelled by user (Ctrl+C)
73382
73346
  ...args.title ? { title: args.title } : {},
73383
73347
  ...args.note ? { note: args.note } : {},
73384
73348
  ...priority ? { priority } : {},
73385
- ...args["business-context"] ? { businessContextId: args["business-context"] } : {},
73386
73349
  ...args.group ? { group: args.group } : {},
73387
73350
  ...args["group-name"] ? { groupName: args["group-name"] } : {},
73388
73351
  ...args["group-description"] ? { groupDescription: args["group-description"] } : {}
@@ -73451,7 +73414,7 @@ Cancelled by user (Ctrl+C)
73451
73414
  const res = await fetch(`${apiUrl}${restBasePath(apiKey)}/projects/${projectId2}/recommendations/generate`, {
73452
73415
  method: "POST",
73453
73416
  headers: { "content-type": "application/json", "x-api-key": apiKey },
73454
- body: JSON.stringify({ projectId: projectId2, ...args["business-context"] ? { businessContextId: args["business-context"] } : {}, ...direction ? { direction } : {} }),
73417
+ body: JSON.stringify({ projectId: projectId2, ...direction ? { direction } : {} }),
73455
73418
  signal: AbortSignal.timeout(30000)
73456
73419
  });
73457
73420
  const text = await res.text();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@todoforai/cli",
3
- "version": "0.1.51",
3
+ "version": "0.1.53",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "todoforai-cli": "bin/todoforai-cli.js",