@todoforai/cli 0.1.47 → 0.1.49
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 +762 -74
- package/package.json +1 -1
package/dist/todoforai-cli.js
CHANGED
|
@@ -42860,6 +42860,75 @@ class ApiClient {
|
|
|
42860
42860
|
deleteTodo(todoId) {
|
|
42861
42861
|
return this.request("DELETE", `/api/v1/todos/${todoId}`);
|
|
42862
42862
|
}
|
|
42863
|
+
restoreTodo(todoId) {
|
|
42864
|
+
return this.request("POST", `/api/v1/todos/${todoId}/restore`, { todoId });
|
|
42865
|
+
}
|
|
42866
|
+
reorderAgentSettings(agentSettingsIds) {
|
|
42867
|
+
return this.request("PUT", "/api/v1/agents/reorder", { agentSettingsIds });
|
|
42868
|
+
}
|
|
42869
|
+
updateTodo(todoId, updates) {
|
|
42870
|
+
return this.request("PUT", `/api/v1/todos/${todoId}`, { todoId, ...updates });
|
|
42871
|
+
}
|
|
42872
|
+
updateScheduledMessage(todoId, messageId, agentSettingsId, updates) {
|
|
42873
|
+
return this.request("PATCH", `/api/v1/todos/${todoId}/messages/${messageId}`, { todoId, messageId, agentSettingsId, ...updates });
|
|
42874
|
+
}
|
|
42875
|
+
setDefaultProject(projectId) {
|
|
42876
|
+
return this.request("POST", "/api/v1/users/default-project", { projectId });
|
|
42877
|
+
}
|
|
42878
|
+
setProjectDefaultAgent(projectId, agentSettingsId) {
|
|
42879
|
+
return this.request("POST", "/api/v1/users/project-agent-mapping", { projectId, agentSettingsId });
|
|
42880
|
+
}
|
|
42881
|
+
renameDevice(id, name) {
|
|
42882
|
+
return this.request("PATCH", `/api/v1/devices/${id}/name`, { id, name });
|
|
42883
|
+
}
|
|
42884
|
+
setDeviceWallpaperMode(id, mode) {
|
|
42885
|
+
return this.request("POST", `/api/v1/devices/${id}/wallpaper-mode`, { id, mode });
|
|
42886
|
+
}
|
|
42887
|
+
listBusinessContexts() {
|
|
42888
|
+
return this.request("GET", "/api/v1/business-context");
|
|
42889
|
+
}
|
|
42890
|
+
createBusinessContext(name) {
|
|
42891
|
+
return this.request("POST", "/api/v1/business-context", { name });
|
|
42892
|
+
}
|
|
42893
|
+
renameBusinessContext(id, name) {
|
|
42894
|
+
return this.request("PUT", `/api/v1/business-context/${id}`, { id, name });
|
|
42895
|
+
}
|
|
42896
|
+
selectBusinessContext(contextId) {
|
|
42897
|
+
return this.request("POST", "/api/v1/business-context/select", { contextId });
|
|
42898
|
+
}
|
|
42899
|
+
listVoiceSources(businessContextId) {
|
|
42900
|
+
return this.request("GET", `/api/v1/business-context/voice-sources?id=${encodeURIComponent(businessContextId)}`);
|
|
42901
|
+
}
|
|
42902
|
+
collectVoiceSource(businessContextId, channel, opts) {
|
|
42903
|
+
return this.request("POST", "/api/v1/business-context/collect-voice-source", { businessContextId, channel, ...opts });
|
|
42904
|
+
}
|
|
42905
|
+
removeVoiceSource(businessContextId, channel) {
|
|
42906
|
+
return this.request("POST", "/api/v1/business-context/remove-voice-source", { businessContextId, channel });
|
|
42907
|
+
}
|
|
42908
|
+
refineBrandVoice(businessContextId, answers, source) {
|
|
42909
|
+
return this.request("POST", "/api/v1/business-context/refine-brand-voice", { businessContextId, answers, ...source && { source } });
|
|
42910
|
+
}
|
|
42911
|
+
getProfile() {
|
|
42912
|
+
return this.request("GET", "/api/v1/users/profile");
|
|
42913
|
+
}
|
|
42914
|
+
getOnboarding() {
|
|
42915
|
+
return this.request("GET", "/api/v1/onboarding");
|
|
42916
|
+
}
|
|
42917
|
+
patchOnboarding(patch) {
|
|
42918
|
+
return this.request("PUT", "/api/v1/onboarding", { patch });
|
|
42919
|
+
}
|
|
42920
|
+
updateProject(projectId, updates) {
|
|
42921
|
+
return this.request("PUT", `/api/v1/projects/${projectId}`, { projectId, ...updates });
|
|
42922
|
+
}
|
|
42923
|
+
listProjectGroups(projectId) {
|
|
42924
|
+
return this.request("GET", `/api/v1/projects/${projectId}/groups`);
|
|
42925
|
+
}
|
|
42926
|
+
upsertProjectGroup(projectId, slug, fields) {
|
|
42927
|
+
return this.request("PUT", `/api/v1/projects/${projectId}/groups/${encodeURIComponent(slug)}`, { projectId, slug, ...fields });
|
|
42928
|
+
}
|
|
42929
|
+
reorderProjectGroups(projectId, slugs) {
|
|
42930
|
+
return this.request("PUT", `/api/v1/projects/${projectId}/groups-order`, { projectId, slugs });
|
|
42931
|
+
}
|
|
42863
42932
|
listModels() {
|
|
42864
42933
|
return this.request("GET", "/api/v1/models");
|
|
42865
42934
|
}
|
|
@@ -43669,6 +43738,9 @@ var SERVER_TO_FRONTENDS = {
|
|
|
43669
43738
|
notification: {
|
|
43670
43739
|
changed: (userId) => `${"notification:changed" /* NOTIFICATION_CHANGED */}:${userId}`
|
|
43671
43740
|
},
|
|
43741
|
+
frontend: {
|
|
43742
|
+
presence: (userId) => `${"frontend:presence" /* FRONTEND_PRESENCE */}:${userId}`
|
|
43743
|
+
},
|
|
43672
43744
|
agent: {
|
|
43673
43745
|
status: (userId) => `${"agent:status" /* AGENT_STATUS */}:${userId}`,
|
|
43674
43746
|
mcpList: () => `${"agent:mcp_list" /* AGENT_MCP_LIST */}:ALL`
|
|
@@ -44334,6 +44406,10 @@ var TOPICS = {
|
|
|
44334
44406
|
channel: (p) => SERVER_TO_FRONTENDS.notification.changed(p.userId),
|
|
44335
44407
|
audience: "frontend"
|
|
44336
44408
|
},
|
|
44409
|
+
["frontend:presence" /* FRONTEND_PRESENCE */]: {
|
|
44410
|
+
channel: (p) => SERVER_TO_FRONTENDS.frontend.presence(p.userId),
|
|
44411
|
+
audience: "frontend"
|
|
44412
|
+
},
|
|
44337
44413
|
["NOTIFY" /* NOTIFY */]: {
|
|
44338
44414
|
channel: (p) => p.userId ? SERVER_TO_FRONTENDS.notify.user(p.userId) : SERVER_TO_FRONTENDS.todo.notify(p.todoId),
|
|
44339
44415
|
audience: "frontend"
|
|
@@ -44450,7 +44526,7 @@ import { parseArgs } from "util";
|
|
|
44450
44526
|
// package.json
|
|
44451
44527
|
var package_default = {
|
|
44452
44528
|
name: "@todoforai/cli",
|
|
44453
|
-
version: "0.1.
|
|
44529
|
+
version: "0.1.49",
|
|
44454
44530
|
type: "module",
|
|
44455
44531
|
bin: {
|
|
44456
44532
|
"todoforai-cli": "bin/todoforai-cli.js",
|
|
@@ -44492,41 +44568,35 @@ function getEnv(name) {
|
|
|
44492
44568
|
}
|
|
44493
44569
|
function printUsage() {
|
|
44494
44570
|
process.stderr.write(`
|
|
44495
|
-
tfa-cli — TODOforAI CLI
|
|
44571
|
+
tfa-cli — TODOforAI CLI
|
|
44496
44572
|
|
|
44497
|
-
Usage:
|
|
44573
|
+
Usage: tfa-cli [OPTIONS] ["prompt"]
|
|
44574
|
+
tfa-cli [OPTIONS] <COMMAND> [ARGS]
|
|
44575
|
+
|
|
44576
|
+
Examples:
|
|
44498
44577
|
tfa-cli login # Browser-based device auth
|
|
44499
|
-
tfa-cli "prompt text" #
|
|
44500
|
-
|
|
44501
|
-
|
|
44502
|
-
tfa-cli
|
|
44503
|
-
tfa-cli
|
|
44504
|
-
tfa-cli
|
|
44505
|
-
tfa-cli
|
|
44506
|
-
tfa-cli
|
|
44507
|
-
tfa-cli
|
|
44508
|
-
tfa-cli
|
|
44509
|
-
tfa-cli
|
|
44510
|
-
tfa-cli
|
|
44511
|
-
tfa-cli
|
|
44512
|
-
tfa-cli status <todo-id> <STATUS> # Update a todo's status (run 'status --help' for the full list)
|
|
44578
|
+
tfa-cli "prompt text" # New todo
|
|
44579
|
+
echo "content" | tfa-cli # From stdin
|
|
44580
|
+
tfa-cli start <id> # Registry template
|
|
44581
|
+
tfa-cli acp # Agent Client Protocol (stdio)
|
|
44582
|
+
tfa-cli agents # List agents
|
|
44583
|
+
tfa-cli models [filter] # Available models
|
|
44584
|
+
tfa-cli agent list|get|update|create … # Agents (update <agent> model=…)
|
|
44585
|
+
tfa-cli todo set <todo-id|-> title=… group=… star=true # Edit a todo's fields
|
|
44586
|
+
tfa-cli project list|set|settings|groups|default|agent … # Projects; edit the current one
|
|
44587
|
+
tfa-cli brand list|create|select|voice … # Brands (business contexts) + voice learning
|
|
44588
|
+
tfa-cli device list|rename|wallpaper … # Paired machines
|
|
44589
|
+
tfa-cli list [-n 30] [--cursor N] [--all] [--status S] # List todos (paginated)
|
|
44590
|
+
tfa-cli status <todo-id> <STATUS> # Update a todo's status
|
|
44513
44591
|
tfa-cli delete <todo-id> # Permanently delete a todo
|
|
44514
|
-
tfa-cli addmessage <todo-id> "text" #
|
|
44515
|
-
tfa-cli show <file|-> [todo-id] #
|
|
44516
|
-
|
|
44517
|
-
|
|
44518
|
-
|
|
44519
|
-
|
|
44520
|
-
|
|
44521
|
-
tfa-cli
|
|
44522
|
-
tfa-cli show list [todo-id] # List show blocks (ref, title, mime/url, card)
|
|
44523
|
-
# [--project <id>] [--card <name>] [--json]
|
|
44524
|
-
# no todo-id + --project (or $TODOFORAI_PROJECT_ID) = every todo
|
|
44525
|
-
tfa-cli open <url> [todo-id] # Show a live http(s) url in the chat as a preview
|
|
44526
|
-
# [--title T] [--alias A] [--json]
|
|
44527
|
-
tfa-cli recommend --template <id> # Add a template as a recommendation card (see 'todoregistry-cli create')
|
|
44528
|
-
tfa-cli claim mint --seed <projectId> [--emails a@x,b@y] [--ttl <sec>] # Mint /claim/<token> ownership links for a project you own
|
|
44529
|
-
tfa-cli next [--direction "<text>"] # Ask the analyzer for growth recommendation cards (optional free-text steer)
|
|
44592
|
+
tfa-cli addmessage <todo-id> "text" # Send a message and exit (like -r, no watch/bridge)
|
|
44593
|
+
tfa-cli show <file|-> [todo-id] # Render a local file (image/pdf/html…) in the chat; - = stdin
|
|
44594
|
+
tfa-cli show list|rm … # List / take down shown files
|
|
44595
|
+
tfa-cli open <url> [todo-id] # Live url preview in the chat
|
|
44596
|
+
tfa-cli recommend --template <id> # Add a template as a recommendation card
|
|
44597
|
+
tfa-cli claim mint --seed <projectId> [--emails a@x,b@y] [--ttl <sec>] # Mint /claim/<token> links for a project you own
|
|
44598
|
+
tfa-cli next [--direction "<text>"] # Analyzer growth recommendation cards
|
|
44599
|
+
tfa-cli <command> help # Details per command
|
|
44530
44600
|
|
|
44531
44601
|
Options:
|
|
44532
44602
|
--path <dir> Workspace path (default: cwd)
|
|
@@ -44534,36 +44604,46 @@ Options:
|
|
|
44534
44604
|
--agent, -a <name> Agent name (partial match)
|
|
44535
44605
|
--group <slug> Group new todo; omitted inherits TODOFORAI_GROUP_ID
|
|
44536
44606
|
--group-name <name> Display name for --group (last write wins)
|
|
44537
|
-
--model <model>
|
|
44538
|
-
|
|
44539
|
-
--
|
|
44540
|
-
--list-models List models usable with --model (optional substring filter) and exit
|
|
44541
|
-
--api-url <url> API URL
|
|
44542
|
-
--api-key <key> API key
|
|
44607
|
+
--model <model> Model for this todo only (see 'models')
|
|
44608
|
+
--api-url <url>
|
|
44609
|
+
--api-key <key>
|
|
44543
44610
|
--user-id <id> Admin HTTP impersonation; requires --no-watch
|
|
44544
|
-
--inspect, -i <todo-id>[@<slice>]
|
|
44545
|
-
--template, -t <id> ["prompt"]
|
|
44546
|
-
--resume, -r
|
|
44547
|
-
--continue, -c
|
|
44548
|
-
--non-interactive, -n Run to completion
|
|
44549
|
-
--dangerously-skip-permissions
|
|
44550
|
-
--allow-all
|
|
44611
|
+
--inspect, -i <todo-id>[@<slice>] Read chat log; slices: -3:, :1, 5:10, 7
|
|
44612
|
+
--template, -t <id> ["prompt"] Same as start; prompt overrides template task
|
|
44613
|
+
--resume, -r <todo-id> ["prompt"] Resume; optional follow-up
|
|
44614
|
+
--continue, -c ["prompt"] Resume last; optional follow-up
|
|
44615
|
+
--non-interactive, -n Run to completion, then exit
|
|
44616
|
+
--dangerously-skip-permissions CLI auto-approves every block
|
|
44617
|
+
--allow-all Agent permissions allow:*:* — nothing ever asks
|
|
44551
44618
|
--raw-sysmsg <file> Use file contents verbatim as system prompt (new TODO only)
|
|
44552
44619
|
--no-watch Create todo and exit
|
|
44553
|
-
--isolated
|
|
44554
|
-
this machine (workspace = --path/cwd); session dies with the CLI
|
|
44620
|
+
--isolated Agent sees ONLY this machine + dir (no cloud VM/other devices); ends with CLI
|
|
44555
44621
|
--no-bridge Do not auto-spawn bridge
|
|
44556
|
-
--
|
|
44557
|
-
--json
|
|
44558
|
-
--detailed 'inspect --json': keep ids, timestamps, agentSettingsId, scheduledTimestamp
|
|
44559
|
-
--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
|
|
44622
|
+
--json
|
|
44623
|
+
--detailed | --format-anthropic inspect --json: keep ids/timestamps | Anthropic messages shape
|
|
44560
44624
|
--safe Validate API key upfront
|
|
44561
|
-
--debug, -d
|
|
44625
|
+
--debug, -d
|
|
44562
44626
|
--debug-dump Attach LLM request debug info per turn (requires server grant)
|
|
44563
|
-
--show-config
|
|
44564
|
-
|
|
44565
|
-
|
|
44566
|
-
|
|
44627
|
+
help | version | config = -h | -v | --show-config (--reset-config wipes it)
|
|
44628
|
+
`);
|
|
44629
|
+
}
|
|
44630
|
+
function printShowHelp() {
|
|
44631
|
+
process.stderr.write(`
|
|
44632
|
+
tfa-cli show — put a file or url into the chat
|
|
44633
|
+
|
|
44634
|
+
Usage:
|
|
44635
|
+
tfa-cli show <file|-> [todo-id] Render a file by mimetype (- reads stdin)
|
|
44636
|
+
[--title T] [--alias A] [--mime M] [--card <name>] [--link] [--json]
|
|
44637
|
+
--link: compact download chip instead of inline render
|
|
44638
|
+
Prints "<todoId>:<alias|id> <public url>". Re-showing with the same
|
|
44639
|
+
--alias updates the block in place; old versions stay at /<id>/<version>
|
|
44640
|
+
tfa-cli show rm <ref|alias> Take it down (block, url, every version)
|
|
44641
|
+
tfa-cli show list [todo-id] List show blocks (ref, title, mime/url, card)
|
|
44642
|
+
[--project <id>] [--card <name>] [--json]
|
|
44643
|
+
no todo-id + --project (or $TODOFORAI_PROJECT_ID) = every todo
|
|
44644
|
+
tfa-cli open <url> [todo-id] Live http(s) url as a preview [--title T] [--alias A] [--json]
|
|
44645
|
+
|
|
44646
|
+
todo-id defaults to $TODOFORAI_TODO_ID (agent shell) or the last todo this CLI touched.
|
|
44567
44647
|
`);
|
|
44568
44648
|
}
|
|
44569
44649
|
var STATUS_HELP = {
|
|
@@ -44587,6 +44667,14 @@ All valid statuses:
|
|
|
44587
44667
|
${Object.values(TodoStatus).join(", ")}
|
|
44588
44668
|
`);
|
|
44589
44669
|
}
|
|
44670
|
+
var WORD_FLAGS = {
|
|
44671
|
+
help: "help",
|
|
44672
|
+
version: "version",
|
|
44673
|
+
models: "list-models",
|
|
44674
|
+
agents: "list-agents",
|
|
44675
|
+
config: "show-config"
|
|
44676
|
+
};
|
|
44677
|
+
var HELP_SUBCOMMANDS = ["agent", "todo", "project", "brand", "device", "list", "ls", "status", "show", "open"];
|
|
44590
44678
|
function parseCliArgs() {
|
|
44591
44679
|
const { values, positionals } = parseArgs({
|
|
44592
44680
|
args: process.argv.slice(2),
|
|
@@ -44614,6 +44702,10 @@ function parseCliArgs() {
|
|
|
44614
44702
|
link: { type: "boolean", default: false },
|
|
44615
44703
|
direction: { type: "string" },
|
|
44616
44704
|
"business-context": { type: "string" },
|
|
44705
|
+
brand: { type: "string" },
|
|
44706
|
+
url: { type: "string" },
|
|
44707
|
+
"pasted-file": { type: "string" },
|
|
44708
|
+
"from-company": { type: "boolean", default: false },
|
|
44617
44709
|
seed: { type: "string" },
|
|
44618
44710
|
emails: { type: "string" },
|
|
44619
44711
|
ttl: { type: "string" },
|
|
@@ -44644,6 +44736,15 @@ function parseCliArgs() {
|
|
|
44644
44736
|
});
|
|
44645
44737
|
if (values["no-edge"])
|
|
44646
44738
|
values["no-bridge"] = true;
|
|
44739
|
+
const asFlag = !values["list-models"] && !values["list-agents"] && !values["show-config"] ? WORD_FLAGS[positionals[0]] : undefined;
|
|
44740
|
+
if (asFlag) {
|
|
44741
|
+
values[asFlag] = true;
|
|
44742
|
+
positionals.shift();
|
|
44743
|
+
}
|
|
44744
|
+
if (positionals[1] === "help" && HELP_SUBCOMMANDS.includes(positionals[0])) {
|
|
44745
|
+
values.help = true;
|
|
44746
|
+
positionals.splice(1, 1);
|
|
44747
|
+
}
|
|
44647
44748
|
return { values, positionals };
|
|
44648
44749
|
}
|
|
44649
44750
|
|
|
@@ -47290,6 +47391,9 @@ Usage:
|
|
|
47290
47391
|
tfa-cli agent list List agents (name, model, id, paths)
|
|
47291
47392
|
tfa-cli agent get <agent> Show a single agent's settings
|
|
47292
47393
|
tfa-cli agent update <agent> <field=value>… Update one or more settings
|
|
47394
|
+
tfa-cli agent create [<field=value>…] Create an agent (defaults, then apply fields)
|
|
47395
|
+
tfa-cli agent global [<field=value>…] Show / update the user's global agent defaults
|
|
47396
|
+
tfa-cli agent reorder <agent>… Set the sidebar order (names or ids)
|
|
47293
47397
|
tfa-cli agent delete <agent> Delete an agent configuration (asks to confirm)
|
|
47294
47398
|
|
|
47295
47399
|
<agent> is a name or id (unique partial name also works).
|
|
@@ -47306,6 +47410,10 @@ Examples:
|
|
|
47306
47410
|
tfa-cli agent update <agent> model=claude
|
|
47307
47411
|
tfa-cli agent update <agent> model=anthropic:anthropic/claude-opus-5 temperature=0.5
|
|
47308
47412
|
tfa-cli agent update <agent> sysmsg="You are a terse video editor."
|
|
47413
|
+
tfa-cli agent create name=Reviewer model=claude sysmsg="Review diffs only."
|
|
47414
|
+
|
|
47415
|
+
From an agent shell (dst_ token) permissions, mcpConfigs, edgesMcpConfigs,
|
|
47416
|
+
devicesConfig and delete are refused by the backend.
|
|
47309
47417
|
`);
|
|
47310
47418
|
}
|
|
47311
47419
|
var FIELD_ALIASES = {
|
|
@@ -47314,7 +47422,7 @@ var FIELD_ALIASES = {
|
|
|
47314
47422
|
temp: "temperature",
|
|
47315
47423
|
thinking: "thinkingLevel"
|
|
47316
47424
|
};
|
|
47317
|
-
function parseAssignment(arg) {
|
|
47425
|
+
function parseAssignment(arg, aliases = {}) {
|
|
47318
47426
|
const eq = arg.indexOf("=");
|
|
47319
47427
|
if (eq < 1) {
|
|
47320
47428
|
process.stderr.write(`${RED}Invalid field assignment '${arg}', expected field=value${RESET}
|
|
@@ -47322,7 +47430,7 @@ function parseAssignment(arg) {
|
|
|
47322
47430
|
process.exit(2);
|
|
47323
47431
|
}
|
|
47324
47432
|
const rawKey = arg.slice(0, eq);
|
|
47325
|
-
const key =
|
|
47433
|
+
const key = aliases[rawKey] || rawKey;
|
|
47326
47434
|
const raw = arg.slice(eq + 1);
|
|
47327
47435
|
try {
|
|
47328
47436
|
return [key, JSON.parse(raw)];
|
|
@@ -47330,6 +47438,9 @@ function parseAssignment(arg) {
|
|
|
47330
47438
|
return [key, raw];
|
|
47331
47439
|
}
|
|
47332
47440
|
}
|
|
47441
|
+
function parseAssignments(args, aliases = {}) {
|
|
47442
|
+
return Object.fromEntries(args.map((a) => parseAssignment(a, aliases)));
|
|
47443
|
+
}
|
|
47333
47444
|
function resolveAgent(agents, query) {
|
|
47334
47445
|
const { match, ambiguous } = resolveAgentMatch2(agents, query);
|
|
47335
47446
|
if (match)
|
|
@@ -47377,7 +47488,7 @@ async function agentCommand(api, positionals, args, formatPath) {
|
|
|
47377
47488
|
`);
|
|
47378
47489
|
process.exit(2);
|
|
47379
47490
|
}
|
|
47380
|
-
const updates =
|
|
47491
|
+
const updates = parseAssignments(assignments, FIELD_ALIASES);
|
|
47381
47492
|
const agents = await api.listAgentSettings();
|
|
47382
47493
|
const agent = resolveAgent(agents, query);
|
|
47383
47494
|
const id = getItemId2(agent);
|
|
@@ -47388,6 +47499,45 @@ async function agentCommand(api, positionals, args, formatPath) {
|
|
|
47388
47499
|
}
|
|
47389
47500
|
const summary = Object.keys(updates).map((k) => `${k}=${JSON.stringify(updated[k])}`).join(" ");
|
|
47390
47501
|
process.stderr.write(`${GREEN}✅ ${getDisplayName2(agent)} updated: ${summary}${RESET}
|
|
47502
|
+
`);
|
|
47503
|
+
return;
|
|
47504
|
+
}
|
|
47505
|
+
if (sub === "global") {
|
|
47506
|
+
const updates = parseAssignments(positionals.slice(2), FIELD_ALIASES);
|
|
47507
|
+
const global = Object.keys(updates).length ? await api.updateGlobalAgentSettings(updates) : await api.getGlobalAgentSettings();
|
|
47508
|
+
if (args.json) {
|
|
47509
|
+
console.log(JSON.stringify(global, null, 2));
|
|
47510
|
+
return;
|
|
47511
|
+
}
|
|
47512
|
+
for (const [k, v] of Object.entries(global))
|
|
47513
|
+
process.stderr.write(` ${DIM}${k}:${RESET} ${typeof v === "string" ? v : JSON.stringify(v)}
|
|
47514
|
+
`);
|
|
47515
|
+
return;
|
|
47516
|
+
}
|
|
47517
|
+
if (sub === "reorder") {
|
|
47518
|
+
const queries = positionals.slice(2);
|
|
47519
|
+
if (!queries.length) {
|
|
47520
|
+
process.stderr.write(`${RED}Usage: tfa-cli agent reorder <agent>…${RESET}
|
|
47521
|
+
`);
|
|
47522
|
+
process.exit(2);
|
|
47523
|
+
}
|
|
47524
|
+
const agents = await api.listAgentSettings();
|
|
47525
|
+
const ids = queries.map((q) => getItemId2(resolveAgent(agents, q)));
|
|
47526
|
+
await api.reorderAgentSettings(ids);
|
|
47527
|
+
process.stderr.write(`${GREEN}✅ Agents reordered${RESET}
|
|
47528
|
+
`);
|
|
47529
|
+
return;
|
|
47530
|
+
}
|
|
47531
|
+
if (sub === "create") {
|
|
47532
|
+
const updates = parseAssignments(positionals.slice(2), FIELD_ALIASES);
|
|
47533
|
+
const created = await api.createAgent();
|
|
47534
|
+
const id = getItemId2(created);
|
|
47535
|
+
const agent = Object.keys(updates).length ? await api.updateAgentSettings(id, id, updates) : created;
|
|
47536
|
+
if (args.json) {
|
|
47537
|
+
console.log(JSON.stringify(agent, null, 2));
|
|
47538
|
+
return;
|
|
47539
|
+
}
|
|
47540
|
+
process.stderr.write(`${GREEN}✅ Created agent ${getDisplayName2(agent)}${RESET} ${DIM}${id}${RESET}
|
|
47391
47541
|
`);
|
|
47392
47542
|
return;
|
|
47393
47543
|
}
|
|
@@ -47423,6 +47573,507 @@ async function agentCommand(api, positionals, args, formatPath) {
|
|
|
47423
47573
|
process.exit(2);
|
|
47424
47574
|
}
|
|
47425
47575
|
|
|
47576
|
+
// src/agent-command.ts
|
|
47577
|
+
function parseAssignment2(arg, aliases = {}) {
|
|
47578
|
+
const eq = arg.indexOf("=");
|
|
47579
|
+
if (eq < 1) {
|
|
47580
|
+
process.stderr.write(`${RED}Invalid field assignment '${arg}', expected field=value${RESET}
|
|
47581
|
+
`);
|
|
47582
|
+
process.exit(2);
|
|
47583
|
+
}
|
|
47584
|
+
const rawKey = arg.slice(0, eq);
|
|
47585
|
+
const key = aliases[rawKey] || rawKey;
|
|
47586
|
+
const raw = arg.slice(eq + 1);
|
|
47587
|
+
try {
|
|
47588
|
+
return [key, JSON.parse(raw)];
|
|
47589
|
+
} catch {
|
|
47590
|
+
return [key, raw];
|
|
47591
|
+
}
|
|
47592
|
+
}
|
|
47593
|
+
function parseAssignments2(args, aliases = {}) {
|
|
47594
|
+
return Object.fromEntries(args.map((a) => parseAssignment2(a, aliases)));
|
|
47595
|
+
}
|
|
47596
|
+
function resolveAgent2(agents, query) {
|
|
47597
|
+
const { match, ambiguous } = resolveAgentMatch2(agents, query);
|
|
47598
|
+
if (match)
|
|
47599
|
+
return match;
|
|
47600
|
+
if (ambiguous) {
|
|
47601
|
+
process.stderr.write(`${RED}Ambiguous agent '${query}' — ${ambiguous.length} matches. Re-run with the exact id:${RESET}
|
|
47602
|
+
`);
|
|
47603
|
+
for (const a of ambiguous)
|
|
47604
|
+
process.stderr.write(` ${getDisplayName2(a)} ${DIM}${getItemId2(a)}${RESET}
|
|
47605
|
+
`);
|
|
47606
|
+
process.exit(2);
|
|
47607
|
+
}
|
|
47608
|
+
process.stderr.write(`${RED}No agent matching '${query}'${RESET}
|
|
47609
|
+
`);
|
|
47610
|
+
process.exit(2);
|
|
47611
|
+
}
|
|
47612
|
+
|
|
47613
|
+
// src/args.ts
|
|
47614
|
+
var VERSION2 = package_default.version;
|
|
47615
|
+
function getEnv2(name) {
|
|
47616
|
+
return (process.env[`TODOFORAI_${name}`] || process.env[`TODO4AI_${name}`] || "").trim();
|
|
47617
|
+
}
|
|
47618
|
+
var STATUS_HELP2 = {
|
|
47619
|
+
["READY" /* READY */]: "AI finished the work on the TODO",
|
|
47620
|
+
["READY_CHECKED" /* READY_CHECKED */]: "AI finished and the user reviewed it",
|
|
47621
|
+
["DONE" /* DONE */]: "Completed and finalized",
|
|
47622
|
+
["REVIEW_REQUESTED" /* REVIEW_REQUESTED */]: "Asks the user to review",
|
|
47623
|
+
["POSTPONED" /* POSTPONED */]: "Put off for later",
|
|
47624
|
+
["ARCHIVED" /* ARCHIVED */]: "Archived (hidden from active list)",
|
|
47625
|
+
["DELETED" /* DELETED */]: "Marked for deletion"
|
|
47626
|
+
};
|
|
47627
|
+
|
|
47628
|
+
// src/manage-command.ts
|
|
47629
|
+
function printTodoHelp() {
|
|
47630
|
+
process.stderr.write(`
|
|
47631
|
+
tfa-cli todo — edit a todo's fields
|
|
47632
|
+
|
|
47633
|
+
Usage:
|
|
47634
|
+
tfa-cli todo set <todo-id|-> <field=value>… Update fields ("-" = $TODOFORAI_TODO_ID)
|
|
47635
|
+
tfa-cli todo restore <todo-id> Bring a trashed todo back (undo of 'delete')
|
|
47636
|
+
|
|
47637
|
+
Fields (PUT /todos/{id}):
|
|
47638
|
+
title first-line content (alias: content)
|
|
47639
|
+
group group slug; "" makes it ungrouped (alias: groupTag)
|
|
47640
|
+
star true|false (starredAt now / 0)
|
|
47641
|
+
schedule unix ms timestamp, 0 = immediate (alias: scheduledTimestamp)
|
|
47642
|
+
agent agentSettingsId (alias: agentSettingsId)
|
|
47643
|
+
brand businessContextId (alias: businessContextId)
|
|
47644
|
+
public true|false — refused from an agent shell (isPublic)
|
|
47645
|
+
|
|
47646
|
+
tfa-cli todo message <todo-id|-> <message-id> <field=value>…
|
|
47647
|
+
Edit a SCHEDULED (not yet run) message: content, schedule (unix ms;
|
|
47648
|
+
0 = run now). Runs with the todo's own agent.
|
|
47649
|
+
|
|
47650
|
+
Examples:
|
|
47651
|
+
tfa-cli todo set - title="Ship v2 landing" group=marketing
|
|
47652
|
+
tfa-cli todo set 3f2a91 star=true
|
|
47653
|
+
tfa-cli todo message - 8c1d… schedule=1760000000000
|
|
47654
|
+
tfa-cli status <todo-id> DONE (status has its own command)
|
|
47655
|
+
`);
|
|
47656
|
+
}
|
|
47657
|
+
function printProjectHelp() {
|
|
47658
|
+
process.stderr.write(`
|
|
47659
|
+
tfa-cli project — edit the current project
|
|
47660
|
+
|
|
47661
|
+
--project <id> or $TODOFORAI_PROJECT_ID selects the project.
|
|
47662
|
+
|
|
47663
|
+
Usage:
|
|
47664
|
+
tfa-cli project list Projects you can access (* = default)
|
|
47665
|
+
tfa-cli project set <field=value>… name=… description=… brand=<businessContextId>
|
|
47666
|
+
(isPublic refused from an agent shell)
|
|
47667
|
+
tfa-cli project default Make it the project you land on at /
|
|
47668
|
+
tfa-cli project agent <agent> Default agent for new todos in this project
|
|
47669
|
+
tfa-cli project settings <field=value>… Project settings (PUT /settings/projects/{id})
|
|
47670
|
+
tfa-cli project groups List todo groups
|
|
47671
|
+
tfa-cli project groups set <slug> <field=value>… Create/update a group: name description pinned archived order
|
|
47672
|
+
tfa-cli project groups reorder <slug>… Set wall order
|
|
47673
|
+
|
|
47674
|
+
Examples:
|
|
47675
|
+
tfa-cli project set name="Q4 launch"
|
|
47676
|
+
tfa-cli project groups set marketing name=Marketing description="Top-of-funnel work"
|
|
47677
|
+
tfa-cli project groups reorder marketing sales ops
|
|
47678
|
+
`);
|
|
47679
|
+
}
|
|
47680
|
+
var TODO_ALIASES = {
|
|
47681
|
+
title: "content",
|
|
47682
|
+
group: "groupTag",
|
|
47683
|
+
schedule: "scheduledTimestamp",
|
|
47684
|
+
agent: "agentSettingsId",
|
|
47685
|
+
brand: "businessContextId",
|
|
47686
|
+
public: "isPublic"
|
|
47687
|
+
};
|
|
47688
|
+
var PROJECT_ALIASES = { brand: "businessContextId" };
|
|
47689
|
+
function fail(msg) {
|
|
47690
|
+
process.stderr.write(`${RED}${msg}${RESET}
|
|
47691
|
+
`);
|
|
47692
|
+
process.exit(2);
|
|
47693
|
+
}
|
|
47694
|
+
async function todoCommand(api, positionals, args) {
|
|
47695
|
+
const [, sub, idArg, ...rest] = positionals;
|
|
47696
|
+
if (sub === "restore") {
|
|
47697
|
+
if (!idArg)
|
|
47698
|
+
fail("Usage: tfa-cli todo restore <todo-id>");
|
|
47699
|
+
await api.restoreTodo(idArg);
|
|
47700
|
+
process.stderr.write(`${GREEN}✅ Restored ${idArg}${RESET}
|
|
47701
|
+
`);
|
|
47702
|
+
return;
|
|
47703
|
+
}
|
|
47704
|
+
const todoId = !idArg || idArg === "-" ? getEnv2("TODO_ID") : idArg;
|
|
47705
|
+
if (sub === "message") {
|
|
47706
|
+
const [messageId, ...fields] = rest;
|
|
47707
|
+
if (!todoId || !messageId || !fields.length)
|
|
47708
|
+
fail("Usage: tfa-cli todo message <todo-id|-> <message-id> <field=value>…");
|
|
47709
|
+
const updates2 = parseAssignments2(fields, { schedule: "scheduledTimestamp" });
|
|
47710
|
+
const agentSettingsId = (await api.getTodo(todoId))?.agentSettingsId;
|
|
47711
|
+
if (!agentSettingsId)
|
|
47712
|
+
fail("Todo has no agent assigned");
|
|
47713
|
+
await api.updateScheduledMessage(todoId, messageId, agentSettingsId, updates2);
|
|
47714
|
+
process.stderr.write(`${GREEN}✅ message ${messageId} updated: ${Object.keys(updates2).join(", ")}${RESET}
|
|
47715
|
+
`);
|
|
47716
|
+
return;
|
|
47717
|
+
}
|
|
47718
|
+
if (sub !== "set") {
|
|
47719
|
+
printTodoHelp();
|
|
47720
|
+
process.exit(sub ? 2 : 0);
|
|
47721
|
+
}
|
|
47722
|
+
if (!todoId || !rest.length)
|
|
47723
|
+
fail("Usage: tfa-cli todo set <todo-id|-> <field=value>…");
|
|
47724
|
+
const updates = parseAssignments2(rest, TODO_ALIASES);
|
|
47725
|
+
if ("star" in updates) {
|
|
47726
|
+
if (typeof updates.star !== "boolean")
|
|
47727
|
+
fail("star must be true or false");
|
|
47728
|
+
updates.starredAt = updates.star ? Date.now() : 0;
|
|
47729
|
+
delete updates.star;
|
|
47730
|
+
}
|
|
47731
|
+
await api.updateTodo(todoId, updates);
|
|
47732
|
+
if (args.json) {
|
|
47733
|
+
console.log(JSON.stringify({ todoId, ...updates }));
|
|
47734
|
+
return;
|
|
47735
|
+
}
|
|
47736
|
+
process.stderr.write(`${GREEN}✅ ${todoId} updated: ${Object.keys(updates).join(", ")}${RESET}
|
|
47737
|
+
`);
|
|
47738
|
+
}
|
|
47739
|
+
async function projectCommand(api, positionals, args) {
|
|
47740
|
+
const [, sub, ...rest] = positionals;
|
|
47741
|
+
const projectId = args.project || getEnv2("PROJECT_ID");
|
|
47742
|
+
if (!sub) {
|
|
47743
|
+
printProjectHelp();
|
|
47744
|
+
process.exit(0);
|
|
47745
|
+
}
|
|
47746
|
+
if (sub === "list" || sub === "ls") {
|
|
47747
|
+
const projects = await api.listProjects();
|
|
47748
|
+
if (args.json) {
|
|
47749
|
+
console.log(JSON.stringify(projects, null, 2));
|
|
47750
|
+
return;
|
|
47751
|
+
}
|
|
47752
|
+
for (const p of projects) {
|
|
47753
|
+
const pr = p.project ?? p;
|
|
47754
|
+
process.stderr.write(`${pr.isDefault ? "*" : " "} ${pr.name ?? ""} ${DIM}${pr.id}${RESET}
|
|
47755
|
+
`);
|
|
47756
|
+
}
|
|
47757
|
+
return;
|
|
47758
|
+
}
|
|
47759
|
+
if (!projectId)
|
|
47760
|
+
fail("No project — pass --project <id> or set TODOFORAI_PROJECT_ID");
|
|
47761
|
+
if (sub === "set") {
|
|
47762
|
+
if (!rest.length)
|
|
47763
|
+
fail("Usage: tfa-cli project set <field=value>…");
|
|
47764
|
+
const updates = parseAssignments2(rest, PROJECT_ALIASES);
|
|
47765
|
+
await api.updateProject(projectId, updates);
|
|
47766
|
+
process.stderr.write(`${GREEN}✅ project updated: ${Object.keys(updates).join(", ")}${RESET}
|
|
47767
|
+
`);
|
|
47768
|
+
return;
|
|
47769
|
+
}
|
|
47770
|
+
if (sub === "default") {
|
|
47771
|
+
await api.setDefaultProject(projectId);
|
|
47772
|
+
process.stderr.write(`${GREEN}✅ ${projectId} is now the default project${RESET}
|
|
47773
|
+
`);
|
|
47774
|
+
return;
|
|
47775
|
+
}
|
|
47776
|
+
if (sub === "agent") {
|
|
47777
|
+
if (!rest[0])
|
|
47778
|
+
fail("Usage: tfa-cli project agent <agent>");
|
|
47779
|
+
const agent = resolveAgent2(await api.listAgentSettings(), rest[0]);
|
|
47780
|
+
await api.setProjectDefaultAgent(projectId, getItemId2(agent));
|
|
47781
|
+
process.stderr.write(`${GREEN}✅ default agent for ${projectId}: ${getDisplayName2(agent)}${RESET}
|
|
47782
|
+
`);
|
|
47783
|
+
return;
|
|
47784
|
+
}
|
|
47785
|
+
if (sub === "settings") {
|
|
47786
|
+
if (!rest.length)
|
|
47787
|
+
fail("Usage: tfa-cli project settings <field=value>…");
|
|
47788
|
+
const settings = parseAssignments2(rest);
|
|
47789
|
+
await api.updateProjectSettings(projectId, settings);
|
|
47790
|
+
process.stderr.write(`${GREEN}✅ project settings updated: ${Object.keys(settings).join(", ")}${RESET}
|
|
47791
|
+
`);
|
|
47792
|
+
return;
|
|
47793
|
+
}
|
|
47794
|
+
if (sub === "groups") {
|
|
47795
|
+
const [verb, ...gargs] = rest;
|
|
47796
|
+
if (!verb) {
|
|
47797
|
+
const groups = await api.listProjectGroups(projectId);
|
|
47798
|
+
if (args.json) {
|
|
47799
|
+
console.log(JSON.stringify(groups, null, 2));
|
|
47800
|
+
return;
|
|
47801
|
+
}
|
|
47802
|
+
for (const g of groups)
|
|
47803
|
+
process.stderr.write(`${getItemId2(g) || g.slug} ${g.name ?? ""} ${DIM}${g.description ?? ""}${RESET}
|
|
47804
|
+
`);
|
|
47805
|
+
return;
|
|
47806
|
+
}
|
|
47807
|
+
if (verb === "set") {
|
|
47808
|
+
const [slug, ...fields] = gargs;
|
|
47809
|
+
if (!slug)
|
|
47810
|
+
fail("Usage: tfa-cli project groups set <slug> <field=value>…");
|
|
47811
|
+
const group = await api.upsertProjectGroup(projectId, slug, parseAssignments2(fields));
|
|
47812
|
+
if (args.json) {
|
|
47813
|
+
console.log(JSON.stringify(group, null, 2));
|
|
47814
|
+
return;
|
|
47815
|
+
}
|
|
47816
|
+
process.stderr.write(`${GREEN}✅ group ${slug} saved${RESET}
|
|
47817
|
+
`);
|
|
47818
|
+
return;
|
|
47819
|
+
}
|
|
47820
|
+
if (verb === "reorder") {
|
|
47821
|
+
if (!gargs.length)
|
|
47822
|
+
fail("Usage: tfa-cli project groups reorder <slug>…");
|
|
47823
|
+
await api.reorderProjectGroups(projectId, gargs);
|
|
47824
|
+
process.stderr.write(`${GREEN}✅ groups reordered${RESET}
|
|
47825
|
+
`);
|
|
47826
|
+
return;
|
|
47827
|
+
}
|
|
47828
|
+
fail(`Unknown 'project groups' verb: ${verb}`);
|
|
47829
|
+
}
|
|
47830
|
+
fail(`Unknown 'project' subcommand: ${sub}`);
|
|
47831
|
+
}
|
|
47832
|
+
function printBrandHelp() {
|
|
47833
|
+
process.stderr.write(`
|
|
47834
|
+
tfa-cli brand — business contexts (brands) and the learned writing voice
|
|
47835
|
+
|
|
47836
|
+
The brand page text itself is a file: read/write todoforai:business-context.
|
|
47837
|
+
These commands manage the contexts and the voice learned for them.
|
|
47838
|
+
|
|
47839
|
+
Usage:
|
|
47840
|
+
tfa-cli brand list Contexts (* = selected)
|
|
47841
|
+
tfa-cli brand create <name> New brand (empty .md page)
|
|
47842
|
+
tfa-cli brand rename <brand> <name>
|
|
47843
|
+
tfa-cli brand select <brand|none> Active brand for the account
|
|
47844
|
+
tfa-cli brand voice Learned profile + sources
|
|
47845
|
+
tfa-cli brand voice answers [<q>=<a>…] Show / set the brand-voice answers (strings)
|
|
47846
|
+
tfa-cli brand voice collect <channel> [--url U | --pasted-file <F|->]
|
|
47847
|
+
Add a writing sample source
|
|
47848
|
+
channels: X LinkedIn Facebook Instagram
|
|
47849
|
+
Gmail Outlook Slack Teams
|
|
47850
|
+
tfa-cli brand voice remove <channel>
|
|
47851
|
+
tfa-cli brand voice learn [--from-company] Run the refinement loop, store the profile
|
|
47852
|
+
|
|
47853
|
+
<brand> is an id or name (unique partial works). Voice subcommands use the
|
|
47854
|
+
selected brand unless --brand <id|name> is given (--pasted-file - reads stdin).
|
|
47855
|
+
Delete / reset are UI-only.
|
|
47856
|
+
|
|
47857
|
+
Questions the UI asks (keys for 'answers'):
|
|
47858
|
+
"What makes you different from competitors?"
|
|
47859
|
+
"What are you working on right now?"
|
|
47860
|
+
"A frustrated customer says things aren't working. How do you reply?"
|
|
47861
|
+
`);
|
|
47862
|
+
}
|
|
47863
|
+
async function resolveBrand(api, query, selectedId) {
|
|
47864
|
+
const contexts = await api.listBusinessContexts();
|
|
47865
|
+
if (!query) {
|
|
47866
|
+
const sel = contexts.find((c3) => c3.id === selectedId);
|
|
47867
|
+
if (sel)
|
|
47868
|
+
return sel;
|
|
47869
|
+
if (contexts.length === 1)
|
|
47870
|
+
return contexts[0];
|
|
47871
|
+
fail("No brand selected — pass --brand <id|name> or 'tfa-cli brand select'");
|
|
47872
|
+
}
|
|
47873
|
+
const q = query.toLowerCase();
|
|
47874
|
+
const exact = contexts.find((c3) => c3.id === query || c3.name.toLowerCase() === q);
|
|
47875
|
+
if (exact)
|
|
47876
|
+
return exact;
|
|
47877
|
+
const partial = contexts.filter((c3) => c3.id.startsWith(query) || c3.name.toLowerCase().includes(q));
|
|
47878
|
+
if (partial.length === 1)
|
|
47879
|
+
return partial[0];
|
|
47880
|
+
fail(partial.length ? `Ambiguous brand '${query}'` : `No brand matching '${query}'`);
|
|
47881
|
+
}
|
|
47882
|
+
async function brandCommand(api, positionals, args) {
|
|
47883
|
+
const [, sub, ...rest] = positionals;
|
|
47884
|
+
if (!sub || sub === "list") {
|
|
47885
|
+
const contexts = await api.listBusinessContexts();
|
|
47886
|
+
const selected = (await api.getProfile()).user?.selectedBusinessContextId;
|
|
47887
|
+
if (args.json) {
|
|
47888
|
+
console.log(JSON.stringify(contexts.map((c3) => ({ ...c3, selected: c3.id === selected })), null, 2));
|
|
47889
|
+
return;
|
|
47890
|
+
}
|
|
47891
|
+
for (const c3 of contexts)
|
|
47892
|
+
process.stderr.write(`${c3.id === selected ? "*" : " "} ${c3.name} ${DIM}${c3.id}${RESET}
|
|
47893
|
+
`);
|
|
47894
|
+
return;
|
|
47895
|
+
}
|
|
47896
|
+
if (sub === "create") {
|
|
47897
|
+
if (!rest[0])
|
|
47898
|
+
fail("Usage: tfa-cli brand create <name>");
|
|
47899
|
+
const c3 = await api.createBusinessContext(rest[0]);
|
|
47900
|
+
if (args.json) {
|
|
47901
|
+
console.log(JSON.stringify(c3, null, 2));
|
|
47902
|
+
return;
|
|
47903
|
+
}
|
|
47904
|
+
process.stderr.write(`${GREEN}✅ brand ${c3.name} created ${DIM}${c3.id}${RESET}
|
|
47905
|
+
`);
|
|
47906
|
+
return;
|
|
47907
|
+
}
|
|
47908
|
+
if (sub === "rename") {
|
|
47909
|
+
if (!rest[1])
|
|
47910
|
+
fail("Usage: tfa-cli brand rename <brand> <name>");
|
|
47911
|
+
const c3 = await resolveBrand(api, rest[0]);
|
|
47912
|
+
await api.renameBusinessContext(c3.id, rest[1]);
|
|
47913
|
+
process.stderr.write(`${GREEN}✅ renamed to ${rest[1]}${RESET}
|
|
47914
|
+
`);
|
|
47915
|
+
return;
|
|
47916
|
+
}
|
|
47917
|
+
if (sub === "select") {
|
|
47918
|
+
if (!rest[0])
|
|
47919
|
+
fail("Usage: tfa-cli brand select <brand|none>");
|
|
47920
|
+
const id = rest[0] === "none" ? null : (await resolveBrand(api, rest[0])).id;
|
|
47921
|
+
await api.selectBusinessContext(id);
|
|
47922
|
+
process.stderr.write(`${GREEN}✅ selected brand: ${id ?? "none"}${RESET}
|
|
47923
|
+
`);
|
|
47924
|
+
return;
|
|
47925
|
+
}
|
|
47926
|
+
if (sub === "voice")
|
|
47927
|
+
return voiceCommand(api, rest, args);
|
|
47928
|
+
fail(`Unknown 'brand' subcommand: ${sub}`);
|
|
47929
|
+
}
|
|
47930
|
+
async function voiceCommand(api, rest, args) {
|
|
47931
|
+
const [verb, ...vargs] = rest;
|
|
47932
|
+
const onboarding = await api.getOnboarding();
|
|
47933
|
+
if (verb === "answers") {
|
|
47934
|
+
if (vargs.length) {
|
|
47935
|
+
const given = Object.fromEntries(vargs.map((a) => {
|
|
47936
|
+
const i = a.indexOf("=");
|
|
47937
|
+
if (i < 1)
|
|
47938
|
+
fail(`Expected question=answer, got '${a}'`);
|
|
47939
|
+
return [a.slice(0, i), a.slice(i + 1)];
|
|
47940
|
+
}));
|
|
47941
|
+
const styleAnswers = { ...onboarding.styleAnswers ?? {}, ...given };
|
|
47942
|
+
await api.patchOnboarding({ styleAnswers });
|
|
47943
|
+
process.stderr.write(`${GREEN}✅ ${vargs.length} answer(s) saved${RESET}
|
|
47944
|
+
`);
|
|
47945
|
+
return;
|
|
47946
|
+
}
|
|
47947
|
+
const answers = onboarding.styleAnswers ?? {};
|
|
47948
|
+
if (args.json) {
|
|
47949
|
+
console.log(JSON.stringify(answers, null, 2));
|
|
47950
|
+
return;
|
|
47951
|
+
}
|
|
47952
|
+
for (const [q, a] of Object.entries(answers))
|
|
47953
|
+
process.stderr.write(`${q}
|
|
47954
|
+
${DIM}${a || "(empty)"}${RESET}
|
|
47955
|
+
`);
|
|
47956
|
+
return;
|
|
47957
|
+
}
|
|
47958
|
+
const brand = await resolveBrand(api, args.brand, (await api.getProfile()).user?.selectedBusinessContextId);
|
|
47959
|
+
if (!verb || verb === "show") {
|
|
47960
|
+
const profile = onboarding.voiceProfiles?.[brand.id];
|
|
47961
|
+
const { sources } = await api.listVoiceSources(brand.id);
|
|
47962
|
+
if (args.json) {
|
|
47963
|
+
console.log(JSON.stringify({ brand, profile, sources }, null, 2));
|
|
47964
|
+
return;
|
|
47965
|
+
}
|
|
47966
|
+
process.stderr.write(`${brand.name} ${DIM}${brand.id}${RESET}
|
|
47967
|
+
`);
|
|
47968
|
+
process.stderr.write(profile ? `
|
|
47969
|
+
${profile.profile}
|
|
47970
|
+
${DIM}match ${profile.match}/100 · source ${profile.source}${RESET}
|
|
47971
|
+
` : `${DIM}(no voice learned yet)${RESET}
|
|
47972
|
+
`);
|
|
47973
|
+
for (const s of sources)
|
|
47974
|
+
process.stderr.write(` ${s.channel} ${DIM}${s.posts ?? "?"} posts · ${s.chars ?? "?"} chars${RESET}
|
|
47975
|
+
`);
|
|
47976
|
+
return;
|
|
47977
|
+
}
|
|
47978
|
+
if (verb === "collect") {
|
|
47979
|
+
const channel = vargs[0];
|
|
47980
|
+
if (!channel)
|
|
47981
|
+
fail("Usage: tfa-cli brand voice collect <channel> [--url U | --pasted-file F|-]");
|
|
47982
|
+
let pasted;
|
|
47983
|
+
if (args["pasted-file"]) {
|
|
47984
|
+
const { readFileSync: readFileSync3 } = await import("node:fs");
|
|
47985
|
+
pasted = readFileSync3(args["pasted-file"] === "-" ? 0 : args["pasted-file"], "utf8");
|
|
47986
|
+
}
|
|
47987
|
+
const { sources } = await api.collectVoiceSource(brand.id, channel, { url: args.url, pasted });
|
|
47988
|
+
process.stderr.write(`${GREEN}✅ ${channel} collected (${sources.length} source(s))${RESET}
|
|
47989
|
+
`);
|
|
47990
|
+
return;
|
|
47991
|
+
}
|
|
47992
|
+
if (verb === "remove") {
|
|
47993
|
+
if (!vargs[0])
|
|
47994
|
+
fail("Usage: tfa-cli brand voice remove <channel>");
|
|
47995
|
+
await api.removeVoiceSource(brand.id, vargs[0]);
|
|
47996
|
+
process.stderr.write(`${GREEN}✅ ${vargs[0]} removed${RESET}
|
|
47997
|
+
`);
|
|
47998
|
+
return;
|
|
47999
|
+
}
|
|
48000
|
+
if (verb === "learn") {
|
|
48001
|
+
process.stderr.write(`${DIM}learning voice for ${brand.name}…${RESET}
|
|
48002
|
+
`);
|
|
48003
|
+
const res = await api.refineBrandVoice(brand.id, onboarding.styleAnswers ?? {}, args["from-company"] ? "company" : undefined);
|
|
48004
|
+
if (!res.profile)
|
|
48005
|
+
fail("Nothing to learn from — add answers or collect a source first");
|
|
48006
|
+
const { [brand.id]: _stale, ...styleAiAnswers } = onboarding.styleAiAnswers ?? {};
|
|
48007
|
+
await api.patchOnboarding({
|
|
48008
|
+
voiceProfiles: { ...onboarding.voiceProfiles ?? {}, [brand.id]: { ...res, updatedAt: Date.now() } },
|
|
48009
|
+
styleAiAnswers
|
|
48010
|
+
});
|
|
48011
|
+
if (args.json) {
|
|
48012
|
+
console.log(JSON.stringify(res, null, 2));
|
|
48013
|
+
return;
|
|
48014
|
+
}
|
|
48015
|
+
process.stderr.write(`${GREEN}✅ voice learned (match ${res.match}/100, source ${res.source})${RESET}
|
|
48016
|
+
${res.profile}
|
|
48017
|
+
`);
|
|
48018
|
+
return;
|
|
48019
|
+
}
|
|
48020
|
+
fail(`Unknown 'brand voice' verb: ${verb}`);
|
|
48021
|
+
}
|
|
48022
|
+
function printDeviceHelp() {
|
|
48023
|
+
process.stderr.write(`
|
|
48024
|
+
tfa-cli device — the user's paired machines
|
|
48025
|
+
|
|
48026
|
+
Usage:
|
|
48027
|
+
tfa-cli device list
|
|
48028
|
+
tfa-cli device rename <device> <name> name: [a-zA-Z0-9][a-zA-Z0-9_]*, ≤64
|
|
48029
|
+
tfa-cli device wallpaper <device> auto|off desktop-wallpaper capture (off deletes stored images)
|
|
48030
|
+
|
|
48031
|
+
<device> is an id, name or hostname (unique partial works).
|
|
48032
|
+
exec / reboot / update / unpair / workspace paths are UI-only from an agent shell.
|
|
48033
|
+
`);
|
|
48034
|
+
}
|
|
48035
|
+
async function deviceCommand(api, positionals, args) {
|
|
48036
|
+
const [, sub, query, value] = positionals;
|
|
48037
|
+
const devices = await api.listDevices();
|
|
48038
|
+
if (!sub || sub === "list") {
|
|
48039
|
+
if (args.json) {
|
|
48040
|
+
console.log(JSON.stringify(devices, null, 2));
|
|
48041
|
+
return;
|
|
48042
|
+
}
|
|
48043
|
+
for (const d of devices)
|
|
48044
|
+
process.stderr.write(`${d.status === "ONLINE" ? GREEN + "●" : DIM + "○"}${RESET} ${d.name} ${DIM}${d.deviceType} ${d.metadata?.identity?.hostname ?? ""} ${d.id}${RESET}
|
|
48045
|
+
`);
|
|
48046
|
+
return;
|
|
48047
|
+
}
|
|
48048
|
+
if (!query)
|
|
48049
|
+
fail(`Usage: tfa-cli device ${sub} <device> …`);
|
|
48050
|
+
const q = query.toLowerCase();
|
|
48051
|
+
const hit = devices.filter((d) => d.id === query || d.name.toLowerCase() === q || d.metadata?.identity?.hostname?.toLowerCase() === q);
|
|
48052
|
+
const found = hit.length === 1 ? hit[0] : (() => {
|
|
48053
|
+
const p = devices.filter((d) => d.id.startsWith(query) || d.name.toLowerCase().includes(q) || d.metadata?.identity?.hostname?.toLowerCase().includes(q));
|
|
48054
|
+
if (p.length === 1)
|
|
48055
|
+
return p[0];
|
|
48056
|
+
fail(p.length ? `Ambiguous device '${query}'` : `No device matching '${query}'`);
|
|
48057
|
+
})();
|
|
48058
|
+
if (sub === "rename") {
|
|
48059
|
+
if (!value)
|
|
48060
|
+
fail("Usage: tfa-cli device rename <device> <name>");
|
|
48061
|
+
await api.renameDevice(found.id, value);
|
|
48062
|
+
process.stderr.write(`${GREEN}✅ ${found.name} → ${value}${RESET}
|
|
48063
|
+
`);
|
|
48064
|
+
return;
|
|
48065
|
+
}
|
|
48066
|
+
if (sub === "wallpaper") {
|
|
48067
|
+
if (value !== "auto" && value !== "off")
|
|
48068
|
+
fail("Usage: tfa-cli device wallpaper <device> auto|off");
|
|
48069
|
+
await api.setDeviceWallpaperMode(found.id, value);
|
|
48070
|
+
process.stderr.write(`${GREEN}✅ ${found.name} wallpaper: ${value}${RESET}
|
|
48071
|
+
`);
|
|
48072
|
+
return;
|
|
48073
|
+
}
|
|
48074
|
+
fail(`Unknown 'device' subcommand: ${sub}`);
|
|
48075
|
+
}
|
|
48076
|
+
|
|
47426
48077
|
// src/list-todos.ts
|
|
47427
48078
|
import { parseArgs as parseArgs2 } from "util";
|
|
47428
48079
|
var STATUS_COLOR = {
|
|
@@ -71932,6 +72583,16 @@ function formatPathWithTilde(path5) {
|
|
|
71932
72583
|
const home = homedir3();
|
|
71933
72584
|
return path5.startsWith(home) ? path5.replace(home, "~") : path5;
|
|
71934
72585
|
}
|
|
72586
|
+
function promptArg(words) {
|
|
72587
|
+
if (words.length === 0)
|
|
72588
|
+
return;
|
|
72589
|
+
if (words.length > 1 || !/\s/.test(words[0])) {
|
|
72590
|
+
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}
|
|
72591
|
+
`);
|
|
72592
|
+
process.exit(2);
|
|
72593
|
+
}
|
|
72594
|
+
return words[0];
|
|
72595
|
+
}
|
|
71935
72596
|
async function interactiveLoop(ws, api2, todoId, projectId, agent2, json2, autoApprove, cfg) {
|
|
71936
72597
|
while (true) {
|
|
71937
72598
|
try {
|
|
@@ -72033,15 +72694,35 @@ Cancelled by user (Ctrl+C)
|
|
|
72033
72694
|
printStatusHelp();
|
|
72034
72695
|
process.exit(0);
|
|
72035
72696
|
}
|
|
72697
|
+
if ((positionals[0] === "show" || positionals[0] === "open") && args.help) {
|
|
72698
|
+
printShowHelp();
|
|
72699
|
+
process.exit(0);
|
|
72700
|
+
}
|
|
72036
72701
|
if (positionals[0] === "agent" && args.help) {
|
|
72037
72702
|
printAgentHelp();
|
|
72038
72703
|
process.exit(0);
|
|
72039
72704
|
}
|
|
72705
|
+
if (positionals[0] === "todo" && args.help) {
|
|
72706
|
+
printTodoHelp();
|
|
72707
|
+
process.exit(0);
|
|
72708
|
+
}
|
|
72709
|
+
if (positionals[0] === "project" && args.help) {
|
|
72710
|
+
printProjectHelp();
|
|
72711
|
+
process.exit(0);
|
|
72712
|
+
}
|
|
72713
|
+
if (positionals[0] === "brand" && args.help) {
|
|
72714
|
+
printBrandHelp();
|
|
72715
|
+
process.exit(0);
|
|
72716
|
+
}
|
|
72717
|
+
if (positionals[0] === "device" && args.help) {
|
|
72718
|
+
printDeviceHelp();
|
|
72719
|
+
process.exit(0);
|
|
72720
|
+
}
|
|
72040
72721
|
if ((positionals[0] === "list" || positionals[0] === "ls") && args.help) {
|
|
72041
72722
|
printListTodosHelp();
|
|
72042
72723
|
process.exit(0);
|
|
72043
72724
|
}
|
|
72044
|
-
if (args.help && !["list", "ls", "agent"].includes(positionals[0])) {
|
|
72725
|
+
if (args.help && !["list", "ls", "agent", "todo", "project", "brand", "device"].includes(positionals[0])) {
|
|
72045
72726
|
printUsage();
|
|
72046
72727
|
process.exit(0);
|
|
72047
72728
|
}
|
|
@@ -72153,6 +72834,22 @@ Cancelled by user (Ctrl+C)
|
|
|
72153
72834
|
`);
|
|
72154
72835
|
return;
|
|
72155
72836
|
}
|
|
72837
|
+
if (positionals[0] === "todo") {
|
|
72838
|
+
await todoCommand(api2, positionals, args);
|
|
72839
|
+
return;
|
|
72840
|
+
}
|
|
72841
|
+
if (positionals[0] === "project") {
|
|
72842
|
+
await projectCommand(api2, positionals, args);
|
|
72843
|
+
return;
|
|
72844
|
+
}
|
|
72845
|
+
if (positionals[0] === "brand") {
|
|
72846
|
+
await brandCommand(api2, positionals, args);
|
|
72847
|
+
return;
|
|
72848
|
+
}
|
|
72849
|
+
if (positionals[0] === "device") {
|
|
72850
|
+
await deviceCommand(api2, positionals, args);
|
|
72851
|
+
return;
|
|
72852
|
+
}
|
|
72156
72853
|
if (positionals[0] === "delete") {
|
|
72157
72854
|
const todoId = positionals[1];
|
|
72158
72855
|
if (!todoId) {
|
|
@@ -72491,7 +73188,7 @@ Cancelled by user (Ctrl+C)
|
|
|
72491
73188
|
process.exit(1);
|
|
72492
73189
|
}
|
|
72493
73190
|
const promptWords = positionals[0] === "start" ? positionals.slice(2) : positionals;
|
|
72494
|
-
const content2 = promptWords
|
|
73191
|
+
const content2 = (promptArg(promptWords) ?? "").trim();
|
|
72495
73192
|
const todo2 = await api2.startFromSpec(projectId2, templateId, {
|
|
72496
73193
|
...content2 ? { content: content2 } : {},
|
|
72497
73194
|
...groupTag ? { groupTag } : {},
|
|
@@ -72566,7 +73263,7 @@ Resumed: ${CYAN2}${getFrontendUrl(apiUrl, todoId)}${RESET2}
|
|
|
72566
73263
|
const ws2 = new FrontendWebSocket(apiUrl, apiKey);
|
|
72567
73264
|
await ws2.connect();
|
|
72568
73265
|
const autoApprove = !!args["dangerously-skip-permissions"];
|
|
72569
|
-
const followUp = positionals
|
|
73266
|
+
const followUp = promptArg(positionals) ?? (process.stdin.isTTY ? "" : await readStdin());
|
|
72570
73267
|
if (followUp) {
|
|
72571
73268
|
cfg.addToHistory(followUp);
|
|
72572
73269
|
await api2.addMessage(projectId2, followUp, agent3, todoId);
|
|
@@ -72584,11 +73281,7 @@ Resumed: ${CYAN2}${getFrontendUrl(apiUrl, todoId)}${RESET2}
|
|
|
72584
73281
|
`);
|
|
72585
73282
|
process.exit(2);
|
|
72586
73283
|
}
|
|
72587
|
-
|
|
72588
|
-
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}
|
|
72589
|
-
`);
|
|
72590
|
-
process.exit(2);
|
|
72591
|
-
}
|
|
73284
|
+
const argPrompt = promptArg(positionals);
|
|
72592
73285
|
const ws = args["no-watch"] ? null : new FrontendWebSocket(apiUrl, apiKey);
|
|
72593
73286
|
const wsReady = ws ? ws.connect() : null;
|
|
72594
73287
|
const bridgeReady = !args["no-bridge"] && !args["no-watch"] && !args.isolated ? ensureBridgeRunning2(apiUrl, apiKey) : null;
|
|
@@ -72648,12 +73341,7 @@ Resumed: ${CYAN2}${getFrontendUrl(apiUrl, todoId)}${RESET2}
|
|
|
72648
73341
|
}
|
|
72649
73342
|
process.stderr.write(`${DIM2}Tip: ${randomTip()}${RESET2}
|
|
72650
73343
|
`);
|
|
72651
|
-
|
|
72652
|
-
if (positionals.length > 0) {
|
|
72653
|
-
content = positionals.join(" ");
|
|
72654
|
-
} else {
|
|
72655
|
-
content = await readStdin();
|
|
72656
|
-
}
|
|
73344
|
+
const content = argPrompt ?? await readStdin();
|
|
72657
73345
|
const envProjectId = getEnv("PROJECT_ID");
|
|
72658
73346
|
const hasProject = args.project || envProjectId || cfgScope.data.default_project_id;
|
|
72659
73347
|
const storedAgent = cfgScope.data.default_agent_settings;
|