@todoforai/cli 0.1.48 → 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 +422 -104
- 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",
|
|
@@ -44568,45 +44575,35 @@ function getEnv(name) {
|
|
|
44568
44575
|
}
|
|
44569
44576
|
function printUsage() {
|
|
44570
44577
|
process.stderr.write(`
|
|
44571
|
-
tfa-cli — TODOforAI CLI
|
|
44578
|
+
tfa-cli — TODOforAI CLI
|
|
44572
44579
|
|
|
44573
|
-
Usage:
|
|
44580
|
+
Usage: tfa-cli [OPTIONS] ["prompt"]
|
|
44581
|
+
tfa-cli [OPTIONS] <COMMAND> [ARGS]
|
|
44582
|
+
|
|
44583
|
+
Examples:
|
|
44574
44584
|
tfa-cli login # Browser-based device auth
|
|
44575
|
-
tfa-cli "prompt text" #
|
|
44576
|
-
|
|
44577
|
-
|
|
44578
|
-
tfa-cli
|
|
44579
|
-
tfa-cli
|
|
44580
|
-
tfa-cli
|
|
44581
|
-
tfa-cli
|
|
44582
|
-
tfa-cli
|
|
44583
|
-
tfa-cli
|
|
44584
|
-
tfa-cli
|
|
44585
|
-
tfa-cli
|
|
44586
|
-
tfa-cli
|
|
44587
|
-
tfa-cli
|
|
44588
|
-
tfa-cli project set|settings|groups|default|agent … # Edit the project, groups, defaults (see 'project --help')
|
|
44589
|
-
tfa-cli brand list|create|select|voice … # Brands (business contexts) + voice learning (see 'brand --help')
|
|
44590
|
-
tfa-cli device list|rename|wallpaper … # Paired machines (see 'device --help')
|
|
44591
|
-
tfa-cli list [-n 30] [--cursor N] [--all] [--status S] # List todos (paginated); see 'list --help'
|
|
44592
|
-
tfa-cli status <todo-id> <STATUS> # Update a todo's status (run 'status --help' for the full list)
|
|
44585
|
+
tfa-cli "prompt text" # New todo
|
|
44586
|
+
echo "content" | tfa-cli # From stdin
|
|
44587
|
+
tfa-cli start <id> # Registry template
|
|
44588
|
+
tfa-cli acp # Agent Client Protocol (stdio)
|
|
44589
|
+
tfa-cli agents # List agents
|
|
44590
|
+
tfa-cli models [filter] # Available models
|
|
44591
|
+
tfa-cli agent list|get|update|create … # Agents (update <agent> model=…)
|
|
44592
|
+
tfa-cli todo set <todo-id|-> title=… group=… star=true # Edit a todo's fields
|
|
44593
|
+
tfa-cli project list|set|settings|groups|default|agent … # Projects; edit the current one
|
|
44594
|
+
tfa-cli brand list|create|select|voice … # Brands (business contexts) + voice learning
|
|
44595
|
+
tfa-cli device list|rename|wallpaper … # Paired machines
|
|
44596
|
+
tfa-cli list [-n 30] [--cursor N] [--all] [--status S] # List todos (paginated)
|
|
44597
|
+
tfa-cli status <todo-id> <STATUS> # Update a todo's status
|
|
44593
44598
|
tfa-cli delete <todo-id> # Permanently delete a todo
|
|
44594
|
-
tfa-cli addmessage <todo-id> "text" #
|
|
44595
|
-
tfa-cli show <file|-> [todo-id] #
|
|
44596
|
-
|
|
44597
|
-
|
|
44598
|
-
|
|
44599
|
-
|
|
44600
|
-
|
|
44601
|
-
tfa-cli
|
|
44602
|
-
tfa-cli show list [todo-id] # List show blocks (ref, title, mime/url, card)
|
|
44603
|
-
# [--project <id>] [--card <name>] [--json]
|
|
44604
|
-
# no todo-id + --project (or $TODOFORAI_PROJECT_ID) = every todo
|
|
44605
|
-
tfa-cli open <url> [todo-id] # Show a live http(s) url in the chat as a preview
|
|
44606
|
-
# [--title T] [--alias A] [--json]
|
|
44607
|
-
tfa-cli recommend --template <id> # Add a template as a recommendation card (see 'todoregistry-cli create')
|
|
44608
|
-
tfa-cli claim mint --seed <projectId> [--emails a@x,b@y] [--ttl <sec>] # Mint /claim/<token> ownership links for a project you own
|
|
44609
|
-
tfa-cli next [--direction "<text>"] # Ask the analyzer for growth recommendation cards (optional free-text steer)
|
|
44599
|
+
tfa-cli addmessage <todo-id> "text" # Send a message and exit (like -r, no watch/bridge)
|
|
44600
|
+
tfa-cli show <file|-> [todo-id] # Render a local file (image/pdf/html…) in the chat; - = stdin
|
|
44601
|
+
tfa-cli show list|rm … # List / take down shown files
|
|
44602
|
+
tfa-cli open <url> [todo-id] # Live url preview in the chat
|
|
44603
|
+
tfa-cli recommend --template <id> # Add a template as a recommendation card
|
|
44604
|
+
tfa-cli claim mint --seed <projectId> [--emails a@x,b@y] [--ttl <sec>] # Mint /claim/<token> links for a project you own
|
|
44605
|
+
tfa-cli next [--direction "<text>"] # Analyzer growth recommendation cards
|
|
44606
|
+
tfa-cli <command> help # Details per command
|
|
44610
44607
|
|
|
44611
44608
|
Options:
|
|
44612
44609
|
--path <dir> Workspace path (default: cwd)
|
|
@@ -44614,36 +44611,46 @@ Options:
|
|
|
44614
44611
|
--agent, -a <name> Agent name (partial match)
|
|
44615
44612
|
--group <slug> Group new todo; omitted inherits TODOFORAI_GROUP_ID
|
|
44616
44613
|
--group-name <name> Display name for --group (last write wins)
|
|
44617
|
-
--model <model>
|
|
44618
|
-
|
|
44619
|
-
--
|
|
44620
|
-
--list-models List models usable with --model (optional substring filter) and exit
|
|
44621
|
-
--api-url <url> API URL
|
|
44622
|
-
--api-key <key> API key
|
|
44614
|
+
--model <model> Model for this todo only (see 'models')
|
|
44615
|
+
--api-url <url>
|
|
44616
|
+
--api-key <key>
|
|
44623
44617
|
--user-id <id> Admin HTTP impersonation; requires --no-watch
|
|
44624
|
-
--inspect, -i <todo-id>[@<slice>]
|
|
44625
|
-
--template, -t <id> ["prompt"]
|
|
44626
|
-
--resume, -r
|
|
44627
|
-
--continue, -c
|
|
44628
|
-
--non-interactive, -n Run to completion
|
|
44629
|
-
--dangerously-skip-permissions
|
|
44630
|
-
--allow-all
|
|
44618
|
+
--inspect, -i <todo-id>[@<slice>] Read chat log; slices: -3:, :1, 5:10, 7
|
|
44619
|
+
--template, -t <id> ["prompt"] Same as start; prompt overrides template task
|
|
44620
|
+
--resume, -r <todo-id> ["prompt"] Resume; optional follow-up
|
|
44621
|
+
--continue, -c ["prompt"] Resume last; optional follow-up
|
|
44622
|
+
--non-interactive, -n Run to completion, then exit
|
|
44623
|
+
--dangerously-skip-permissions CLI auto-approves every block
|
|
44624
|
+
--allow-all Agent permissions allow:*:* — nothing ever asks
|
|
44631
44625
|
--raw-sysmsg <file> Use file contents verbatim as system prompt (new TODO only)
|
|
44632
44626
|
--no-watch Create todo and exit
|
|
44633
|
-
--isolated
|
|
44634
|
-
this machine (workspace = --path/cwd); session dies with the CLI
|
|
44627
|
+
--isolated Agent sees ONLY this machine + dir (no cloud VM/other devices); ends with CLI
|
|
44635
44628
|
--no-bridge Do not auto-spawn bridge
|
|
44636
|
-
--
|
|
44637
|
-
--json
|
|
44638
|
-
--detailed 'inspect --json': keep ids, timestamps, agentSettingsId, scheduledTimestamp
|
|
44639
|
-
--format-anthropic 'inspect --json': Anthropic-style shape (tool_result in next user msg); attachment sources are uri-typed, so not a 1:1 messages.create input
|
|
44629
|
+
--json
|
|
44630
|
+
--detailed | --format-anthropic inspect --json: keep ids/timestamps | Anthropic messages shape
|
|
44640
44631
|
--safe Validate API key upfront
|
|
44641
|
-
--debug, -d
|
|
44632
|
+
--debug, -d
|
|
44642
44633
|
--debug-dump Attach LLM request debug info per turn (requires server grant)
|
|
44643
|
-
--show-config
|
|
44644
|
-
|
|
44645
|
-
|
|
44646
|
-
|
|
44634
|
+
help | version | config = -h | -v | --show-config (--reset-config wipes it)
|
|
44635
|
+
`);
|
|
44636
|
+
}
|
|
44637
|
+
function printShowHelp() {
|
|
44638
|
+
process.stderr.write(`
|
|
44639
|
+
tfa-cli show — put a file or url into the chat
|
|
44640
|
+
|
|
44641
|
+
Usage:
|
|
44642
|
+
tfa-cli show <file|-> [todo-id] Render a file by mimetype (- reads stdin)
|
|
44643
|
+
[--title T] [--alias A] [--mime M] [--card <name>] [--link] [--json]
|
|
44644
|
+
--link: compact download chip instead of inline render
|
|
44645
|
+
Prints "<todoId>:<alias|id> <public url>". Re-showing with the same
|
|
44646
|
+
--alias updates the block in place; old versions stay at /<id>/<version>
|
|
44647
|
+
tfa-cli show rm <ref|alias> Take it down (block, url, every version)
|
|
44648
|
+
tfa-cli show list [todo-id] List show blocks (ref, title, mime/url, card)
|
|
44649
|
+
[--project <id>] [--card <name>] [--json]
|
|
44650
|
+
no todo-id + --project (or $TODOFORAI_PROJECT_ID) = every todo
|
|
44651
|
+
tfa-cli open <url> [todo-id] Live http(s) url as a preview [--title T] [--alias A] [--json]
|
|
44652
|
+
|
|
44653
|
+
todo-id defaults to $TODOFORAI_TODO_ID (agent shell) or the last todo this CLI touched.
|
|
44647
44654
|
`);
|
|
44648
44655
|
}
|
|
44649
44656
|
var STATUS_HELP = {
|
|
@@ -44667,6 +44674,14 @@ All valid statuses:
|
|
|
44667
44674
|
${Object.values(TodoStatus).join(", ")}
|
|
44668
44675
|
`);
|
|
44669
44676
|
}
|
|
44677
|
+
var WORD_FLAGS = {
|
|
44678
|
+
help: "help",
|
|
44679
|
+
version: "version",
|
|
44680
|
+
models: "list-models",
|
|
44681
|
+
agents: "list-agents",
|
|
44682
|
+
config: "show-config"
|
|
44683
|
+
};
|
|
44684
|
+
var HELP_SUBCOMMANDS = ["agent", "todo", "project", "brand", "device", "list", "ls", "status", "show", "open"];
|
|
44670
44685
|
function parseCliArgs() {
|
|
44671
44686
|
const { values, positionals } = parseArgs({
|
|
44672
44687
|
args: process.argv.slice(2),
|
|
@@ -44698,6 +44713,8 @@ function parseCliArgs() {
|
|
|
44698
44713
|
url: { type: "string" },
|
|
44699
44714
|
"pasted-file": { type: "string" },
|
|
44700
44715
|
"from-company": { type: "boolean", default: false },
|
|
44716
|
+
"dry-run": { type: "boolean", default: false },
|
|
44717
|
+
max: { type: "string" },
|
|
44701
44718
|
seed: { type: "string" },
|
|
44702
44719
|
emails: { type: "string" },
|
|
44703
44720
|
ttl: { type: "string" },
|
|
@@ -44728,6 +44745,15 @@ function parseCliArgs() {
|
|
|
44728
44745
|
});
|
|
44729
44746
|
if (values["no-edge"])
|
|
44730
44747
|
values["no-bridge"] = true;
|
|
44748
|
+
const asFlag = !values["list-models"] && !values["list-agents"] && !values["show-config"] ? WORD_FLAGS[positionals[0]] : undefined;
|
|
44749
|
+
if (asFlag) {
|
|
44750
|
+
values[asFlag] = true;
|
|
44751
|
+
positionals.shift();
|
|
44752
|
+
}
|
|
44753
|
+
if (positionals[1] === "help" && HELP_SUBCOMMANDS.includes(positionals[0])) {
|
|
44754
|
+
values.help = true;
|
|
44755
|
+
positionals.splice(1, 1);
|
|
44756
|
+
}
|
|
44731
44757
|
return { values, positionals };
|
|
44732
44758
|
}
|
|
44733
44759
|
|
|
@@ -47608,6 +47634,180 @@ var STATUS_HELP2 = {
|
|
|
47608
47634
|
["DELETED" /* DELETED */]: "Marked for deletion"
|
|
47609
47635
|
};
|
|
47610
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
|
+
|
|
47611
47811
|
// src/manage-command.ts
|
|
47612
47812
|
function printTodoHelp() {
|
|
47613
47813
|
process.stderr.write(`
|
|
@@ -47644,6 +47844,7 @@ tfa-cli project — edit the current project
|
|
|
47644
47844
|
--project <id> or $TODOFORAI_PROJECT_ID selects the project.
|
|
47645
47845
|
|
|
47646
47846
|
Usage:
|
|
47847
|
+
tfa-cli project list Projects you can access (* = default)
|
|
47647
47848
|
tfa-cli project set <field=value>… name=… description=… brand=<businessContextId>
|
|
47648
47849
|
(isPublic refused from an agent shell)
|
|
47649
47850
|
tfa-cli project default Make it the project you land on at /
|
|
@@ -47725,6 +47926,19 @@ async function projectCommand(api, positionals, args) {
|
|
|
47725
47926
|
printProjectHelp();
|
|
47726
47927
|
process.exit(0);
|
|
47727
47928
|
}
|
|
47929
|
+
if (sub === "list" || sub === "ls") {
|
|
47930
|
+
const projects = await api.listProjects();
|
|
47931
|
+
if (args.json) {
|
|
47932
|
+
console.log(JSON.stringify(projects, null, 2));
|
|
47933
|
+
return;
|
|
47934
|
+
}
|
|
47935
|
+
for (const p of projects) {
|
|
47936
|
+
const pr = p.project ?? p;
|
|
47937
|
+
process.stderr.write(`${pr.isDefault ? "*" : " "} ${pr.name ?? ""} ${DIM}${pr.id}${RESET}
|
|
47938
|
+
`);
|
|
47939
|
+
}
|
|
47940
|
+
return;
|
|
47941
|
+
}
|
|
47728
47942
|
if (!projectId)
|
|
47729
47943
|
fail("No project — pass --project <id> or set TODOFORAI_PROJECT_ID");
|
|
47730
47944
|
if (sub === "set") {
|
|
@@ -47812,12 +48026,18 @@ Usage:
|
|
|
47812
48026
|
tfa-cli brand select <brand|none> Active brand for the account
|
|
47813
48027
|
tfa-cli brand voice Learned profile + sources
|
|
47814
48028
|
tfa-cli brand voice answers [<q>=<a>…] Show / set the brand-voice answers (strings)
|
|
47815
|
-
tfa-cli brand voice collect <channel> [--url U | --pasted-file <F|->]
|
|
47816
|
-
Add a writing sample source
|
|
47817
|
-
|
|
47818
|
-
|
|
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
|
|
47819
48037
|
tfa-cli brand voice remove <channel>
|
|
47820
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
|
|
47821
48041
|
|
|
47822
48042
|
<brand> is an id or name (unique partial works). Voice subcommands use the
|
|
47823
48043
|
selected brand unless --brand <id|name> is given (--pasted-file - reads stdin).
|
|
@@ -47896,8 +48116,61 @@ async function brandCommand(api, positionals, args) {
|
|
|
47896
48116
|
return voiceCommand(api, rest, args);
|
|
47897
48117
|
fail(`Unknown 'brand' subcommand: ${sub}`);
|
|
47898
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
|
+
}
|
|
47899
48170
|
async function voiceCommand(api, rest, args) {
|
|
47900
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]");
|
|
47901
48174
|
const onboarding = await api.getOnboarding();
|
|
47902
48175
|
if (verb === "answers") {
|
|
47903
48176
|
if (vargs.length) {
|
|
@@ -47946,13 +48219,26 @@ ${DIM}match ${profile.match}/100 · source ${profile.source}${RESET}
|
|
|
47946
48219
|
}
|
|
47947
48220
|
if (verb === "collect") {
|
|
47948
48221
|
const channel = vargs[0];
|
|
47949
|
-
if (!channel)
|
|
47950
|
-
fail("Usage: tfa-cli brand voice collect <channel> [--url U | --pasted-file F|-]");
|
|
47951
48222
|
let pasted;
|
|
47952
48223
|
if (args["pasted-file"]) {
|
|
47953
48224
|
const { readFileSync: readFileSync3 } = await import("node:fs");
|
|
47954
48225
|
pasted = readFileSync3(args["pasted-file"] === "-" ? 0 : args["pasted-file"], "utf8");
|
|
47955
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
|
+
}
|
|
47956
48242
|
const { sources } = await api.collectVoiceSource(brand.id, channel, { url: args.url, pasted });
|
|
47957
48243
|
process.stderr.write(`${GREEN}✅ ${channel} collected (${sources.length} source(s))${RESET}
|
|
47958
48244
|
`);
|
|
@@ -47983,6 +48269,31 @@ ${DIM}match ${profile.match}/100 · source ${profile.source}${RESET}
|
|
|
47983
48269
|
}
|
|
47984
48270
|
process.stderr.write(`${GREEN}✅ voice learned (match ${res.match}/100, source ${res.source})${RESET}
|
|
47985
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}
|
|
47986
48297
|
`);
|
|
47987
48298
|
return;
|
|
47988
48299
|
}
|
|
@@ -48255,10 +48566,10 @@ function probeBridge2(bin) {
|
|
|
48255
48566
|
function hasBridge2() {
|
|
48256
48567
|
if (probeBridge2(bridgeBin2))
|
|
48257
48568
|
return true;
|
|
48258
|
-
const
|
|
48259
|
-
if (!probeBridge2(
|
|
48569
|
+
const installed2 = path3.join(INSTALL_PREFIX2, "todoforai-bridge");
|
|
48570
|
+
if (!probeBridge2(installed2))
|
|
48260
48571
|
return false;
|
|
48261
|
-
bridgeBin2 =
|
|
48572
|
+
bridgeBin2 = installed2;
|
|
48262
48573
|
return true;
|
|
48263
48574
|
}
|
|
48264
48575
|
function installBridge2() {
|
|
@@ -67726,7 +68037,7 @@ function visit(schema, fnOrHandlers) {
|
|
|
67726
68037
|
return h ? h(node2, rewritten) : node2;
|
|
67727
68038
|
};
|
|
67728
68039
|
const cache = new Map;
|
|
67729
|
-
function
|
|
68040
|
+
function run2(s) {
|
|
67730
68041
|
const cached2 = cache.get(s);
|
|
67731
68042
|
if (cached2 === RESOLVING) {
|
|
67732
68043
|
return new $ZodLazy({
|
|
@@ -67752,21 +68063,21 @@ function visit(schema, fnOrHandlers) {
|
|
|
67752
68063
|
let changed = false;
|
|
67753
68064
|
const newShape = {};
|
|
67754
68065
|
for (const k of keys) {
|
|
67755
|
-
const mapped =
|
|
68066
|
+
const mapped = run2(oldShape[k]);
|
|
67756
68067
|
if (mapped !== oldShape[k])
|
|
67757
68068
|
changed = true;
|
|
67758
68069
|
newShape[k] = mapped;
|
|
67759
68070
|
}
|
|
67760
68071
|
let newCatchall = def.catchall;
|
|
67761
68072
|
if (def.catchall) {
|
|
67762
|
-
newCatchall =
|
|
68073
|
+
newCatchall = run2(def.catchall);
|
|
67763
68074
|
if (newCatchall !== def.catchall)
|
|
67764
68075
|
changed = true;
|
|
67765
68076
|
}
|
|
67766
68077
|
return changed ? clone(s, { ...def, shape: newShape, catchall: newCatchall }) : s;
|
|
67767
68078
|
}
|
|
67768
68079
|
case "array": {
|
|
67769
|
-
const mapped =
|
|
68080
|
+
const mapped = run2(def.element);
|
|
67770
68081
|
return mapped === def.element ? s : clone(s, { ...def, element: mapped });
|
|
67771
68082
|
}
|
|
67772
68083
|
case "tuple": {
|
|
@@ -67774,14 +68085,14 @@ function visit(schema, fnOrHandlers) {
|
|
|
67774
68085
|
let changed = false;
|
|
67775
68086
|
const newItems = [];
|
|
67776
68087
|
for (const item of oldItems) {
|
|
67777
|
-
const mapped =
|
|
68088
|
+
const mapped = run2(item);
|
|
67778
68089
|
if (mapped !== item)
|
|
67779
68090
|
changed = true;
|
|
67780
68091
|
newItems.push(mapped);
|
|
67781
68092
|
}
|
|
67782
68093
|
let newRest = def.rest;
|
|
67783
68094
|
if (def.rest) {
|
|
67784
|
-
newRest =
|
|
68095
|
+
newRest = run2(def.rest);
|
|
67785
68096
|
if (newRest !== def.rest)
|
|
67786
68097
|
changed = true;
|
|
67787
68098
|
}
|
|
@@ -67789,12 +68100,12 @@ function visit(schema, fnOrHandlers) {
|
|
|
67789
68100
|
}
|
|
67790
68101
|
case "record":
|
|
67791
68102
|
case "map": {
|
|
67792
|
-
const newKey =
|
|
67793
|
-
const newVal =
|
|
68103
|
+
const newKey = run2(def.keyType);
|
|
68104
|
+
const newVal = run2(def.valueType);
|
|
67794
68105
|
return newKey === def.keyType && newVal === def.valueType ? s : clone(s, { ...def, keyType: newKey, valueType: newVal });
|
|
67795
68106
|
}
|
|
67796
68107
|
case "set": {
|
|
67797
|
-
const newVal =
|
|
68108
|
+
const newVal = run2(def.valueType);
|
|
67798
68109
|
return newVal === def.valueType ? s : clone(s, { ...def, valueType: newVal });
|
|
67799
68110
|
}
|
|
67800
68111
|
case "union": {
|
|
@@ -67802,7 +68113,7 @@ function visit(schema, fnOrHandlers) {
|
|
|
67802
68113
|
let changed = false;
|
|
67803
68114
|
const newOptions = [];
|
|
67804
68115
|
for (const opt of oldOptions) {
|
|
67805
|
-
const mapped =
|
|
68116
|
+
const mapped = run2(opt);
|
|
67806
68117
|
if (mapped !== opt)
|
|
67807
68118
|
changed = true;
|
|
67808
68119
|
newOptions.push(mapped);
|
|
@@ -67810,8 +68121,8 @@ function visit(schema, fnOrHandlers) {
|
|
|
67810
68121
|
return changed ? clone(s, { ...def, options: newOptions }) : s;
|
|
67811
68122
|
}
|
|
67812
68123
|
case "intersection": {
|
|
67813
|
-
const newLeft =
|
|
67814
|
-
const newRight =
|
|
68124
|
+
const newLeft = run2(def.left);
|
|
68125
|
+
const newRight = run2(def.right);
|
|
67815
68126
|
return newLeft === def.left && newRight === def.right ? s : clone(s, { ...def, left: newLeft, right: newRight });
|
|
67816
68127
|
}
|
|
67817
68128
|
case "optional":
|
|
@@ -67823,23 +68134,23 @@ function visit(schema, fnOrHandlers) {
|
|
|
67823
68134
|
case "nonoptional":
|
|
67824
68135
|
case "promise":
|
|
67825
68136
|
case "success": {
|
|
67826
|
-
const newInner =
|
|
68137
|
+
const newInner = run2(def.innerType);
|
|
67827
68138
|
return newInner === def.innerType ? s : clone(s, { ...def, innerType: newInner });
|
|
67828
68139
|
}
|
|
67829
68140
|
case "pipe": {
|
|
67830
|
-
const newIn =
|
|
67831
|
-
const newOut =
|
|
68141
|
+
const newIn = run2(def.in);
|
|
68142
|
+
const newOut = run2(def.out);
|
|
67832
68143
|
return newIn === def.in && newOut === def.out ? s : clone(s, { ...def, in: newIn, out: newOut });
|
|
67833
68144
|
}
|
|
67834
68145
|
case "function": {
|
|
67835
|
-
const newInput =
|
|
67836
|
-
const newOutput =
|
|
68146
|
+
const newInput = run2(def.input);
|
|
68147
|
+
const newOutput = run2(def.output);
|
|
67837
68148
|
return newInput === def.input && newOutput === def.output ? s : clone(s, { ...def, input: newInput, output: newOutput });
|
|
67838
68149
|
}
|
|
67839
68150
|
case "lazy": {
|
|
67840
68151
|
const original = def.getter;
|
|
67841
68152
|
const { _cachedInner, ...rest } = def;
|
|
67842
|
-
return clone(s, { ...rest, getter: () =>
|
|
68153
|
+
return clone(s, { ...rest, getter: () => run2(original()) });
|
|
67843
68154
|
}
|
|
67844
68155
|
case "template_literal":
|
|
67845
68156
|
case "string":
|
|
@@ -67867,7 +68178,7 @@ function visit(schema, fnOrHandlers) {
|
|
|
67867
68178
|
}
|
|
67868
68179
|
}
|
|
67869
68180
|
}
|
|
67870
|
-
return
|
|
68181
|
+
return run2(schema);
|
|
67871
68182
|
}
|
|
67872
68183
|
|
|
67873
68184
|
// node_modules/zod/v4/classic/deep-partial.js
|
|
@@ -72552,6 +72863,16 @@ function formatPathWithTilde(path5) {
|
|
|
72552
72863
|
const home = homedir3();
|
|
72553
72864
|
return path5.startsWith(home) ? path5.replace(home, "~") : path5;
|
|
72554
72865
|
}
|
|
72866
|
+
function promptArg(words) {
|
|
72867
|
+
if (words.length === 0)
|
|
72868
|
+
return;
|
|
72869
|
+
if (words.length > 1 || !/\s/.test(words[0])) {
|
|
72870
|
+
process.stderr.write(`${RED3}Error: "${words.join(" ")}" is not a subcommand, and a prompt must be ONE quoted argument, e.g. tfa-cli "fix the login bug" (or pipe it on stdin). Run tfa-cli --help for subcommands.${RESET2}
|
|
72871
|
+
`);
|
|
72872
|
+
process.exit(2);
|
|
72873
|
+
}
|
|
72874
|
+
return words[0];
|
|
72875
|
+
}
|
|
72555
72876
|
async function interactiveLoop(ws, api2, todoId, projectId, agent2, json2, autoApprove, cfg) {
|
|
72556
72877
|
while (true) {
|
|
72557
72878
|
try {
|
|
@@ -72653,6 +72974,10 @@ Cancelled by user (Ctrl+C)
|
|
|
72653
72974
|
printStatusHelp();
|
|
72654
72975
|
process.exit(0);
|
|
72655
72976
|
}
|
|
72977
|
+
if ((positionals[0] === "show" || positionals[0] === "open") && args.help) {
|
|
72978
|
+
printShowHelp();
|
|
72979
|
+
process.exit(0);
|
|
72980
|
+
}
|
|
72656
72981
|
if (positionals[0] === "agent" && args.help) {
|
|
72657
72982
|
printAgentHelp();
|
|
72658
72983
|
process.exit(0);
|
|
@@ -72724,6 +73049,8 @@ Cancelled by user (Ctrl+C)
|
|
|
72724
73049
|
process.exit(2);
|
|
72725
73050
|
}
|
|
72726
73051
|
}
|
|
73052
|
+
if (positionals[0] === "brand" && positionals[1] === "voice" && await voiceDeviceCommand(positionals.slice(2), args))
|
|
73053
|
+
return;
|
|
72727
73054
|
const deviceLogin = () => runDeviceLogin(apiUrl).catch((e) => {
|
|
72728
73055
|
if (!(e instanceof DeviceLoginError))
|
|
72729
73056
|
throw e;
|
|
@@ -73143,7 +73470,7 @@ Cancelled by user (Ctrl+C)
|
|
|
73143
73470
|
process.exit(1);
|
|
73144
73471
|
}
|
|
73145
73472
|
const promptWords = positionals[0] === "start" ? positionals.slice(2) : positionals;
|
|
73146
|
-
const content2 = promptWords
|
|
73473
|
+
const content2 = (promptArg(promptWords) ?? "").trim();
|
|
73147
73474
|
const todo2 = await api2.startFromSpec(projectId2, templateId, {
|
|
73148
73475
|
...content2 ? { content: content2 } : {},
|
|
73149
73476
|
...groupTag ? { groupTag } : {},
|
|
@@ -73218,7 +73545,7 @@ Resumed: ${CYAN2}${getFrontendUrl(apiUrl, todoId)}${RESET2}
|
|
|
73218
73545
|
const ws2 = new FrontendWebSocket(apiUrl, apiKey);
|
|
73219
73546
|
await ws2.connect();
|
|
73220
73547
|
const autoApprove = !!args["dangerously-skip-permissions"];
|
|
73221
|
-
const followUp = positionals
|
|
73548
|
+
const followUp = promptArg(positionals) ?? (process.stdin.isTTY ? "" : await readStdin());
|
|
73222
73549
|
if (followUp) {
|
|
73223
73550
|
cfg.addToHistory(followUp);
|
|
73224
73551
|
await api2.addMessage(projectId2, followUp, agent3, todoId);
|
|
@@ -73236,11 +73563,7 @@ Resumed: ${CYAN2}${getFrontendUrl(apiUrl, todoId)}${RESET2}
|
|
|
73236
73563
|
`);
|
|
73237
73564
|
process.exit(2);
|
|
73238
73565
|
}
|
|
73239
|
-
|
|
73240
|
-
process.stderr.write(`${RED3}Error: "${positionals[0]}" is not a subcommand, and a prompt needs more than one word (or pipe it on stdin).${RESET2}
|
|
73241
|
-
`);
|
|
73242
|
-
process.exit(2);
|
|
73243
|
-
}
|
|
73566
|
+
const argPrompt = promptArg(positionals);
|
|
73244
73567
|
const ws = args["no-watch"] ? null : new FrontendWebSocket(apiUrl, apiKey);
|
|
73245
73568
|
const wsReady = ws ? ws.connect() : null;
|
|
73246
73569
|
const bridgeReady = !args["no-bridge"] && !args["no-watch"] && !args.isolated ? ensureBridgeRunning2(apiUrl, apiKey) : null;
|
|
@@ -73300,12 +73623,7 @@ Resumed: ${CYAN2}${getFrontendUrl(apiUrl, todoId)}${RESET2}
|
|
|
73300
73623
|
}
|
|
73301
73624
|
process.stderr.write(`${DIM2}Tip: ${randomTip()}${RESET2}
|
|
73302
73625
|
`);
|
|
73303
|
-
|
|
73304
|
-
if (positionals.length > 0) {
|
|
73305
|
-
content = positionals.join(" ");
|
|
73306
|
-
} else {
|
|
73307
|
-
content = await readStdin();
|
|
73308
|
-
}
|
|
73626
|
+
const content = argPrompt ?? await readStdin();
|
|
73309
73627
|
const envProjectId = getEnv("PROJECT_ID");
|
|
73310
73628
|
const hasProject = args.project || envProjectId || cfgScope.data.default_project_id;
|
|
73311
73629
|
const storedAgent = cfgScope.data.default_agent_settings;
|