@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.
- package/dist/todoforai-cli.js +313 -31
- package/package.json +1 -1
package/dist/todoforai-cli.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
47849
|
-
|
|
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
|
|
48290
|
-
if (!probeBridge2(
|
|
48569
|
+
const installed2 = path3.join(INSTALL_PREFIX2, "todoforai-bridge");
|
|
48570
|
+
if (!probeBridge2(installed2))
|
|
48291
48571
|
return false;
|
|
48292
|
-
bridgeBin2 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
67824
|
-
const newVal =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
67845
|
-
const newRight =
|
|
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 =
|
|
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 =
|
|
67862
|
-
const newOut =
|
|
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 =
|
|
67867
|
-
const newOutput =
|
|
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: () =>
|
|
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
|
|
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;
|