@todoforai/cli 0.1.49 → 0.1.50

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 +313 -31
  2. package/package.json +1 -1
@@ -42905,8 +42905,8 @@ class ApiClient {
42905
42905
  removeVoiceSource(businessContextId, channel) {
42906
42906
  return this.request("POST", "/api/v1/business-context/remove-voice-source", { businessContextId, channel });
42907
42907
  }
42908
- refineBrandVoice(businessContextId, answers, source) {
42909
- return this.request("POST", "/api/v1/business-context/refine-brand-voice", { businessContextId, answers, ...source && { source } });
42908
+ refineBrandVoice(businessContextId, answers, source, correction) {
42909
+ return this.request("POST", "/api/v1/business-context/refine-brand-voice", { businessContextId, answers, ...source && { source }, ...correction && { correction } });
42910
42910
  }
42911
42911
  getProfile() {
42912
42912
  return this.request("GET", "/api/v1/users/profile");
@@ -43729,6 +43729,9 @@ var SERVER_TO_FRONTENDS = {
43729
43729
  hexGrid: {
43730
43730
  updated: (projectId) => `${"hexgrid:updated" /* HEXGRID_UPDATED */}:${projectId}`
43731
43731
  },
43732
+ killSwitch: {
43733
+ changed: (projectId) => `${"project:kill_switch" /* KILL_SWITCH */}:${projectId}`
43734
+ },
43732
43735
  sandbox: {
43733
43736
  state: (userId) => `${"sandbox:state" /* SANDBOX_STATE */}:${userId}`
43734
43737
  },
@@ -44390,6 +44393,10 @@ var TOPICS = {
44390
44393
  channel: (p) => SERVER_TO_FRONTENDS.hexGrid.updated(p.projectId),
44391
44394
  audience: "frontend"
44392
44395
  },
44396
+ ["project:kill_switch" /* KILL_SWITCH */]: {
44397
+ channel: (p) => SERVER_TO_FRONTENDS.killSwitch.changed(p.projectId),
44398
+ audience: "frontend"
44399
+ },
44393
44400
  ["sandbox:state" /* SANDBOX_STATE */]: {
44394
44401
  channel: (p) => SERVER_TO_FRONTENDS.sandbox.state(p.userId),
44395
44402
  audience: "frontend"
@@ -44526,7 +44533,7 @@ import { parseArgs } from "util";
44526
44533
  // package.json
44527
44534
  var package_default = {
44528
44535
  name: "@todoforai/cli",
44529
- version: "0.1.49",
44536
+ version: "0.1.50",
44530
44537
  type: "module",
44531
44538
  bin: {
44532
44539
  "todoforai-cli": "bin/todoforai-cli.js",
@@ -44706,6 +44713,8 @@ function parseCliArgs() {
44706
44713
  url: { type: "string" },
44707
44714
  "pasted-file": { type: "string" },
44708
44715
  "from-company": { type: "boolean", default: false },
44716
+ "dry-run": { type: "boolean", default: false },
44717
+ max: { type: "string" },
44709
44718
  seed: { type: "string" },
44710
44719
  emails: { type: "string" },
44711
44720
  ttl: { type: "string" },
@@ -47625,6 +47634,180 @@ var STATUS_HELP2 = {
47625
47634
  ["DELETED" /* DELETED */]: "Marked for deletion"
47626
47635
  };
47627
47636
 
47637
+ // src/voice-collect.ts
47638
+ import { execFileSync } from "node:child_process";
47639
+ var run = (bin, args) => execFileSync(bin, args, { encoding: "utf8", maxBuffer: 256 << 20, timeout: 60000, stdio: ["ignore", "pipe", "ignore"] });
47640
+ var installed = (bin) => {
47641
+ try {
47642
+ run("which", [bin]);
47643
+ return true;
47644
+ } catch {
47645
+ return false;
47646
+ }
47647
+ };
47648
+ var MIN_CHARS = 30;
47649
+ var SEP = /^─{20,}$/m;
47650
+ function parseThread(out) {
47651
+ return out.split(SEP).slice(1).map((chunk) => {
47652
+ const lines = chunk.trim().split(`
47653
+ `);
47654
+ let i = 0;
47655
+ const h = {};
47656
+ for (;i < lines.length && /^\s*(From|To|Date|Auth|Cc):/.test(lines[i]); i++) {
47657
+ const [k, ...v] = lines[i].trim().split(":");
47658
+ h[k] = v.join(":").trim();
47659
+ }
47660
+ return { from: h.From ?? "", date: h.Date ?? "", body: lines.slice(i).join(`
47661
+ `).trim() };
47662
+ }).filter((m) => m.from);
47663
+ }
47664
+ function stripReply(body) {
47665
+ const lines = body.split(`
47666
+ `);
47667
+ const cut = lines.findIndex((l, i) => /^>/.test(l) || /^(On .{6,}wrote:|.{3,} ezt írta \(időpont:|Le .{6,} a écrit :|Am .{6,} schrieb .*:|-----Original Message-----|From: .*|________________________________)$/i.test(l.trim()) || /^(On .{6,}|.{3,} ezt írta)/.test(l.trim()) && i + 1 < lines.length && /^>|wrote:$|\):$/.test(lines[i + 1].trim()));
47668
+ let kept = (cut >= 0 ? lines.slice(0, cut) : lines).filter((l) => !/^Attachments: /.test(l)).join(`
47669
+ `).trim();
47670
+ kept = kept.replace(/\n--\s*\n[\s\S]*$/, "").trim();
47671
+ return kept;
47672
+ }
47673
+ var gmail = {
47674
+ tool: "zele",
47675
+ check: () => {
47676
+ if (!installed("zele"))
47677
+ return "zele is not installed";
47678
+ const who = (() => {
47679
+ try {
47680
+ return run("zele", ["whoami"]);
47681
+ } catch {
47682
+ return "";
47683
+ }
47684
+ })();
47685
+ return /email:/.test(who) ? null : "zele is not signed in (run: zele login --method google)";
47686
+ },
47687
+ collect: (max) => {
47688
+ const accounts = [...run("zele", ["whoami"]).matchAll(/email: (\S+)/g)].map((m) => m[1]);
47689
+ const isMe = (from) => accounts.some((a) => from.toLowerCase().includes(a.toLowerCase()));
47690
+ const samples = [];
47691
+ for (const account of accounts) {
47692
+ const list = run("zele", ["mail", "search", "from:me", "--limit", String(max), "--account", account]);
47693
+ const ids = [...list.matchAll(/^ - id: ([0-9a-f]+)\n(?:.*\n)*? messages: (\d+)/gm)].filter((m) => Number(m[2]) > 1).map((m) => m[1]);
47694
+ for (const id of ids) {
47695
+ if (samples.length >= max)
47696
+ break;
47697
+ const msgs = parseThread(run("zele", ["mail", "read", id, "--account", account]));
47698
+ for (let i = 1;i < msgs.length && samples.length < max; i++) {
47699
+ if (!isMe(msgs[i].from) || isMe(msgs[i - 1].from))
47700
+ continue;
47701
+ const text = stripReply(msgs[i].body), prompt = stripReply(msgs[i - 1].body);
47702
+ if (text.length < MIN_CHARS || prompt.length < 20)
47703
+ continue;
47704
+ samples.push({ at: msgs[i].date, text, prompt, url: `gmail:${account}/${id}#${i}` });
47705
+ }
47706
+ }
47707
+ }
47708
+ return { samples, note: `${accounts.length} account(s)` };
47709
+ }
47710
+ };
47711
+ var outlook = {
47712
+ tool: "outlook-api",
47713
+ check: () => {
47714
+ if (!installed("outlook-api"))
47715
+ return "outlook-api is not installed";
47716
+ try {
47717
+ const me = JSON.parse(run("outlook-api", ["whoami"]));
47718
+ return me?.mail || me?.userPrincipalName ? null : "outlook-api is not signed in";
47719
+ } catch {
47720
+ return "outlook-api is not signed in (run: outlook-api auth)";
47721
+ }
47722
+ },
47723
+ collect: (max) => {
47724
+ const me = JSON.parse(run("outlook-api", ["whoami"]));
47725
+ const mine = [me.mail, me.userPrincipalName].filter(Boolean).map((a) => a.toLowerCase());
47726
+ const isMe = (m) => mine.includes((m.from?.emailAddress?.address ?? "").toLowerCase());
47727
+ const sent = JSON.parse(run("outlook-api", ["list", "-f", "sentitems", "-n", String(max), "--select", "id,conversationId,from,sentDateTime,webLink"]));
47728
+ const sentRows = Array.isArray(sent) ? sent : sent.value ?? [];
47729
+ const samples = [];
47730
+ const seenConv = new Set;
47731
+ for (const s of sentRows) {
47732
+ if (!s.conversationId || seenConv.has(s.conversationId))
47733
+ continue;
47734
+ seenConv.add(s.conversationId);
47735
+ const conv = JSON.parse(run("outlook-api", ["get", `/me/messages?$filter=conversationId eq '${s.conversationId.replace(/'/g, "''")}'&$top=50&$select=id,from,receivedDateTime,sentDateTime,webLink`]));
47736
+ const msgs = (conv.value ?? []).sort((a, b) => (a.receivedDateTime ?? "").localeCompare(b.receivedDateTime ?? ""));
47737
+ const body = (id) => stripReply(JSON.parse(run("outlook-api", ["read", id, "--text"])).body?.content ?? "");
47738
+ for (let i = 1;i < msgs.length && samples.length < max; i++) {
47739
+ if (!isMe(msgs[i]) || isMe(msgs[i - 1]))
47740
+ continue;
47741
+ const text = body(msgs[i].id), prompt = body(msgs[i - 1].id);
47742
+ if (text.length < MIN_CHARS || prompt.length < 20)
47743
+ continue;
47744
+ samples.push({ at: msgs[i].sentDateTime ?? msgs[i].receivedDateTime, text, prompt, url: msgs[i].webLink ?? `outlook:${msgs[i].id}` });
47745
+ }
47746
+ if (samples.length >= max)
47747
+ break;
47748
+ }
47749
+ return { samples };
47750
+ }
47751
+ };
47752
+ var QUERIES = "the we you ok this that fix why do how make add run test build post code file error need want should could now more less good bad new old first last".split(" ");
47753
+ var chat = {
47754
+ tool: "tfa-memory",
47755
+ check: () => installed("tfa-memory") ? null : "tfa-memory is not installed",
47756
+ collect: (max) => {
47757
+ const mem = (...a) => run("tfa-memory", a);
47758
+ const anchors = new Map;
47759
+ for (const q of QUERIES) {
47760
+ for (const line of mem("search", q, "--source", "todo", "--mode", "lexical", "--limit", "50").split(`
47761
+ `)) {
47762
+ const m = line.match(/todo\/([0-9a-f-]{36}):([0-9a-f-]{36})/);
47763
+ if (m && !anchors.has(m[1]))
47764
+ anchors.set(m[1], `todo/${m[1]}:${m[2]}`);
47765
+ }
47766
+ if (anchors.size >= max)
47767
+ break;
47768
+ }
47769
+ const samples = [];
47770
+ for (const anchor of [...anchors.values()].slice(0, max)) {
47771
+ let msgs;
47772
+ try {
47773
+ msgs = JSON.parse(mem("around", anchor, "--before", "200", "--after", "200", "--json"));
47774
+ } catch {
47775
+ continue;
47776
+ }
47777
+ msgs.sort((a, b) => a.createdAt - b.createdAt);
47778
+ for (let i = 1;i < msgs.length; i++) {
47779
+ if (msgs[i].meta?.role !== "user" || msgs[i - 1].meta?.role === "user")
47780
+ continue;
47781
+ const text = msgs[i].content.trim(), prompt = msgs[i - 1].content.trim();
47782
+ if (text.length < MIN_CHARS || prompt.length < 40)
47783
+ continue;
47784
+ if (/^\s*(#|\/|@|\{)/.test(text))
47785
+ continue;
47786
+ if (text.split(`
47787
+ `).length > 6)
47788
+ continue;
47789
+ if (/^Wait w_|\nWait w_[0-9a-f]+ '|^Connected \w+ \(card\)|^\[/m.test(text))
47790
+ continue;
47791
+ samples.push({ at: new Date(msgs[i].createdAt).toISOString(), text, prompt: prompt.slice(0, 4000), url: msgs[i].address });
47792
+ }
47793
+ }
47794
+ return { samples: samples.slice(0, max), note: `${anchors.size} todos` };
47795
+ }
47796
+ };
47797
+ var ADAPTERS = { Gmail: gmail, Outlook: outlook, Chat: chat };
47798
+ var DEVICE_CHANNELS = Object.keys(ADAPTERS);
47799
+ function checkChannel(channel) {
47800
+ const a = ADAPTERS[channel];
47801
+ return a ? a.check() : `${channel} is not read on the device (server-side: pass --url, or paste)`;
47802
+ }
47803
+ function collectChannel(channel, max = 40) {
47804
+ const a = ADAPTERS[channel];
47805
+ if (!a)
47806
+ throw new Error(`No device adapter for ${channel}`);
47807
+ const r = a.collect(Math.min(200, Math.max(1, Math.floor(Number(max) || 40))));
47808
+ return { ...r, samples: r.samples.filter((x) => x.text.trim().length >= MIN_CHARS) };
47809
+ }
47810
+
47628
47811
  // src/manage-command.ts
47629
47812
  function printTodoHelp() {
47630
47813
  process.stderr.write(`
@@ -47843,12 +48026,18 @@ Usage:
47843
48026
  tfa-cli brand select <brand|none> Active brand for the account
47844
48027
  tfa-cli brand voice Learned profile + sources
47845
48028
  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
48029
+ tfa-cli brand voice collect <channel> [--url U | --pasted-file <F|-> | --max N]
48030
+ Add a writing sample source.
48031
+ On this device (signed-in CLI, reply pairs):
48032
+ Gmail (zele) Outlook (outlook-api) Chat (tfa-memory)
48033
+ Server-side (--url or paste):
48034
+ X LinkedIn Facebook Instagram Slack Teams
48035
+ --dry-run print the samples as JSONL, store nothing
48036
+ tfa-cli brand voice check [<channel>|--all] Can this device read the channel? tool, login, sample count
47850
48037
  tfa-cli brand voice remove <channel>
47851
48038
  tfa-cli brand voice learn [--from-company] Run the refinement loop, store the profile
48039
+ tfa-cli brand voice correct "<what is off>" Tell the learner what it got wrong; profile is updated
48040
+ in one pass and the correction is kept for every re-learn
47852
48041
 
47853
48042
  <brand> is an id or name (unique partial works). Voice subcommands use the
47854
48043
  selected brand unless --brand <id|name> is given (--pasted-file - reads stdin).
@@ -47927,8 +48116,61 @@ async function brandCommand(api, positionals, args) {
47927
48116
  return voiceCommand(api, rest, args);
47928
48117
  fail(`Unknown 'brand' subcommand: ${sub}`);
47929
48118
  }
48119
+ function dryCollect(channel, max) {
48120
+ const reason = checkChannel(channel);
48121
+ if (reason)
48122
+ fail(`${channel}: ${reason}`);
48123
+ const { samples, note } = collectChannel(channel, max);
48124
+ for (const x of samples)
48125
+ console.log(JSON.stringify(x));
48126
+ process.stderr.write(`${DIM}${samples.length} samples${note ? ` · ${note}` : ""} (not stored)${RESET}
48127
+ `);
48128
+ }
48129
+ async function voiceDeviceCommand(rest, args) {
48130
+ const [verb, ...vargs] = rest;
48131
+ if (verb === "check") {
48132
+ const channels3 = args.all || !vargs[0] ? DEVICE_CHANNELS : [vargs[0]];
48133
+ let failed = 0;
48134
+ const report = {};
48135
+ for (const ch of channels3) {
48136
+ const reason = checkChannel(ch);
48137
+ if (reason) {
48138
+ failed++;
48139
+ report[ch] = { ok: false, reason };
48140
+ continue;
48141
+ }
48142
+ try {
48143
+ const r = collectChannel(ch, Number(args.max ?? 10));
48144
+ const bad = r.samples.filter((x) => !x.text.trim() || ADAPTERS[ch] && !x.prompt).length;
48145
+ const ok = r.samples.length > 0 && bad === 0;
48146
+ if (!ok)
48147
+ failed++;
48148
+ report[ch] = { ok, samples: r.samples.length, ...r.note && { note: r.note }, ...bad ? { reason: `${bad} sample(s) without a prompt` } : r.samples.length ? {} : { reason: "no samples came back" } };
48149
+ } catch (e) {
48150
+ failed++;
48151
+ report[ch] = { ok: false, reason: e.message };
48152
+ }
48153
+ }
48154
+ if (args.json)
48155
+ console.log(JSON.stringify(report, null, 2));
48156
+ else
48157
+ for (const [ch, r] of Object.entries(report))
48158
+ process.stderr.write(`${r.ok ? GREEN + "✅" : RED + "❌"} ${ch}${RESET} ${DIM}${r.ok ? `${r.samples} samples${r.note ? ` · ${r.note}` : ""}` : r.reason}${RESET}
48159
+ `);
48160
+ process.exit(failed ? 1 : 0);
48161
+ }
48162
+ if (verb === "collect" && args["dry-run"]) {
48163
+ if (!vargs[0])
48164
+ fail("Usage: tfa-cli brand voice collect <channel> --dry-run [--max N]");
48165
+ dryCollect(vargs[0], Number(args.max ?? 40));
48166
+ return true;
48167
+ }
48168
+ return false;
48169
+ }
47930
48170
  async function voiceCommand(api, rest, args) {
47931
48171
  const [verb, ...vargs] = rest;
48172
+ if (verb === "collect" && !vargs[0])
48173
+ fail("Usage: tfa-cli brand voice collect <channel> [--url U | --pasted-file F|- | --max N] [--dry-run]");
47932
48174
  const onboarding = await api.getOnboarding();
47933
48175
  if (verb === "answers") {
47934
48176
  if (vargs.length) {
@@ -47977,13 +48219,26 @@ ${DIM}match ${profile.match}/100 · source ${profile.source}${RESET}
47977
48219
  }
47978
48220
  if (verb === "collect") {
47979
48221
  const channel = vargs[0];
47980
- if (!channel)
47981
- fail("Usage: tfa-cli brand voice collect <channel> [--url U | --pasted-file F|-]");
47982
48222
  let pasted;
47983
48223
  if (args["pasted-file"]) {
47984
48224
  const { readFileSync: readFileSync3 } = await import("node:fs");
47985
48225
  pasted = readFileSync3(args["pasted-file"] === "-" ? 0 : args["pasted-file"], "utf8");
47986
48226
  }
48227
+ const onDevice = !pasted && !args.url && ADAPTERS[channel];
48228
+ if (onDevice) {
48229
+ const reason = checkChannel(channel);
48230
+ if (reason)
48231
+ fail(`${channel}: ${reason}`);
48232
+ process.stderr.write(`${DIM}reading ${channel} on this device…${RESET}
48233
+ `);
48234
+ const { samples, note } = collectChannel(channel, Number(args.max ?? 40));
48235
+ if (!samples.length)
48236
+ fail(`${channel}: nothing usable came back${note ? ` (${note})` : ""}`);
48237
+ const { sources: sources2 } = await api.collectVoiceSource(brand.id, channel, { samples });
48238
+ process.stderr.write(`${GREEN}✅ ${channel}: ${samples.length} reply pairs stored (${sources2.length} source(s))${RESET}
48239
+ `);
48240
+ return;
48241
+ }
47987
48242
  const { sources } = await api.collectVoiceSource(brand.id, channel, { url: args.url, pasted });
47988
48243
  process.stderr.write(`${GREEN}✅ ${channel} collected (${sources.length} source(s))${RESET}
47989
48244
  `);
@@ -48014,6 +48269,31 @@ ${DIM}match ${profile.match}/100 · source ${profile.source}${RESET}
48014
48269
  }
48015
48270
  process.stderr.write(`${GREEN}✅ voice learned (match ${res.match}/100, source ${res.source})${RESET}
48016
48271
  ${res.profile}
48272
+ `);
48273
+ return;
48274
+ }
48275
+ if (verb === "correct") {
48276
+ const text = vargs.join(" ").trim();
48277
+ if (!text)
48278
+ fail('Usage: tfa-cli brand voice correct "<what is off>"');
48279
+ const stored = onboarding.voiceProfiles?.[brand.id];
48280
+ if (!stored?.profile)
48281
+ fail("No voice learned yet — 'tfa-cli brand voice learn' first");
48282
+ process.stderr.write(`${DIM}applying correction…${RESET}
48283
+ `);
48284
+ const res = await api.refineBrandVoice(brand.id, onboarding.styleAnswers ?? {}, undefined, text);
48285
+ if (!res.profile)
48286
+ fail("The correction pass returned nothing — try rewording it");
48287
+ const { [brand.id]: _stale, ...styleAiAnswers } = onboarding.styleAiAnswers ?? {};
48288
+ await api.patchOnboarding({ voiceProfiles: { ...onboarding.voiceProfiles ?? {}, [brand.id]: { ...res, updatedAt: Date.now() } }, styleAiAnswers });
48289
+ if (args.json) {
48290
+ console.log(JSON.stringify(res, null, 2));
48291
+ return;
48292
+ }
48293
+ const last = res.iterations[res.iterations.length - 1];
48294
+ process.stderr.write(`${GREEN}✅ voice corrected (match ${res.match}/100)${RESET}
48295
+ ${res.profile}
48296
+ ${DIM}sample: ${last?.sample ?? ""}${RESET}
48017
48297
  `);
48018
48298
  return;
48019
48299
  }
@@ -48286,10 +48566,10 @@ function probeBridge2(bin) {
48286
48566
  function hasBridge2() {
48287
48567
  if (probeBridge2(bridgeBin2))
48288
48568
  return true;
48289
- const installed = path3.join(INSTALL_PREFIX2, "todoforai-bridge");
48290
- if (!probeBridge2(installed))
48569
+ const installed2 = path3.join(INSTALL_PREFIX2, "todoforai-bridge");
48570
+ if (!probeBridge2(installed2))
48291
48571
  return false;
48292
- bridgeBin2 = installed;
48572
+ bridgeBin2 = installed2;
48293
48573
  return true;
48294
48574
  }
48295
48575
  function installBridge2() {
@@ -67757,7 +68037,7 @@ function visit(schema, fnOrHandlers) {
67757
68037
  return h ? h(node2, rewritten) : node2;
67758
68038
  };
67759
68039
  const cache = new Map;
67760
- function run(s) {
68040
+ function run2(s) {
67761
68041
  const cached2 = cache.get(s);
67762
68042
  if (cached2 === RESOLVING) {
67763
68043
  return new $ZodLazy({
@@ -67783,21 +68063,21 @@ function visit(schema, fnOrHandlers) {
67783
68063
  let changed = false;
67784
68064
  const newShape = {};
67785
68065
  for (const k of keys) {
67786
- const mapped = run(oldShape[k]);
68066
+ const mapped = run2(oldShape[k]);
67787
68067
  if (mapped !== oldShape[k])
67788
68068
  changed = true;
67789
68069
  newShape[k] = mapped;
67790
68070
  }
67791
68071
  let newCatchall = def.catchall;
67792
68072
  if (def.catchall) {
67793
- newCatchall = run(def.catchall);
68073
+ newCatchall = run2(def.catchall);
67794
68074
  if (newCatchall !== def.catchall)
67795
68075
  changed = true;
67796
68076
  }
67797
68077
  return changed ? clone(s, { ...def, shape: newShape, catchall: newCatchall }) : s;
67798
68078
  }
67799
68079
  case "array": {
67800
- const mapped = run(def.element);
68080
+ const mapped = run2(def.element);
67801
68081
  return mapped === def.element ? s : clone(s, { ...def, element: mapped });
67802
68082
  }
67803
68083
  case "tuple": {
@@ -67805,14 +68085,14 @@ function visit(schema, fnOrHandlers) {
67805
68085
  let changed = false;
67806
68086
  const newItems = [];
67807
68087
  for (const item of oldItems) {
67808
- const mapped = run(item);
68088
+ const mapped = run2(item);
67809
68089
  if (mapped !== item)
67810
68090
  changed = true;
67811
68091
  newItems.push(mapped);
67812
68092
  }
67813
68093
  let newRest = def.rest;
67814
68094
  if (def.rest) {
67815
- newRest = run(def.rest);
68095
+ newRest = run2(def.rest);
67816
68096
  if (newRest !== def.rest)
67817
68097
  changed = true;
67818
68098
  }
@@ -67820,12 +68100,12 @@ function visit(schema, fnOrHandlers) {
67820
68100
  }
67821
68101
  case "record":
67822
68102
  case "map": {
67823
- const newKey = run(def.keyType);
67824
- const newVal = run(def.valueType);
68103
+ const newKey = run2(def.keyType);
68104
+ const newVal = run2(def.valueType);
67825
68105
  return newKey === def.keyType && newVal === def.valueType ? s : clone(s, { ...def, keyType: newKey, valueType: newVal });
67826
68106
  }
67827
68107
  case "set": {
67828
- const newVal = run(def.valueType);
68108
+ const newVal = run2(def.valueType);
67829
68109
  return newVal === def.valueType ? s : clone(s, { ...def, valueType: newVal });
67830
68110
  }
67831
68111
  case "union": {
@@ -67833,7 +68113,7 @@ function visit(schema, fnOrHandlers) {
67833
68113
  let changed = false;
67834
68114
  const newOptions = [];
67835
68115
  for (const opt of oldOptions) {
67836
- const mapped = run(opt);
68116
+ const mapped = run2(opt);
67837
68117
  if (mapped !== opt)
67838
68118
  changed = true;
67839
68119
  newOptions.push(mapped);
@@ -67841,8 +68121,8 @@ function visit(schema, fnOrHandlers) {
67841
68121
  return changed ? clone(s, { ...def, options: newOptions }) : s;
67842
68122
  }
67843
68123
  case "intersection": {
67844
- const newLeft = run(def.left);
67845
- const newRight = run(def.right);
68124
+ const newLeft = run2(def.left);
68125
+ const newRight = run2(def.right);
67846
68126
  return newLeft === def.left && newRight === def.right ? s : clone(s, { ...def, left: newLeft, right: newRight });
67847
68127
  }
67848
68128
  case "optional":
@@ -67854,23 +68134,23 @@ function visit(schema, fnOrHandlers) {
67854
68134
  case "nonoptional":
67855
68135
  case "promise":
67856
68136
  case "success": {
67857
- const newInner = run(def.innerType);
68137
+ const newInner = run2(def.innerType);
67858
68138
  return newInner === def.innerType ? s : clone(s, { ...def, innerType: newInner });
67859
68139
  }
67860
68140
  case "pipe": {
67861
- const newIn = run(def.in);
67862
- const newOut = run(def.out);
68141
+ const newIn = run2(def.in);
68142
+ const newOut = run2(def.out);
67863
68143
  return newIn === def.in && newOut === def.out ? s : clone(s, { ...def, in: newIn, out: newOut });
67864
68144
  }
67865
68145
  case "function": {
67866
- const newInput = run(def.input);
67867
- const newOutput = run(def.output);
68146
+ const newInput = run2(def.input);
68147
+ const newOutput = run2(def.output);
67868
68148
  return newInput === def.input && newOutput === def.output ? s : clone(s, { ...def, input: newInput, output: newOutput });
67869
68149
  }
67870
68150
  case "lazy": {
67871
68151
  const original = def.getter;
67872
68152
  const { _cachedInner, ...rest } = def;
67873
- return clone(s, { ...rest, getter: () => run(original()) });
68153
+ return clone(s, { ...rest, getter: () => run2(original()) });
67874
68154
  }
67875
68155
  case "template_literal":
67876
68156
  case "string":
@@ -67898,7 +68178,7 @@ function visit(schema, fnOrHandlers) {
67898
68178
  }
67899
68179
  }
67900
68180
  }
67901
- return run(schema);
68181
+ return run2(schema);
67902
68182
  }
67903
68183
 
67904
68184
  // node_modules/zod/v4/classic/deep-partial.js
@@ -72769,6 +73049,8 @@ Cancelled by user (Ctrl+C)
72769
73049
  process.exit(2);
72770
73050
  }
72771
73051
  }
73052
+ if (positionals[0] === "brand" && positionals[1] === "voice" && await voiceDeviceCommand(positionals.slice(2), args))
73053
+ return;
72772
73054
  const deviceLogin = () => runDeviceLogin(apiUrl).catch((e) => {
72773
73055
  if (!(e instanceof DeviceLoginError))
72774
73056
  throw e;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@todoforai/cli",
3
- "version": "0.1.49",
3
+ "version": "0.1.50",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "todoforai-cli": "bin/todoforai-cli.js",