@todoforai/cli 0.1.49 → 0.1.51
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 +396 -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");
|
|
@@ -42926,6 +42926,15 @@ class ApiClient {
|
|
|
42926
42926
|
upsertProjectGroup(projectId, slug, fields) {
|
|
42927
42927
|
return this.request("PUT", `/api/v1/projects/${projectId}/groups/${encodeURIComponent(slug)}`, { projectId, slug, ...fields });
|
|
42928
42928
|
}
|
|
42929
|
+
listProjectSurfaces(projectId) {
|
|
42930
|
+
return this.request("GET", `/api/v1/projects/${projectId}/surfaces`);
|
|
42931
|
+
}
|
|
42932
|
+
upsertProjectSurface(projectId, surfaceId, fields) {
|
|
42933
|
+
return this.request("PUT", `/api/v1/projects/${projectId}/surfaces/${encodeURIComponent(surfaceId)}`, { projectId, surfaceId, ...fields });
|
|
42934
|
+
}
|
|
42935
|
+
deleteProjectSurface(projectId, surfaceId) {
|
|
42936
|
+
return this.request("DELETE", `/api/v1/projects/${projectId}/surfaces/${encodeURIComponent(surfaceId)}`, { projectId, surfaceId });
|
|
42937
|
+
}
|
|
42929
42938
|
reorderProjectGroups(projectId, slugs) {
|
|
42930
42939
|
return this.request("PUT", `/api/v1/projects/${projectId}/groups-order`, { projectId, slugs });
|
|
42931
42940
|
}
|
|
@@ -43729,6 +43738,12 @@ var SERVER_TO_FRONTENDS = {
|
|
|
43729
43738
|
hexGrid: {
|
|
43730
43739
|
updated: (projectId) => `${"hexgrid:updated" /* HEXGRID_UPDATED */}:${projectId}`
|
|
43731
43740
|
},
|
|
43741
|
+
surfaces: {
|
|
43742
|
+
updated: (projectId) => `${"project:surfaces_updated" /* PROJECT_SURFACES_UPDATED */}:${projectId}`
|
|
43743
|
+
},
|
|
43744
|
+
killSwitch: {
|
|
43745
|
+
changed: (projectId) => `${"project:kill_switch" /* KILL_SWITCH */}:${projectId}`
|
|
43746
|
+
},
|
|
43732
43747
|
sandbox: {
|
|
43733
43748
|
state: (userId) => `${"sandbox:state" /* SANDBOX_STATE */}:${userId}`
|
|
43734
43749
|
},
|
|
@@ -44390,6 +44405,14 @@ var TOPICS = {
|
|
|
44390
44405
|
channel: (p) => SERVER_TO_FRONTENDS.hexGrid.updated(p.projectId),
|
|
44391
44406
|
audience: "frontend"
|
|
44392
44407
|
},
|
|
44408
|
+
["project:surfaces_updated" /* PROJECT_SURFACES_UPDATED */]: {
|
|
44409
|
+
channel: (p) => SERVER_TO_FRONTENDS.surfaces.updated(p.projectId),
|
|
44410
|
+
audience: "frontend"
|
|
44411
|
+
},
|
|
44412
|
+
["project:kill_switch" /* KILL_SWITCH */]: {
|
|
44413
|
+
channel: (p) => SERVER_TO_FRONTENDS.killSwitch.changed(p.projectId),
|
|
44414
|
+
audience: "frontend"
|
|
44415
|
+
},
|
|
44393
44416
|
["sandbox:state" /* SANDBOX_STATE */]: {
|
|
44394
44417
|
channel: (p) => SERVER_TO_FRONTENDS.sandbox.state(p.userId),
|
|
44395
44418
|
audience: "frontend"
|
|
@@ -44526,7 +44549,7 @@ import { parseArgs } from "util";
|
|
|
44526
44549
|
// package.json
|
|
44527
44550
|
var package_default = {
|
|
44528
44551
|
name: "@todoforai/cli",
|
|
44529
|
-
version: "0.1.
|
|
44552
|
+
version: "0.1.51",
|
|
44530
44553
|
type: "module",
|
|
44531
44554
|
bin: {
|
|
44532
44555
|
"todoforai-cli": "bin/todoforai-cli.js",
|
|
@@ -44706,6 +44729,8 @@ function parseCliArgs() {
|
|
|
44706
44729
|
url: { type: "string" },
|
|
44707
44730
|
"pasted-file": { type: "string" },
|
|
44708
44731
|
"from-company": { type: "boolean", default: false },
|
|
44732
|
+
"dry-run": { type: "boolean", default: false },
|
|
44733
|
+
max: { type: "string" },
|
|
44709
44734
|
seed: { type: "string" },
|
|
44710
44735
|
emails: { type: "string" },
|
|
44711
44736
|
ttl: { type: "string" },
|
|
@@ -47625,6 +47650,180 @@ var STATUS_HELP2 = {
|
|
|
47625
47650
|
["DELETED" /* DELETED */]: "Marked for deletion"
|
|
47626
47651
|
};
|
|
47627
47652
|
|
|
47653
|
+
// src/voice-collect.ts
|
|
47654
|
+
import { execFileSync } from "node:child_process";
|
|
47655
|
+
var run = (bin, args) => execFileSync(bin, args, { encoding: "utf8", maxBuffer: 256 << 20, timeout: 60000, stdio: ["ignore", "pipe", "ignore"] });
|
|
47656
|
+
var installed = (bin) => {
|
|
47657
|
+
try {
|
|
47658
|
+
run("which", [bin]);
|
|
47659
|
+
return true;
|
|
47660
|
+
} catch {
|
|
47661
|
+
return false;
|
|
47662
|
+
}
|
|
47663
|
+
};
|
|
47664
|
+
var MIN_CHARS = 30;
|
|
47665
|
+
var SEP = /^─{20,}$/m;
|
|
47666
|
+
function parseThread(out) {
|
|
47667
|
+
return out.split(SEP).slice(1).map((chunk) => {
|
|
47668
|
+
const lines = chunk.trim().split(`
|
|
47669
|
+
`);
|
|
47670
|
+
let i = 0;
|
|
47671
|
+
const h = {};
|
|
47672
|
+
for (;i < lines.length && /^\s*(From|To|Date|Auth|Cc):/.test(lines[i]); i++) {
|
|
47673
|
+
const [k, ...v] = lines[i].trim().split(":");
|
|
47674
|
+
h[k] = v.join(":").trim();
|
|
47675
|
+
}
|
|
47676
|
+
return { from: h.From ?? "", date: h.Date ?? "", body: lines.slice(i).join(`
|
|
47677
|
+
`).trim() };
|
|
47678
|
+
}).filter((m) => m.from);
|
|
47679
|
+
}
|
|
47680
|
+
function stripReply(body) {
|
|
47681
|
+
const lines = body.split(`
|
|
47682
|
+
`);
|
|
47683
|
+
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()));
|
|
47684
|
+
let kept = (cut >= 0 ? lines.slice(0, cut) : lines).filter((l) => !/^Attachments: /.test(l)).join(`
|
|
47685
|
+
`).trim();
|
|
47686
|
+
kept = kept.replace(/\n--\s*\n[\s\S]*$/, "").trim();
|
|
47687
|
+
return kept;
|
|
47688
|
+
}
|
|
47689
|
+
var gmail = {
|
|
47690
|
+
tool: "zele",
|
|
47691
|
+
check: () => {
|
|
47692
|
+
if (!installed("zele"))
|
|
47693
|
+
return "zele is not installed";
|
|
47694
|
+
const who = (() => {
|
|
47695
|
+
try {
|
|
47696
|
+
return run("zele", ["whoami"]);
|
|
47697
|
+
} catch {
|
|
47698
|
+
return "";
|
|
47699
|
+
}
|
|
47700
|
+
})();
|
|
47701
|
+
return /email:/.test(who) ? null : "zele is not signed in (run: zele login --method google)";
|
|
47702
|
+
},
|
|
47703
|
+
collect: (max) => {
|
|
47704
|
+
const accounts = [...run("zele", ["whoami"]).matchAll(/email: (\S+)/g)].map((m) => m[1]);
|
|
47705
|
+
const isMe = (from) => accounts.some((a) => from.toLowerCase().includes(a.toLowerCase()));
|
|
47706
|
+
const samples = [];
|
|
47707
|
+
for (const account of accounts) {
|
|
47708
|
+
const list = run("zele", ["mail", "search", "from:me", "--limit", String(max), "--account", account]);
|
|
47709
|
+
const ids = [...list.matchAll(/^ - id: ([0-9a-f]+)\n(?:.*\n)*? messages: (\d+)/gm)].filter((m) => Number(m[2]) > 1).map((m) => m[1]);
|
|
47710
|
+
for (const id of ids) {
|
|
47711
|
+
if (samples.length >= max)
|
|
47712
|
+
break;
|
|
47713
|
+
const msgs = parseThread(run("zele", ["mail", "read", id, "--account", account]));
|
|
47714
|
+
for (let i = 1;i < msgs.length && samples.length < max; i++) {
|
|
47715
|
+
if (!isMe(msgs[i].from) || isMe(msgs[i - 1].from))
|
|
47716
|
+
continue;
|
|
47717
|
+
const text = stripReply(msgs[i].body), prompt = stripReply(msgs[i - 1].body);
|
|
47718
|
+
if (text.length < MIN_CHARS || prompt.length < 20)
|
|
47719
|
+
continue;
|
|
47720
|
+
samples.push({ at: msgs[i].date, text, prompt, url: `gmail:${account}/${id}#${i}` });
|
|
47721
|
+
}
|
|
47722
|
+
}
|
|
47723
|
+
}
|
|
47724
|
+
return { samples, note: `${accounts.length} account(s)` };
|
|
47725
|
+
}
|
|
47726
|
+
};
|
|
47727
|
+
var outlook = {
|
|
47728
|
+
tool: "outlook-api",
|
|
47729
|
+
check: () => {
|
|
47730
|
+
if (!installed("outlook-api"))
|
|
47731
|
+
return "outlook-api is not installed";
|
|
47732
|
+
try {
|
|
47733
|
+
const me = JSON.parse(run("outlook-api", ["whoami"]));
|
|
47734
|
+
return me?.mail || me?.userPrincipalName ? null : "outlook-api is not signed in";
|
|
47735
|
+
} catch {
|
|
47736
|
+
return "outlook-api is not signed in (run: outlook-api auth)";
|
|
47737
|
+
}
|
|
47738
|
+
},
|
|
47739
|
+
collect: (max) => {
|
|
47740
|
+
const me = JSON.parse(run("outlook-api", ["whoami"]));
|
|
47741
|
+
const mine = [me.mail, me.userPrincipalName].filter(Boolean).map((a) => a.toLowerCase());
|
|
47742
|
+
const isMe = (m) => mine.includes((m.from?.emailAddress?.address ?? "").toLowerCase());
|
|
47743
|
+
const sent = JSON.parse(run("outlook-api", ["list", "-f", "sentitems", "-n", String(max), "--select", "id,conversationId,from,sentDateTime,webLink"]));
|
|
47744
|
+
const sentRows = Array.isArray(sent) ? sent : sent.value ?? [];
|
|
47745
|
+
const samples = [];
|
|
47746
|
+
const seenConv = new Set;
|
|
47747
|
+
for (const s of sentRows) {
|
|
47748
|
+
if (!s.conversationId || seenConv.has(s.conversationId))
|
|
47749
|
+
continue;
|
|
47750
|
+
seenConv.add(s.conversationId);
|
|
47751
|
+
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`]));
|
|
47752
|
+
const msgs = (conv.value ?? []).sort((a, b) => (a.receivedDateTime ?? "").localeCompare(b.receivedDateTime ?? ""));
|
|
47753
|
+
const body = (id) => stripReply(JSON.parse(run("outlook-api", ["read", id, "--text"])).body?.content ?? "");
|
|
47754
|
+
for (let i = 1;i < msgs.length && samples.length < max; i++) {
|
|
47755
|
+
if (!isMe(msgs[i]) || isMe(msgs[i - 1]))
|
|
47756
|
+
continue;
|
|
47757
|
+
const text = body(msgs[i].id), prompt = body(msgs[i - 1].id);
|
|
47758
|
+
if (text.length < MIN_CHARS || prompt.length < 20)
|
|
47759
|
+
continue;
|
|
47760
|
+
samples.push({ at: msgs[i].sentDateTime ?? msgs[i].receivedDateTime, text, prompt, url: msgs[i].webLink ?? `outlook:${msgs[i].id}` });
|
|
47761
|
+
}
|
|
47762
|
+
if (samples.length >= max)
|
|
47763
|
+
break;
|
|
47764
|
+
}
|
|
47765
|
+
return { samples };
|
|
47766
|
+
}
|
|
47767
|
+
};
|
|
47768
|
+
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(" ");
|
|
47769
|
+
var chat = {
|
|
47770
|
+
tool: "tfa-memory",
|
|
47771
|
+
check: () => installed("tfa-memory") ? null : "tfa-memory is not installed",
|
|
47772
|
+
collect: (max) => {
|
|
47773
|
+
const mem = (...a) => run("tfa-memory", a);
|
|
47774
|
+
const anchors = new Map;
|
|
47775
|
+
for (const q of QUERIES) {
|
|
47776
|
+
for (const line of mem("search", q, "--source", "todo", "--mode", "lexical", "--limit", "50").split(`
|
|
47777
|
+
`)) {
|
|
47778
|
+
const m = line.match(/todo\/([0-9a-f-]{36}):([0-9a-f-]{36})/);
|
|
47779
|
+
if (m && !anchors.has(m[1]))
|
|
47780
|
+
anchors.set(m[1], `todo/${m[1]}:${m[2]}`);
|
|
47781
|
+
}
|
|
47782
|
+
if (anchors.size >= max)
|
|
47783
|
+
break;
|
|
47784
|
+
}
|
|
47785
|
+
const samples = [];
|
|
47786
|
+
for (const anchor of [...anchors.values()].slice(0, max)) {
|
|
47787
|
+
let msgs;
|
|
47788
|
+
try {
|
|
47789
|
+
msgs = JSON.parse(mem("around", anchor, "--before", "200", "--after", "200", "--json"));
|
|
47790
|
+
} catch {
|
|
47791
|
+
continue;
|
|
47792
|
+
}
|
|
47793
|
+
msgs.sort((a, b) => a.createdAt - b.createdAt);
|
|
47794
|
+
for (let i = 1;i < msgs.length; i++) {
|
|
47795
|
+
if (msgs[i].meta?.role !== "user" || msgs[i - 1].meta?.role === "user")
|
|
47796
|
+
continue;
|
|
47797
|
+
const text = msgs[i].content.trim(), prompt = msgs[i - 1].content.trim();
|
|
47798
|
+
if (text.length < MIN_CHARS || prompt.length < 40)
|
|
47799
|
+
continue;
|
|
47800
|
+
if (/^\s*(#|\/|@|\{)/.test(text))
|
|
47801
|
+
continue;
|
|
47802
|
+
if (text.split(`
|
|
47803
|
+
`).length > 6)
|
|
47804
|
+
continue;
|
|
47805
|
+
if (/^Wait w_|\nWait w_[0-9a-f]+ '|^Connected \w+ \(card\)|^\[/m.test(text))
|
|
47806
|
+
continue;
|
|
47807
|
+
samples.push({ at: new Date(msgs[i].createdAt).toISOString(), text, prompt: prompt.slice(0, 4000), url: msgs[i].address });
|
|
47808
|
+
}
|
|
47809
|
+
}
|
|
47810
|
+
return { samples: samples.slice(0, max), note: `${anchors.size} todos` };
|
|
47811
|
+
}
|
|
47812
|
+
};
|
|
47813
|
+
var ADAPTERS = { Gmail: gmail, Outlook: outlook, Chat: chat };
|
|
47814
|
+
var DEVICE_CHANNELS = Object.keys(ADAPTERS);
|
|
47815
|
+
function checkChannel(channel) {
|
|
47816
|
+
const a = ADAPTERS[channel];
|
|
47817
|
+
return a ? a.check() : `${channel} is not read on the device (server-side: pass --url, or paste)`;
|
|
47818
|
+
}
|
|
47819
|
+
function collectChannel(channel, max = 40) {
|
|
47820
|
+
const a = ADAPTERS[channel];
|
|
47821
|
+
if (!a)
|
|
47822
|
+
throw new Error(`No device adapter for ${channel}`);
|
|
47823
|
+
const r = a.collect(Math.min(200, Math.max(1, Math.floor(Number(max) || 40))));
|
|
47824
|
+
return { ...r, samples: r.samples.filter((x) => x.text.trim().length >= MIN_CHARS) };
|
|
47825
|
+
}
|
|
47826
|
+
|
|
47628
47827
|
// src/manage-command.ts
|
|
47629
47828
|
function printTodoHelp() {
|
|
47630
47829
|
process.stderr.write(`
|
|
@@ -47670,11 +47869,18 @@ Usage:
|
|
|
47670
47869
|
tfa-cli project groups List todo groups
|
|
47671
47870
|
tfa-cli project groups set <slug> <field=value>… Create/update a group: name description pinned archived order
|
|
47672
47871
|
tfa-cli project groups reorder <slug>… Set wall order
|
|
47872
|
+
tfa-cli project surfaces List the board's card surfaces (live refs shown as cards)
|
|
47873
|
+
tfa-cli project surfaces set <id> ref=<ref> [title=…] [group=<slug>] [order=N]
|
|
47874
|
+
Show a ref on the board: a todo id, <todoId>:<alias>
|
|
47875
|
+
(shown artifact) or an http(s) url. group= puts it
|
|
47876
|
+
inside that group's card; group="" makes it standalone.
|
|
47877
|
+
tfa-cli project surfaces rm <id> Take it off the board
|
|
47673
47878
|
|
|
47674
47879
|
Examples:
|
|
47675
47880
|
tfa-cli project set name="Q4 launch"
|
|
47676
47881
|
tfa-cli project groups set marketing name=Marketing description="Top-of-funnel work"
|
|
47677
47882
|
tfa-cli project groups reorder marketing sales ops
|
|
47883
|
+
tfa-cli project surfaces set weekly-kpis ref=$TODOFORAI_TODO_ID:kpi title="Weekly KPIs" group=marketing
|
|
47678
47884
|
`);
|
|
47679
47885
|
}
|
|
47680
47886
|
var TODO_ALIASES = {
|
|
@@ -47827,6 +48033,43 @@ async function projectCommand(api, positionals, args) {
|
|
|
47827
48033
|
}
|
|
47828
48034
|
fail(`Unknown 'project groups' verb: ${verb}`);
|
|
47829
48035
|
}
|
|
48036
|
+
if (sub === "surfaces") {
|
|
48037
|
+
const [verb, ...sargs] = rest;
|
|
48038
|
+
if (!verb) {
|
|
48039
|
+
const surfaces = await api.listProjectSurfaces(projectId);
|
|
48040
|
+
if (args.json) {
|
|
48041
|
+
console.log(JSON.stringify(surfaces, null, 2));
|
|
48042
|
+
return;
|
|
48043
|
+
}
|
|
48044
|
+
for (const s of surfaces)
|
|
48045
|
+
process.stderr.write(`${s.id} ${s.ref} ${DIM}${[s.title, s.group && `in ${s.group}`].filter(Boolean).join(" ")}${RESET}
|
|
48046
|
+
`);
|
|
48047
|
+
return;
|
|
48048
|
+
}
|
|
48049
|
+
if (verb === "set") {
|
|
48050
|
+
const [id, ...fields] = sargs;
|
|
48051
|
+
if (!id)
|
|
48052
|
+
fail("Usage: tfa-cli project surfaces set <id> ref=<ref> [title=…] [group=<slug>] [order=N]");
|
|
48053
|
+
const surface = await api.upsertProjectSurface(projectId, id, parseAssignments2(fields));
|
|
48054
|
+
if (args.json) {
|
|
48055
|
+
console.log(JSON.stringify(surface, null, 2));
|
|
48056
|
+
return;
|
|
48057
|
+
}
|
|
48058
|
+
process.stderr.write(`${GREEN}✅ surface ${id} shows ${surface.ref}${surface.group ? ` in ${surface.group}` : ""}${RESET}
|
|
48059
|
+
`);
|
|
48060
|
+
return;
|
|
48061
|
+
}
|
|
48062
|
+
if (verb === "rm") {
|
|
48063
|
+
const [id] = sargs;
|
|
48064
|
+
if (!id)
|
|
48065
|
+
fail("Usage: tfa-cli project surfaces rm <id>");
|
|
48066
|
+
await api.deleteProjectSurface(projectId, id);
|
|
48067
|
+
process.stderr.write(`${GREEN}✅ surface ${id} removed${RESET}
|
|
48068
|
+
`);
|
|
48069
|
+
return;
|
|
48070
|
+
}
|
|
48071
|
+
fail(`Unknown 'project surfaces' verb: ${verb}`);
|
|
48072
|
+
}
|
|
47830
48073
|
fail(`Unknown 'project' subcommand: ${sub}`);
|
|
47831
48074
|
}
|
|
47832
48075
|
function printBrandHelp() {
|
|
@@ -47843,12 +48086,18 @@ Usage:
|
|
|
47843
48086
|
tfa-cli brand select <brand|none> Active brand for the account
|
|
47844
48087
|
tfa-cli brand voice Learned profile + sources
|
|
47845
48088
|
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
|
-
|
|
48089
|
+
tfa-cli brand voice collect <channel> [--url U | --pasted-file <F|-> | --max N]
|
|
48090
|
+
Add a writing sample source.
|
|
48091
|
+
On this device (signed-in CLI, reply pairs):
|
|
48092
|
+
Gmail (zele) Outlook (outlook-api) Chat (tfa-memory)
|
|
48093
|
+
Server-side (--url or paste):
|
|
48094
|
+
X LinkedIn Facebook Instagram Slack Teams
|
|
48095
|
+
--dry-run print the samples as JSONL, store nothing
|
|
48096
|
+
tfa-cli brand voice check [<channel>|--all] Can this device read the channel? tool, login, sample count
|
|
47850
48097
|
tfa-cli brand voice remove <channel>
|
|
47851
48098
|
tfa-cli brand voice learn [--from-company] Run the refinement loop, store the profile
|
|
48099
|
+
tfa-cli brand voice correct "<what is off>" Tell the learner what it got wrong; profile is updated
|
|
48100
|
+
in one pass and the correction is kept for every re-learn
|
|
47852
48101
|
|
|
47853
48102
|
<brand> is an id or name (unique partial works). Voice subcommands use the
|
|
47854
48103
|
selected brand unless --brand <id|name> is given (--pasted-file - reads stdin).
|
|
@@ -47927,8 +48176,61 @@ async function brandCommand(api, positionals, args) {
|
|
|
47927
48176
|
return voiceCommand(api, rest, args);
|
|
47928
48177
|
fail(`Unknown 'brand' subcommand: ${sub}`);
|
|
47929
48178
|
}
|
|
48179
|
+
function dryCollect(channel, max) {
|
|
48180
|
+
const reason = checkChannel(channel);
|
|
48181
|
+
if (reason)
|
|
48182
|
+
fail(`${channel}: ${reason}`);
|
|
48183
|
+
const { samples, note } = collectChannel(channel, max);
|
|
48184
|
+
for (const x of samples)
|
|
48185
|
+
console.log(JSON.stringify(x));
|
|
48186
|
+
process.stderr.write(`${DIM}${samples.length} samples${note ? ` · ${note}` : ""} (not stored)${RESET}
|
|
48187
|
+
`);
|
|
48188
|
+
}
|
|
48189
|
+
async function voiceDeviceCommand(rest, args) {
|
|
48190
|
+
const [verb, ...vargs] = rest;
|
|
48191
|
+
if (verb === "check") {
|
|
48192
|
+
const channels3 = args.all || !vargs[0] ? DEVICE_CHANNELS : [vargs[0]];
|
|
48193
|
+
let failed = 0;
|
|
48194
|
+
const report = {};
|
|
48195
|
+
for (const ch of channels3) {
|
|
48196
|
+
const reason = checkChannel(ch);
|
|
48197
|
+
if (reason) {
|
|
48198
|
+
failed++;
|
|
48199
|
+
report[ch] = { ok: false, reason };
|
|
48200
|
+
continue;
|
|
48201
|
+
}
|
|
48202
|
+
try {
|
|
48203
|
+
const r = collectChannel(ch, Number(args.max ?? 10));
|
|
48204
|
+
const bad = r.samples.filter((x) => !x.text.trim() || ADAPTERS[ch] && !x.prompt).length;
|
|
48205
|
+
const ok = r.samples.length > 0 && bad === 0;
|
|
48206
|
+
if (!ok)
|
|
48207
|
+
failed++;
|
|
48208
|
+
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" } };
|
|
48209
|
+
} catch (e) {
|
|
48210
|
+
failed++;
|
|
48211
|
+
report[ch] = { ok: false, reason: e.message };
|
|
48212
|
+
}
|
|
48213
|
+
}
|
|
48214
|
+
if (args.json)
|
|
48215
|
+
console.log(JSON.stringify(report, null, 2));
|
|
48216
|
+
else
|
|
48217
|
+
for (const [ch, r] of Object.entries(report))
|
|
48218
|
+
process.stderr.write(`${r.ok ? GREEN + "✅" : RED + "❌"} ${ch}${RESET} ${DIM}${r.ok ? `${r.samples} samples${r.note ? ` · ${r.note}` : ""}` : r.reason}${RESET}
|
|
48219
|
+
`);
|
|
48220
|
+
process.exit(failed ? 1 : 0);
|
|
48221
|
+
}
|
|
48222
|
+
if (verb === "collect" && args["dry-run"]) {
|
|
48223
|
+
if (!vargs[0])
|
|
48224
|
+
fail("Usage: tfa-cli brand voice collect <channel> --dry-run [--max N]");
|
|
48225
|
+
dryCollect(vargs[0], Number(args.max ?? 40));
|
|
48226
|
+
return true;
|
|
48227
|
+
}
|
|
48228
|
+
return false;
|
|
48229
|
+
}
|
|
47930
48230
|
async function voiceCommand(api, rest, args) {
|
|
47931
48231
|
const [verb, ...vargs] = rest;
|
|
48232
|
+
if (verb === "collect" && !vargs[0])
|
|
48233
|
+
fail("Usage: tfa-cli brand voice collect <channel> [--url U | --pasted-file F|- | --max N] [--dry-run]");
|
|
47932
48234
|
const onboarding = await api.getOnboarding();
|
|
47933
48235
|
if (verb === "answers") {
|
|
47934
48236
|
if (vargs.length) {
|
|
@@ -47977,13 +48279,26 @@ ${DIM}match ${profile.match}/100 · source ${profile.source}${RESET}
|
|
|
47977
48279
|
}
|
|
47978
48280
|
if (verb === "collect") {
|
|
47979
48281
|
const channel = vargs[0];
|
|
47980
|
-
if (!channel)
|
|
47981
|
-
fail("Usage: tfa-cli brand voice collect <channel> [--url U | --pasted-file F|-]");
|
|
47982
48282
|
let pasted;
|
|
47983
48283
|
if (args["pasted-file"]) {
|
|
47984
48284
|
const { readFileSync: readFileSync3 } = await import("node:fs");
|
|
47985
48285
|
pasted = readFileSync3(args["pasted-file"] === "-" ? 0 : args["pasted-file"], "utf8");
|
|
47986
48286
|
}
|
|
48287
|
+
const onDevice = !pasted && !args.url && ADAPTERS[channel];
|
|
48288
|
+
if (onDevice) {
|
|
48289
|
+
const reason = checkChannel(channel);
|
|
48290
|
+
if (reason)
|
|
48291
|
+
fail(`${channel}: ${reason}`);
|
|
48292
|
+
process.stderr.write(`${DIM}reading ${channel} on this device…${RESET}
|
|
48293
|
+
`);
|
|
48294
|
+
const { samples, note } = collectChannel(channel, Number(args.max ?? 40));
|
|
48295
|
+
if (!samples.length)
|
|
48296
|
+
fail(`${channel}: nothing usable came back${note ? ` (${note})` : ""}`);
|
|
48297
|
+
const { sources: sources2 } = await api.collectVoiceSource(brand.id, channel, { samples });
|
|
48298
|
+
process.stderr.write(`${GREEN}✅ ${channel}: ${samples.length} reply pairs stored (${sources2.length} source(s))${RESET}
|
|
48299
|
+
`);
|
|
48300
|
+
return;
|
|
48301
|
+
}
|
|
47987
48302
|
const { sources } = await api.collectVoiceSource(brand.id, channel, { url: args.url, pasted });
|
|
47988
48303
|
process.stderr.write(`${GREEN}✅ ${channel} collected (${sources.length} source(s))${RESET}
|
|
47989
48304
|
`);
|
|
@@ -48014,6 +48329,31 @@ ${DIM}match ${profile.match}/100 · source ${profile.source}${RESET}
|
|
|
48014
48329
|
}
|
|
48015
48330
|
process.stderr.write(`${GREEN}✅ voice learned (match ${res.match}/100, source ${res.source})${RESET}
|
|
48016
48331
|
${res.profile}
|
|
48332
|
+
`);
|
|
48333
|
+
return;
|
|
48334
|
+
}
|
|
48335
|
+
if (verb === "correct") {
|
|
48336
|
+
const text = vargs.join(" ").trim();
|
|
48337
|
+
if (!text)
|
|
48338
|
+
fail('Usage: tfa-cli brand voice correct "<what is off>"');
|
|
48339
|
+
const stored = onboarding.voiceProfiles?.[brand.id];
|
|
48340
|
+
if (!stored?.profile)
|
|
48341
|
+
fail("No voice learned yet — 'tfa-cli brand voice learn' first");
|
|
48342
|
+
process.stderr.write(`${DIM}applying correction…${RESET}
|
|
48343
|
+
`);
|
|
48344
|
+
const res = await api.refineBrandVoice(brand.id, onboarding.styleAnswers ?? {}, undefined, text);
|
|
48345
|
+
if (!res.profile)
|
|
48346
|
+
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
|
+
if (args.json) {
|
|
48350
|
+
console.log(JSON.stringify(res, null, 2));
|
|
48351
|
+
return;
|
|
48352
|
+
}
|
|
48353
|
+
const last = res.iterations[res.iterations.length - 1];
|
|
48354
|
+
process.stderr.write(`${GREEN}✅ voice corrected (match ${res.match}/100)${RESET}
|
|
48355
|
+
${res.profile}
|
|
48356
|
+
${DIM}sample: ${last?.sample ?? ""}${RESET}
|
|
48017
48357
|
`);
|
|
48018
48358
|
return;
|
|
48019
48359
|
}
|
|
@@ -48286,10 +48626,10 @@ function probeBridge2(bin) {
|
|
|
48286
48626
|
function hasBridge2() {
|
|
48287
48627
|
if (probeBridge2(bridgeBin2))
|
|
48288
48628
|
return true;
|
|
48289
|
-
const
|
|
48290
|
-
if (!probeBridge2(
|
|
48629
|
+
const installed2 = path3.join(INSTALL_PREFIX2, "todoforai-bridge");
|
|
48630
|
+
if (!probeBridge2(installed2))
|
|
48291
48631
|
return false;
|
|
48292
|
-
bridgeBin2 =
|
|
48632
|
+
bridgeBin2 = installed2;
|
|
48293
48633
|
return true;
|
|
48294
48634
|
}
|
|
48295
48635
|
function installBridge2() {
|
|
@@ -67757,7 +68097,7 @@ function visit(schema, fnOrHandlers) {
|
|
|
67757
68097
|
return h ? h(node2, rewritten) : node2;
|
|
67758
68098
|
};
|
|
67759
68099
|
const cache = new Map;
|
|
67760
|
-
function
|
|
68100
|
+
function run2(s) {
|
|
67761
68101
|
const cached2 = cache.get(s);
|
|
67762
68102
|
if (cached2 === RESOLVING) {
|
|
67763
68103
|
return new $ZodLazy({
|
|
@@ -67783,21 +68123,21 @@ function visit(schema, fnOrHandlers) {
|
|
|
67783
68123
|
let changed = false;
|
|
67784
68124
|
const newShape = {};
|
|
67785
68125
|
for (const k of keys) {
|
|
67786
|
-
const mapped =
|
|
68126
|
+
const mapped = run2(oldShape[k]);
|
|
67787
68127
|
if (mapped !== oldShape[k])
|
|
67788
68128
|
changed = true;
|
|
67789
68129
|
newShape[k] = mapped;
|
|
67790
68130
|
}
|
|
67791
68131
|
let newCatchall = def.catchall;
|
|
67792
68132
|
if (def.catchall) {
|
|
67793
|
-
newCatchall =
|
|
68133
|
+
newCatchall = run2(def.catchall);
|
|
67794
68134
|
if (newCatchall !== def.catchall)
|
|
67795
68135
|
changed = true;
|
|
67796
68136
|
}
|
|
67797
68137
|
return changed ? clone(s, { ...def, shape: newShape, catchall: newCatchall }) : s;
|
|
67798
68138
|
}
|
|
67799
68139
|
case "array": {
|
|
67800
|
-
const mapped =
|
|
68140
|
+
const mapped = run2(def.element);
|
|
67801
68141
|
return mapped === def.element ? s : clone(s, { ...def, element: mapped });
|
|
67802
68142
|
}
|
|
67803
68143
|
case "tuple": {
|
|
@@ -67805,14 +68145,14 @@ function visit(schema, fnOrHandlers) {
|
|
|
67805
68145
|
let changed = false;
|
|
67806
68146
|
const newItems = [];
|
|
67807
68147
|
for (const item of oldItems) {
|
|
67808
|
-
const mapped =
|
|
68148
|
+
const mapped = run2(item);
|
|
67809
68149
|
if (mapped !== item)
|
|
67810
68150
|
changed = true;
|
|
67811
68151
|
newItems.push(mapped);
|
|
67812
68152
|
}
|
|
67813
68153
|
let newRest = def.rest;
|
|
67814
68154
|
if (def.rest) {
|
|
67815
|
-
newRest =
|
|
68155
|
+
newRest = run2(def.rest);
|
|
67816
68156
|
if (newRest !== def.rest)
|
|
67817
68157
|
changed = true;
|
|
67818
68158
|
}
|
|
@@ -67820,12 +68160,12 @@ function visit(schema, fnOrHandlers) {
|
|
|
67820
68160
|
}
|
|
67821
68161
|
case "record":
|
|
67822
68162
|
case "map": {
|
|
67823
|
-
const newKey =
|
|
67824
|
-
const newVal =
|
|
68163
|
+
const newKey = run2(def.keyType);
|
|
68164
|
+
const newVal = run2(def.valueType);
|
|
67825
68165
|
return newKey === def.keyType && newVal === def.valueType ? s : clone(s, { ...def, keyType: newKey, valueType: newVal });
|
|
67826
68166
|
}
|
|
67827
68167
|
case "set": {
|
|
67828
|
-
const newVal =
|
|
68168
|
+
const newVal = run2(def.valueType);
|
|
67829
68169
|
return newVal === def.valueType ? s : clone(s, { ...def, valueType: newVal });
|
|
67830
68170
|
}
|
|
67831
68171
|
case "union": {
|
|
@@ -67833,7 +68173,7 @@ function visit(schema, fnOrHandlers) {
|
|
|
67833
68173
|
let changed = false;
|
|
67834
68174
|
const newOptions = [];
|
|
67835
68175
|
for (const opt of oldOptions) {
|
|
67836
|
-
const mapped =
|
|
68176
|
+
const mapped = run2(opt);
|
|
67837
68177
|
if (mapped !== opt)
|
|
67838
68178
|
changed = true;
|
|
67839
68179
|
newOptions.push(mapped);
|
|
@@ -67841,8 +68181,8 @@ function visit(schema, fnOrHandlers) {
|
|
|
67841
68181
|
return changed ? clone(s, { ...def, options: newOptions }) : s;
|
|
67842
68182
|
}
|
|
67843
68183
|
case "intersection": {
|
|
67844
|
-
const newLeft =
|
|
67845
|
-
const newRight =
|
|
68184
|
+
const newLeft = run2(def.left);
|
|
68185
|
+
const newRight = run2(def.right);
|
|
67846
68186
|
return newLeft === def.left && newRight === def.right ? s : clone(s, { ...def, left: newLeft, right: newRight });
|
|
67847
68187
|
}
|
|
67848
68188
|
case "optional":
|
|
@@ -67854,23 +68194,23 @@ function visit(schema, fnOrHandlers) {
|
|
|
67854
68194
|
case "nonoptional":
|
|
67855
68195
|
case "promise":
|
|
67856
68196
|
case "success": {
|
|
67857
|
-
const newInner =
|
|
68197
|
+
const newInner = run2(def.innerType);
|
|
67858
68198
|
return newInner === def.innerType ? s : clone(s, { ...def, innerType: newInner });
|
|
67859
68199
|
}
|
|
67860
68200
|
case "pipe": {
|
|
67861
|
-
const newIn =
|
|
67862
|
-
const newOut =
|
|
68201
|
+
const newIn = run2(def.in);
|
|
68202
|
+
const newOut = run2(def.out);
|
|
67863
68203
|
return newIn === def.in && newOut === def.out ? s : clone(s, { ...def, in: newIn, out: newOut });
|
|
67864
68204
|
}
|
|
67865
68205
|
case "function": {
|
|
67866
|
-
const newInput =
|
|
67867
|
-
const newOutput =
|
|
68206
|
+
const newInput = run2(def.input);
|
|
68207
|
+
const newOutput = run2(def.output);
|
|
67868
68208
|
return newInput === def.input && newOutput === def.output ? s : clone(s, { ...def, input: newInput, output: newOutput });
|
|
67869
68209
|
}
|
|
67870
68210
|
case "lazy": {
|
|
67871
68211
|
const original = def.getter;
|
|
67872
68212
|
const { _cachedInner, ...rest } = def;
|
|
67873
|
-
return clone(s, { ...rest, getter: () =>
|
|
68213
|
+
return clone(s, { ...rest, getter: () => run2(original()) });
|
|
67874
68214
|
}
|
|
67875
68215
|
case "template_literal":
|
|
67876
68216
|
case "string":
|
|
@@ -67898,7 +68238,7 @@ function visit(schema, fnOrHandlers) {
|
|
|
67898
68238
|
}
|
|
67899
68239
|
}
|
|
67900
68240
|
}
|
|
67901
|
-
return
|
|
68241
|
+
return run2(schema);
|
|
67902
68242
|
}
|
|
67903
68243
|
|
|
67904
68244
|
// node_modules/zod/v4/classic/deep-partial.js
|
|
@@ -69485,6 +69825,22 @@ var zUsageUpdate = object({
|
|
|
69485
69825
|
return;
|
|
69486
69826
|
})
|
|
69487
69827
|
});
|
|
69828
|
+
var zNoticeSeverity = union([
|
|
69829
|
+
literal("info"),
|
|
69830
|
+
literal("warning"),
|
|
69831
|
+
literal("error"),
|
|
69832
|
+
string2()
|
|
69833
|
+
]);
|
|
69834
|
+
var zNotice = object({
|
|
69835
|
+
severity: zNoticeSeverity,
|
|
69836
|
+
title: string2().min(1),
|
|
69837
|
+
description: defaultOnError(string2().nullish(), () => {
|
|
69838
|
+
return;
|
|
69839
|
+
}),
|
|
69840
|
+
_meta: defaultOnError(record(string2(), unknown()).nullish(), () => {
|
|
69841
|
+
return;
|
|
69842
|
+
})
|
|
69843
|
+
});
|
|
69488
69844
|
var zCompactionId = string2();
|
|
69489
69845
|
var zCompactionStatus = union([
|
|
69490
69846
|
literal("in_progress"),
|
|
@@ -69553,6 +69909,9 @@ var zSessionUpdate = union([
|
|
|
69553
69909
|
zUsageUpdate.and(object({
|
|
69554
69910
|
sessionUpdate: literal("usage_update")
|
|
69555
69911
|
})),
|
|
69912
|
+
zNotice.and(object({
|
|
69913
|
+
sessionUpdate: literal("notice")
|
|
69914
|
+
})),
|
|
69556
69915
|
zCompactionUpdate.and(object({
|
|
69557
69916
|
sessionUpdate: literal("compaction_update")
|
|
69558
69917
|
})),
|
|
@@ -69614,6 +69973,7 @@ var zSessionConfigOptionsCapabilities = object({
|
|
|
69614
69973
|
return;
|
|
69615
69974
|
})
|
|
69616
69975
|
});
|
|
69976
|
+
var zNoticeCapabilities = record(string2(), unknown());
|
|
69617
69977
|
var zClientSessionCapabilities = object({
|
|
69618
69978
|
compaction: defaultOnError(zCompactionCapabilities.nullish(), () => {
|
|
69619
69979
|
return;
|
|
@@ -69621,6 +69981,9 @@ var zClientSessionCapabilities = object({
|
|
|
69621
69981
|
configOptions: defaultOnError(zSessionConfigOptionsCapabilities.nullish(), () => {
|
|
69622
69982
|
return;
|
|
69623
69983
|
}),
|
|
69984
|
+
notices: defaultOnError(zNoticeCapabilities.nullish(), () => {
|
|
69985
|
+
return;
|
|
69986
|
+
}),
|
|
69624
69987
|
_meta: defaultOnError(record(string2(), unknown()).nullish(), () => {
|
|
69625
69988
|
return;
|
|
69626
69989
|
})
|
|
@@ -72769,6 +73132,8 @@ Cancelled by user (Ctrl+C)
|
|
|
72769
73132
|
process.exit(2);
|
|
72770
73133
|
}
|
|
72771
73134
|
}
|
|
73135
|
+
if (positionals[0] === "brand" && positionals[1] === "voice" && await voiceDeviceCommand(positionals.slice(2), args))
|
|
73136
|
+
return;
|
|
72772
73137
|
const deviceLogin = () => runDeviceLogin(apiUrl).catch((e) => {
|
|
72773
73138
|
if (!(e instanceof DeviceLoginError))
|
|
72774
73139
|
throw e;
|