@todoforai/cli 0.1.21 → 0.1.24
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/bin/todoforai-cli.js +9 -0
- package/dist/todoai.js +95 -98
- package/package.json +8 -5
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Dev (linked repo): run src directly so there is no stale-build step.
|
|
3
|
+
// Published package ships only dist/ (the @shared/* file: deps are bundled in).
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const src = fileURLToPath(new URL("../src/index.ts", import.meta.url));
|
|
8
|
+
const dist = fileURLToPath(new URL("../dist/todoai.js", import.meta.url));
|
|
9
|
+
await import(existsSync(src) ? src : dist);
|
package/dist/todoai.js
CHANGED
|
@@ -42732,6 +42732,10 @@ function randomTip() {
|
|
|
42732
42732
|
}
|
|
42733
42733
|
|
|
42734
42734
|
// ../edge/bun/src/api.ts
|
|
42735
|
+
function restBasePath(apiKey) {
|
|
42736
|
+
return apiKey.startsWith("dst_") ? "/dst/v1" : "/api/v1";
|
|
42737
|
+
}
|
|
42738
|
+
|
|
42735
42739
|
class ApiClient {
|
|
42736
42740
|
apiUrl;
|
|
42737
42741
|
apiKey;
|
|
@@ -42743,7 +42747,7 @@ class ApiClient {
|
|
|
42743
42747
|
return { "content-type": "application/json", "x-api-key": this.apiKey };
|
|
42744
42748
|
}
|
|
42745
42749
|
async request(method, endpoint, body) {
|
|
42746
|
-
const url = `${this.apiUrl}${endpoint}`;
|
|
42750
|
+
const url = `${this.apiUrl}${endpoint.replace(/^\/api\/v1/, restBasePath(this.apiKey))}`;
|
|
42747
42751
|
const opts = { method, headers: this.headers, signal: AbortSignal.timeout(30000) };
|
|
42748
42752
|
if (body)
|
|
42749
42753
|
opts.body = JSON.stringify(body);
|
|
@@ -42777,7 +42781,7 @@ class ApiClient {
|
|
|
42777
42781
|
if (!this.apiKey)
|
|
42778
42782
|
return { valid: false, error: "No API key provided" };
|
|
42779
42783
|
try {
|
|
42780
|
-
const res = await fetch(`${this.apiUrl}/
|
|
42784
|
+
const res = await fetch(`${this.apiUrl}${restBasePath(this.apiKey)}/apikey/validate`, {
|
|
42781
42785
|
headers: { "x-api-key": this.apiKey },
|
|
42782
42786
|
signal: AbortSignal.timeout(1e4)
|
|
42783
42787
|
});
|
|
@@ -42814,6 +42818,13 @@ class ApiClient {
|
|
|
42814
42818
|
listTodos(projectId, opts) {
|
|
42815
42819
|
if (!projectId)
|
|
42816
42820
|
return this.request("GET", "/api/v1/todos");
|
|
42821
|
+
if (this.apiKey.startsWith("dst_")) {
|
|
42822
|
+
const qs = new URLSearchParams;
|
|
42823
|
+
for (const [k, v] of Object.entries(opts ?? {}))
|
|
42824
|
+
if (v !== undefined)
|
|
42825
|
+
qs.set(k, String(v));
|
|
42826
|
+
return this.request("GET", `/api/v1/projects/${projectId}/todos${qs.size ? `?${qs}` : ""}`);
|
|
42827
|
+
}
|
|
42817
42828
|
return this.trpcQuery("todo.list", { projectId, ...opts });
|
|
42818
42829
|
}
|
|
42819
42830
|
getTodo(todoId) {
|
|
@@ -42875,17 +42886,21 @@ class ApiClient {
|
|
|
42875
42886
|
listDevices() {
|
|
42876
42887
|
return this.request("GET", "/api/v1/devices");
|
|
42877
42888
|
}
|
|
42878
|
-
|
|
42889
|
+
startFromSpec(projectId, specId, opts) {
|
|
42879
42890
|
return this.request("POST", `/api/v1/projects/${projectId}/todos/from-template`, {
|
|
42880
|
-
|
|
42891
|
+
specId,
|
|
42881
42892
|
...opts
|
|
42882
42893
|
});
|
|
42883
42894
|
}
|
|
42884
|
-
|
|
42895
|
+
recommend(input) {
|
|
42896
|
+
const { projectId, ...body } = input;
|
|
42897
|
+
return this.request("POST", `/api/v1/projects/${projectId}/recommendations`, body);
|
|
42898
|
+
}
|
|
42899
|
+
async getRegistrySpec(specId) {
|
|
42885
42900
|
const base = this.apiUrl.replace(/\/api\/v1\/?$/, "").replace(/\/$/, "");
|
|
42886
|
-
const res = await fetch(`${base}/cookie/v1/registry/templates/${
|
|
42901
|
+
const res = await fetch(`${base}/cookie/v1/registry/templates/${specId}`);
|
|
42887
42902
|
if (!res.ok)
|
|
42888
|
-
throw new Error(`
|
|
42903
|
+
throw new Error(`Spec '${specId}' not found (${res.status})`);
|
|
42889
42904
|
return res.json();
|
|
42890
42905
|
}
|
|
42891
42906
|
async initDeviceLogin(clientName = "edge") {
|
|
@@ -42904,7 +42919,7 @@ class ApiClient {
|
|
|
42904
42919
|
throw new Error(`Device login poll failed: ${res.status}`);
|
|
42905
42920
|
return res.json();
|
|
42906
42921
|
}
|
|
42907
|
-
async
|
|
42922
|
+
async listRegistrySpecs(category = "all") {
|
|
42908
42923
|
const base = this.apiUrl.replace(/\/api\/v1\/?$/, "").replace(/\/$/, "");
|
|
42909
42924
|
const res = await fetch(`${base}/cookie/v1/registry/templates?category=${category}`);
|
|
42910
42925
|
if (!res.ok)
|
|
@@ -43135,13 +43150,16 @@ import { parseArgs } from "util";
|
|
|
43135
43150
|
// package.json
|
|
43136
43151
|
var package_default = {
|
|
43137
43152
|
name: "@todoforai/cli",
|
|
43138
|
-
version: "0.1.
|
|
43153
|
+
version: "0.1.24",
|
|
43139
43154
|
type: "module",
|
|
43140
43155
|
bin: {
|
|
43141
|
-
"todoforai-cli": "
|
|
43142
|
-
todoai: "
|
|
43156
|
+
"todoforai-cli": "bin/todoforai-cli.js",
|
|
43157
|
+
todoai: "bin/todoforai-cli.js"
|
|
43143
43158
|
},
|
|
43144
|
-
files: [
|
|
43159
|
+
files: [
|
|
43160
|
+
"bin",
|
|
43161
|
+
"dist/todoai.js"
|
|
43162
|
+
],
|
|
43145
43163
|
scripts: {
|
|
43146
43164
|
build: "bun build src/index.ts --target=bun --outfile dist/todoai.js --external ws",
|
|
43147
43165
|
prepublishOnly: "bun run build",
|
|
@@ -43400,8 +43418,8 @@ var SERVER_TO_FRONTENDS = {
|
|
|
43400
43418
|
hexGrid: {
|
|
43401
43419
|
updated: (projectId) => `${"hexgrid:updated" /* HEXGRID_UPDATED */}:${projectId}`
|
|
43402
43420
|
},
|
|
43403
|
-
|
|
43404
|
-
updated: (projectId) => `${"
|
|
43421
|
+
placement: {
|
|
43422
|
+
updated: (projectId) => `${"placement:updated" /* PLACEMENT_UPDATED */}:${projectId}`
|
|
43405
43423
|
},
|
|
43406
43424
|
sandbox: {
|
|
43407
43425
|
state: (userId) => `${"sandbox:state" /* SANDBOX_STATE */}:${userId}`
|
|
@@ -43827,12 +43845,12 @@ var BUSINESS_ANALYZER_AGENT = {
|
|
|
43827
43845
|
|
|
43828
43846
|
You have no previous context about this business yet \u2014 everything you know must come from the current business context markdown and what you research now. Do not assume prior knowledge or earlier conversations.
|
|
43829
43847
|
|
|
43830
|
-
This chat lives on the user's Brand page: the business context markdown you maintain is rendered live right next to this conversation, together with the brand voice answers and the company's online-presence map.
|
|
43848
|
+
This chat lives on the user's Brand page: the business context markdown you maintain is rendered live right next to this conversation, together with the brand voice answers and the company's online-presence map. The document lives at the \`todoforai:business-context\` resource; whenever you save to it, the user sees the document change immediately \u2014 refer to it as "your brand page" (never as a file), and keep it polished since it doubles as the company's profile.
|
|
43831
43849
|
|
|
43832
43850
|
How to analyze and update the business context:
|
|
43833
|
-
1. Read the current business context
|
|
43851
|
+
1. Read the current business context with \`Read todoforai:business-context\`
|
|
43834
43852
|
2. Research: fetch websites with webfetch (follow key subpages like /about, /pricing, /contact if they exist); use google_search and google_rag to find competitors and fill knowledge gaps
|
|
43835
|
-
3. Merge findings into the existing context \u2014 preserve valid existing information, update conflicting facts only when the new source is clearer or more specific \u2014 and save with
|
|
43853
|
+
3. Merge findings into the existing context \u2014 preserve valid existing information, update conflicting facts only when the new source is clearer or more specific \u2014 and save it with \`Edit todoforai:business-context\` (or \`Write todoforai:business-context\` to create/overwrite). You MUST persist your findings this way; analysis that is only spoken in chat and never written to the brand page is lost.
|
|
43836
43854
|
|
|
43837
43855
|
Cover these sections (skip any without data):
|
|
43838
43856
|
- **Company Info** \u2014 name, founding year, location, team size, stage
|
|
@@ -43846,7 +43864,7 @@ Cover these sections (skip any without data):
|
|
|
43846
43864
|
|
|
43847
43865
|
If anything important remains unclear, add an **Open Questions** section at the end.
|
|
43848
43866
|
|
|
43849
|
-
Maintain a single machine-readable **
|
|
43867
|
+
Maintain a single machine-readable **Presence** block so the user's brand page can render a live presence map. Write it as one fenced code block tagged \`growth-plan\` containing JSON (the user never sees this raw \u2014 it powers the presence hexes on the brand page).
|
|
43850
43868
|
|
|
43851
43869
|
\`\`\`growth-plan
|
|
43852
43870
|
{
|
|
@@ -43854,25 +43872,16 @@ Maintain a single machine-readable **Growth Plan** block so the user's brand pag
|
|
|
43854
43872
|
"linkedin-networking": { "score": 45, "handle": "https://linkedin.com/company/example", "note": "Company page exists but posts infrequently" },
|
|
43855
43873
|
"x-twitter-presence": { "score": 60, "handle": "@example", "note": "Active, regular posts" },
|
|
43856
43874
|
"newsletter": { "score": null, "note": "No newsletter signup found" }
|
|
43857
|
-
}
|
|
43858
|
-
"recommendations": [
|
|
43859
|
-
{
|
|
43860
|
-
"activityId": "linkedin-networking",
|
|
43861
|
-
"title": "Publish two founder-led LinkedIn posts each week",
|
|
43862
|
-
"rationale": "Target buyers are active on LinkedIn, but the page is nearly dormant.",
|
|
43863
|
-
"angle": "Explain the operational pain through short customer scenarios.",
|
|
43864
|
-
"track": "Qualified profile visits and demo requests per week",
|
|
43865
|
-
"priority": "high"
|
|
43866
|
-
}
|
|
43867
|
-
]
|
|
43875
|
+
}
|
|
43868
43876
|
}
|
|
43869
43877
|
\`\`\`
|
|
43870
43878
|
|
|
43871
|
-
Rules for the
|
|
43872
|
-
-
|
|
43879
|
+
Rules for the Presence block:
|
|
43880
|
+
- Keys MUST be one of these exact ids: facebook-posting, x-twitter-presence, tiktok-content, instagram-content, linkedin-networking, reddit-engagement, blog-writing, seo-optimization, backlink-building, keyword-research, aso-optimization, llm-optimization, google-ads-management, meta-ads-management, lead-outreach, crm-management, newsletter, affiliate-program, partnership-outreach, analytics-monitoring. Skip channels you did not check.
|
|
43873
43881
|
- \`score\` is an integer 0-100, or \`null\` when you checked but found no presence. Base scores on real findings, not guesses. Include every channel you actually checked.
|
|
43874
|
-
-
|
|
43875
|
-
|
|
43882
|
+
- Keep it consistent with the prose Online Presence section \u2014 the JSON is the structured mirror of those findings.
|
|
43883
|
+
|
|
43884
|
+
Growth recommendations are NOT part of this block \u2014 they live in the project's Recommendations column as placement cards (see "Recommending actionable TODOs" below). After any analysis that surfaces growth opportunities, materialize your top recommendations as cards there; the prose Next Steps section stays human-readable advice.
|
|
43876
43885
|
|
|
43877
43886
|
Document order for the saved brand page: human-readable sections first, then the **Next Steps** section, then Open Questions (if any), and finally exactly one \`growth-plan\` block as the very last element. (Your chat reply can still end with a short spoken summary; only the saved markdown must end with the machine block.)
|
|
43878
43887
|
|
|
@@ -43882,10 +43891,10 @@ Guide the user one step at a time \u2014 they should always know the single next
|
|
|
43882
43891
|
|
|
43883
43892
|
In follow-up conversations, act as an ongoing advisor: answer growth and digital-presence questions using the business context, refresh your analysis when asked, and suggest what to tackle next.
|
|
43884
43893
|
|
|
43885
|
-
When the user asks you to recommend how to grow (e.g. "recommend growth TODOs", the brand page's guided suggestion
|
|
43894
|
+
When the user asks you to recommend how to grow (e.g. "recommend growth TODOs", the brand page's guided suggestion, the board's Recommend button), read the current business context (including the presence block), research only where information is missing or stale, refresh the presence block if findings changed, then materialize your top growth actions as recommendation cards (see below). The cards ARE the growth plan \u2014 do not write a separate recommendations list into the markdown.
|
|
43886
43895
|
|
|
43887
43896
|
Recommending actionable TODOs from your recommendations:
|
|
43888
|
-
|
|
43897
|
+
To materialize growth recommendations, create recommendation cards in the project's Recommendations column. You NEVER create running todos \u2014 you only recommend templates by reference. Two tools, strict roles:
|
|
43889
43898
|
|
|
43890
43899
|
1. FIND OR CREATE A TEMPLATE (todoregistry-cli):
|
|
43891
43900
|
- \`todoregistry-cli search <query>\` / \`todoregistry-cli list -c <category>\` / \`todoregistry-cli get <id>\` \u2014 prefer a matching existing template (proven playbook).
|
|
@@ -43894,9 +43903,9 @@ When the user asks you to generate/recommend TODOs (e.g. "generate TODOs", the b
|
|
|
43894
43903
|
|
|
43895
43904
|
2. RECOMMEND (todoforai-cli \u2014 references a template id onto the project; never creates a running todo):
|
|
43896
43905
|
\`todoforai-cli recommend --template <id> --project <projectId> --business-context <businessContextId> --note "<why>" --priority high|medium|low\`
|
|
43897
|
-
Take <projectId> and <businessContextId> from your current session context and pass them explicitly. The card appears
|
|
43906
|
+
Take <projectId> and <businessContextId> from your current session context and pass them explicitly. Put the one-line WHY for THIS business in --note (the card's tooltip). The card appears in the Recommendations column for the user to review before any credits are spent.
|
|
43898
43907
|
|
|
43899
|
-
3. Recommend 3-6
|
|
43908
|
+
3. Recommend 3-6 cards (highest priority first). When creating a spec for a business-specific action, write the body so the positioning angle and what to track are part of the task. Then reply with a one-line summary per card and tell the user they're waiting in the Recommendations column for review.`,
|
|
43900
43909
|
mcpConfigs: {},
|
|
43901
43910
|
edgesMcpConfigs: {},
|
|
43902
43911
|
permissions: {
|
|
@@ -43976,8 +43985,8 @@ var TOPICS = {
|
|
|
43976
43985
|
channel: (p) => SERVER_TO_FRONTENDS.hexGrid.updated(p.projectId),
|
|
43977
43986
|
audience: "frontend"
|
|
43978
43987
|
},
|
|
43979
|
-
["
|
|
43980
|
-
channel: (p) => SERVER_TO_FRONTENDS.
|
|
43988
|
+
["placement:updated" /* PLACEMENT_UPDATED */]: {
|
|
43989
|
+
channel: (p) => SERVER_TO_FRONTENDS.placement.updated(p.projectId),
|
|
43981
43990
|
audience: "frontend"
|
|
43982
43991
|
},
|
|
43983
43992
|
["sandbox:state" /* SANDBOX_STATE */]: {
|
|
@@ -44096,7 +44105,7 @@ Usage:
|
|
|
44096
44105
|
todoforai-cli -c ["prompt"] # Resume last todo (optional prompt sent on attach)
|
|
44097
44106
|
todoforai-cli --resume <todo-id> ["prompt"] # Resume specific todo (optional prompt sent on attach)
|
|
44098
44107
|
todoforai-cli --inspect <todo-id>[@<slice>] # Read chat log. <slice> = -3:, :1, 5:10, 7 (Python-style)
|
|
44099
|
-
todoforai-cli
|
|
44108
|
+
todoforai-cli start <id> # Start a TODO from the registry (todoregistry.com)
|
|
44100
44109
|
todoforai-cli --list-agents # List available agents and exit
|
|
44101
44110
|
todoforai-cli --list-models [filter] # List models usable with --model and exit
|
|
44102
44111
|
todoforai-cli agent update <agent> model=<model> # Update agent settings (see 'agent --help')
|
|
@@ -44104,12 +44113,12 @@ Usage:
|
|
|
44104
44113
|
todoforai-cli status <todo-id> <STATUS> # Update a todo's status (run 'status --help' for the full list)
|
|
44105
44114
|
todoforai-cli delete <todo-id> # Permanently delete a todo
|
|
44106
44115
|
todoforai-cli addmessage <todo-id> "text" # Add a message to an existing todo
|
|
44107
|
-
todoforai-cli recommend --template <id> # Add a template as a
|
|
44116
|
+
todoforai-cli recommend --template <id> # Add a template as a recommendation card (see 'todoregistry-cli create')
|
|
44108
44117
|
todoforai-cli claim mint --seed <projectId> [--emails a@x,b@y] [--ttl <sec>] # Mint /claim/<token> ownership links for a project you own
|
|
44109
44118
|
todoforai-cli steering get # Show recommendation steering (direction + boosted/muted categories)
|
|
44110
44119
|
todoforai-cli steering direction "<text>" # Set the direction the generator should follow
|
|
44111
44120
|
todoforai-cli steering boost|mute <cat> # Favour / avoid a category
|
|
44112
|
-
todoforai-cli next # Generate steered
|
|
44121
|
+
todoforai-cli next # Generate steered recommendation cards
|
|
44113
44122
|
|
|
44114
44123
|
Options:
|
|
44115
44124
|
--path <dir> Workspace path (default: cwd)
|
|
@@ -44123,8 +44132,7 @@ Options:
|
|
|
44123
44132
|
--api-key <key> API key
|
|
44124
44133
|
--user-id <id> Admin HTTP impersonation; requires --no-watch
|
|
44125
44134
|
--inspect, -i <todo-id>[@<slice>] Print chat log (read-only)
|
|
44126
|
-
--template, -t <id> Start from a registry template
|
|
44127
|
-
--input <key=value> Template input (repeatable)
|
|
44135
|
+
--template, -t <id> Start from a registry template (alias: start <id>)
|
|
44128
44136
|
--resume, -r [todo-id] Resume existing todo
|
|
44129
44137
|
--continue, -c Continue most recent todo
|
|
44130
44138
|
--non-interactive, -n Run to completion and exit without interactive prompt
|
|
@@ -44181,7 +44189,6 @@ function parseCliArgs() {
|
|
|
44181
44189
|
"user-id": { type: "string" },
|
|
44182
44190
|
inspect: { type: "string", short: "i" },
|
|
44183
44191
|
template: { type: "string", short: "t" },
|
|
44184
|
-
input: { type: "string", multiple: true },
|
|
44185
44192
|
note: { type: "string" },
|
|
44186
44193
|
priority: { type: "string" },
|
|
44187
44194
|
title: { type: "string" },
|
|
@@ -44217,9 +44224,6 @@ function parseCliArgs() {
|
|
|
44217
44224
|
return { values, positionals };
|
|
44218
44225
|
}
|
|
44219
44226
|
|
|
44220
|
-
// src/input.ts
|
|
44221
|
-
import { createInterface } from "readline";
|
|
44222
|
-
|
|
44223
44227
|
// src/colors.ts
|
|
44224
44228
|
var on = !process.env.NO_COLOR && !!process.stdout.isTTY;
|
|
44225
44229
|
var c = (seq) => on ? seq : "";
|
|
@@ -44240,15 +44244,6 @@ var BG_RED_HL = c("\x1B[48;2;100;35;35m");
|
|
|
44240
44244
|
var BG_GREEN_HL = c("\x1B[48;2;35;85;35m");
|
|
44241
44245
|
|
|
44242
44246
|
// src/input.ts
|
|
44243
|
-
function readLine(prompt) {
|
|
44244
|
-
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
44245
|
-
return new Promise((res) => {
|
|
44246
|
-
rl.question(prompt, (ans) => {
|
|
44247
|
-
rl.close();
|
|
44248
|
-
res(ans.trim());
|
|
44249
|
-
});
|
|
44250
|
-
});
|
|
44251
|
-
}
|
|
44252
44247
|
function readMultiline(prompt, history) {
|
|
44253
44248
|
let cancelFn = () => {};
|
|
44254
44249
|
const promise = new Promise((resolve, reject) => {
|
|
@@ -44854,6 +44849,12 @@ class ScopedConfig {
|
|
|
44854
44849
|
get data() {
|
|
44855
44850
|
return this.store.data.per_api_url[this.apiUrl];
|
|
44856
44851
|
}
|
|
44852
|
+
clearDefaultProject() {
|
|
44853
|
+
const s = this.data;
|
|
44854
|
+
s.default_project_id = null;
|
|
44855
|
+
s.default_project_name = null;
|
|
44856
|
+
this.store.save();
|
|
44857
|
+
}
|
|
44857
44858
|
setDefaultProject(id, name) {
|
|
44858
44859
|
const s = this.data;
|
|
44859
44860
|
s.default_project_id = id;
|
|
@@ -45320,7 +45321,7 @@ ${label}${ts}
|
|
|
45320
45321
|
}
|
|
45321
45322
|
|
|
45322
45323
|
// src/select.ts
|
|
45323
|
-
import { createInterface
|
|
45324
|
+
import { createInterface } from "readline";
|
|
45324
45325
|
function getDisplayName(item) {
|
|
45325
45326
|
if (item?.project?.name)
|
|
45326
45327
|
return item.project.name;
|
|
@@ -45354,7 +45355,7 @@ function resolveAgentMatch(agents, query) {
|
|
|
45354
45355
|
return {};
|
|
45355
45356
|
}
|
|
45356
45357
|
function terminalLine(prompt) {
|
|
45357
|
-
const rl =
|
|
45358
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
45358
45359
|
return new Promise((resolve3) => {
|
|
45359
45360
|
rl.question(prompt, (answer) => {
|
|
45360
45361
|
rl.close();
|
|
@@ -46108,7 +46109,7 @@ Usage:
|
|
|
46108
46109
|
<agent> is a name or id (unique partial name also works).
|
|
46109
46110
|
Fields map directly to agent settings; values are parsed as JSON when possible
|
|
46110
46111
|
(numbers, booleans), otherwise treated as strings. Common fields:
|
|
46111
|
-
model e.g. claude | anthropic:anthropic/claude-opus-
|
|
46112
|
+
model e.g. claude | anthropic:anthropic/claude-opus-5
|
|
46112
46113
|
('claude' is the rolling alias \u2192 latest Claude Opus)
|
|
46113
46114
|
systemMessage freeform prompt text (alias: sysmsg)
|
|
46114
46115
|
temperature number, e.g. 0.7
|
|
@@ -46117,7 +46118,7 @@ Fields map directly to agent settings; values are parsed as JSON when possible
|
|
|
46117
46118
|
|
|
46118
46119
|
Examples:
|
|
46119
46120
|
todoforai-cli agent update <agent> model=claude
|
|
46120
|
-
todoforai-cli agent update <agent> model=anthropic:anthropic/claude-opus-
|
|
46121
|
+
todoforai-cli agent update <agent> model=anthropic:anthropic/claude-opus-5 temperature=0.5
|
|
46121
46122
|
todoforai-cli agent update <agent> sysmsg="You are a terse video editor."
|
|
46122
46123
|
`);
|
|
46123
46124
|
}
|
|
@@ -46410,7 +46411,7 @@ var GREEN2 = "\x1B[32m";
|
|
|
46410
46411
|
var DIM2 = "\x1B[2m";
|
|
46411
46412
|
var RESET2 = "\x1B[0m";
|
|
46412
46413
|
async function rest(ctx, method, path4, body) {
|
|
46413
|
-
const res = await fetch(`${ctx.apiUrl}
|
|
46414
|
+
const res = await fetch(`${ctx.apiUrl}${restBasePath(ctx.apiKey)}${path4}`, {
|
|
46414
46415
|
method,
|
|
46415
46416
|
headers: { "content-type": "application/json", "x-api-key": ctx.apiKey },
|
|
46416
46417
|
...body ? { body: JSON.stringify(body) } : {},
|
|
@@ -46580,6 +46581,15 @@ Cancelled by user (Ctrl+C)
|
|
|
46580
46581
|
process.exit(130);
|
|
46581
46582
|
});
|
|
46582
46583
|
const { values: args, positionals } = parseCliArgs();
|
|
46584
|
+
if (positionals[0] === "start") {
|
|
46585
|
+
if (!positionals[1] && !args.template) {
|
|
46586
|
+
process.stderr.write(`${RED}Usage: todoforai-cli start <todo-id>${RESET}
|
|
46587
|
+
`);
|
|
46588
|
+
process.exit(2);
|
|
46589
|
+
}
|
|
46590
|
+
if (!args.template)
|
|
46591
|
+
args.template = positionals[1];
|
|
46592
|
+
}
|
|
46583
46593
|
if (args.version) {
|
|
46584
46594
|
console.log(VERSION);
|
|
46585
46595
|
process.exit(0);
|
|
@@ -46681,8 +46691,7 @@ Cancelled by user (Ctrl+C)
|
|
|
46681
46691
|
await deviceLogin();
|
|
46682
46692
|
return;
|
|
46683
46693
|
}
|
|
46684
|
-
|
|
46685
|
-
let apiKey = args["api-key"] || notDst(readCredential(apiUrl)) || notDst(getEnv("API_TOKEN")) || "";
|
|
46694
|
+
let apiKey = args["api-key"] || readCredential(apiUrl) || getEnv("API_TOKEN") || "";
|
|
46686
46695
|
if (!apiKey) {
|
|
46687
46696
|
apiKey = await deviceLogin();
|
|
46688
46697
|
}
|
|
@@ -46769,7 +46778,7 @@ Cancelled by user (Ctrl+C)
|
|
|
46769
46778
|
}
|
|
46770
46779
|
const rec = await api.recommend({
|
|
46771
46780
|
projectId: projectId2,
|
|
46772
|
-
templateId,
|
|
46781
|
+
specId: templateId,
|
|
46773
46782
|
...args.title ? { title: args.title } : {},
|
|
46774
46783
|
...args.note ? { note: args.note } : {},
|
|
46775
46784
|
...priority ? { priority } : {},
|
|
@@ -46803,7 +46812,7 @@ Cancelled by user (Ctrl+C)
|
|
|
46803
46812
|
process.exit(2);
|
|
46804
46813
|
}
|
|
46805
46814
|
}
|
|
46806
|
-
const res = await fetch(`${apiUrl}/
|
|
46815
|
+
const res = await fetch(`${apiUrl}${restBasePath(apiKey)}/claims/mint`, {
|
|
46807
46816
|
method: "POST",
|
|
46808
46817
|
headers: { "content-type": "application/json", "x-api-key": apiKey },
|
|
46809
46818
|
body: JSON.stringify({ seedProjectId, ...emails?.length ? { emails } : {}, ...ttlSec !== undefined ? { ttlSec } : {} }),
|
|
@@ -46837,7 +46846,7 @@ Cancelled by user (Ctrl+C)
|
|
|
46837
46846
|
}
|
|
46838
46847
|
const sctx = { apiUrl, apiKey, projectId: projectId2, json: !!args.json };
|
|
46839
46848
|
if (positionals[0] === "next") {
|
|
46840
|
-
const res = await fetch(`${apiUrl}/
|
|
46849
|
+
const res = await fetch(`${apiUrl}${restBasePath(apiKey)}/projects/${projectId2}/recommendations/generate`, {
|
|
46841
46850
|
method: "POST",
|
|
46842
46851
|
headers: { "content-type": "application/json", "x-api-key": apiKey },
|
|
46843
46852
|
body: JSON.stringify({ projectId: projectId2, ...args["business-context"] ? { businessContextId: args["business-context"] } : {} }),
|
|
@@ -46921,47 +46930,24 @@ Cancelled by user (Ctrl+C)
|
|
|
46921
46930
|
if (!args["no-bridge"] && !args["no-watch"])
|
|
46922
46931
|
ensureBridgeRunning(apiUrl, apiKey);
|
|
46923
46932
|
const templateId = args.template;
|
|
46924
|
-
const
|
|
46925
|
-
|
|
46926
|
-
const eq = kv.indexOf("=");
|
|
46927
|
-
if (eq > 0)
|
|
46928
|
-
inputValues[kv.slice(0, eq)] = kv.slice(eq + 1);
|
|
46929
|
-
}
|
|
46930
|
-
const template = await api.getRegistryTemplate(templateId);
|
|
46931
|
-
process.stderr.write(`${DIM}Template:${RESET} ${BRAND}${template.todoname}${RESET}
|
|
46933
|
+
const spec = await api.getRegistrySpec(templateId);
|
|
46934
|
+
process.stderr.write(`${DIM}Template:${RESET} ${BRAND}${spec.name}${RESET}
|
|
46932
46935
|
`);
|
|
46933
|
-
if (
|
|
46934
|
-
process.stderr.write(`${DIM}${
|
|
46936
|
+
if (spec.description)
|
|
46937
|
+
process.stderr.write(`${DIM}${spec.description}${RESET}
|
|
46935
46938
|
`);
|
|
46936
|
-
const templateInputs = template.inputs || [];
|
|
46937
|
-
if (templateInputs.length && !args["non-interactive"]) {
|
|
46938
|
-
for (const inp of templateInputs) {
|
|
46939
|
-
const key = inp.label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/_+$/, "");
|
|
46940
|
-
if (inputValues[key])
|
|
46941
|
-
continue;
|
|
46942
|
-
const req = inp.required ? ` ${RED}*${RESET}` : "";
|
|
46943
|
-
const hint = inp.placeholder ? ` ${DIM}(${inp.placeholder.split(`
|
|
46944
|
-
`)[0]})${RESET}` : "";
|
|
46945
|
-
const val = await readLine(`${inp.label}${req}${hint}: `);
|
|
46946
|
-
if (val)
|
|
46947
|
-
inputValues[key] = val;
|
|
46948
|
-
}
|
|
46949
|
-
}
|
|
46950
|
-
if (Object.keys(inputValues).length) {
|
|
46951
|
-
process.stderr.write(`${DIM}Inputs:${RESET} ${JSON.stringify(inputValues)}
|
|
46952
|
-
`);
|
|
46953
|
-
}
|
|
46954
46939
|
const projects2 = await api.listProjects();
|
|
46955
46940
|
let projectId2 = args.project;
|
|
46956
46941
|
if (!projectId2) {
|
|
46957
|
-
|
|
46942
|
+
const cached = cfgScope.data.default_project_id;
|
|
46943
|
+
projectId2 = (cached && projects2.some((p) => getItemId(p) === cached) ? cached : null) || projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
|
|
46958
46944
|
}
|
|
46959
46945
|
if (!projectId2) {
|
|
46960
46946
|
process.stderr.write(`Error: No project found
|
|
46961
46947
|
`);
|
|
46962
46948
|
process.exit(1);
|
|
46963
46949
|
}
|
|
46964
|
-
const todo2 = await api.
|
|
46950
|
+
const todo2 = await api.startFromSpec(projectId2, templateId);
|
|
46965
46951
|
const todoId = todo2.id;
|
|
46966
46952
|
cfgScope.setLastTodoId(todoId);
|
|
46967
46953
|
const frontendUrl2 = getFrontendUrl(apiUrl, projectId2, todoId);
|
|
@@ -47157,7 +47143,18 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
|
|
|
47157
47143
|
agent = { ...agent, permissions: { ...perms, allow: [...perms.allow || [], "*:*"] } };
|
|
47158
47144
|
}
|
|
47159
47145
|
cfg.addToHistory(content);
|
|
47160
|
-
|
|
47146
|
+
let todo;
|
|
47147
|
+
try {
|
|
47148
|
+
todo = await api.addMessage(projectId, content, agent);
|
|
47149
|
+
} catch (e) {
|
|
47150
|
+
if (!args.project && cfgScope.data.default_project_id === projectId && /failed: 403/.test(e.message || "")) {
|
|
47151
|
+
cfgScope.clearDefaultProject();
|
|
47152
|
+
process.stderr.write(`${RED}Not authorized for cached default project ${projectName} (${projectId}) \u2014 cleared it. Re-run to pick a project.${RESET}
|
|
47153
|
+
`);
|
|
47154
|
+
process.exit(1);
|
|
47155
|
+
}
|
|
47156
|
+
throw e;
|
|
47157
|
+
}
|
|
47161
47158
|
const actualTodoId = todo.id || crypto.randomUUID();
|
|
47162
47159
|
cfgScope.setLastTodoId(actualTodoId);
|
|
47163
47160
|
const frontendUrl = getFrontendUrl(apiUrl, projectId, actualTodoId);
|
package/package.json
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@todoforai/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
|
-
"todoforai-cli": "
|
|
7
|
-
"todoai": "
|
|
6
|
+
"todoforai-cli": "bin/todoforai-cli.js",
|
|
7
|
+
"todoai": "bin/todoforai-cli.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
+
"bin",
|
|
10
11
|
"dist/todoai.js"
|
|
11
12
|
],
|
|
12
13
|
"scripts": {
|
|
13
14
|
"build": "bun build src/index.ts --target=bun --outfile dist/todoai.js --external ws",
|
|
14
15
|
"prepublishOnly": "bun run build",
|
|
15
16
|
"start": "bun run src/index.ts",
|
|
16
|
-
"dev": "bun run src/index.ts"
|
|
17
|
+
"dev": "bun run src/index.ts",
|
|
18
|
+
"postinstall": "rm -rf node_modules/@todoforai/edge && ln -s ../../../edge/bun node_modules/@todoforai/edge"
|
|
17
19
|
},
|
|
18
20
|
"dependencies": {
|
|
19
21
|
"@shared/credentials": "file:../packages/shared-credentials",
|
|
@@ -23,7 +25,7 @@
|
|
|
23
25
|
"ws": "^8.18.0"
|
|
24
26
|
},
|
|
25
27
|
"peerDependencies": {
|
|
26
|
-
"@todoforai/edge": "
|
|
28
|
+
"@todoforai/edge": "file:../edge/bun"
|
|
27
29
|
},
|
|
28
30
|
"peerDependenciesMeta": {
|
|
29
31
|
"@todoforai/edge": {
|
|
@@ -32,6 +34,7 @@
|
|
|
32
34
|
},
|
|
33
35
|
"devDependencies": {
|
|
34
36
|
"@types/ws": "^8.5.13",
|
|
37
|
+
"@todoforai/edge": "file:../edge/bun",
|
|
35
38
|
"typescript": "^5.7.0"
|
|
36
39
|
}
|
|
37
40
|
}
|