@todoforai/cli 0.1.22 → 0.1.25
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 +165 -164
- package/package.json +4 -3
|
@@ -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}`
|
|
@@ -43447,6 +43465,7 @@ var SERVER_TO_FRONTENDS = {
|
|
|
43447
43465
|
block_update: (todoId) => `todo:${todoId}:block_update`,
|
|
43448
43466
|
context_summary_updated: (todoId) => `${"context:summary_updated" /* CONTEXT_SUMMARY_UPDATED */}:${todoId}`,
|
|
43449
43467
|
context_info_updated: (todoId) => `${"context:info_updated" /* CONTEXT_INFO_UPDATED */}:${todoId}`,
|
|
43468
|
+
predicted_reply_updated: (todoId) => `${"predict:reply_updated" /* PREDICTED_REPLY_UPDATED */}:${todoId}`,
|
|
43450
43469
|
error_result: (todoId) => `${"block:error_result" /* BLOCK_ERROR_RESULT */}:${todoId}`,
|
|
43451
43470
|
meta_result: (todoId) => `${"block:meta_result" /* BLOCK_META_RESULT */}:${todoId}`,
|
|
43452
43471
|
file_changed: (edgeId, path3) => `${"block:file_changed" /* BLOCK_FILE_CHANGED */}:${edgeId}:${path3}`,
|
|
@@ -43827,12 +43846,12 @@ var BUSINESS_ANALYZER_AGENT = {
|
|
|
43827
43846
|
|
|
43828
43847
|
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
43848
|
|
|
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.
|
|
43849
|
+
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
43850
|
|
|
43832
43851
|
How to analyze and update the business context:
|
|
43833
|
-
1. Read the current business context
|
|
43852
|
+
1. Read the current business context with \`Read todoforai:business-context\`
|
|
43834
43853
|
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
|
|
43854
|
+
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
43855
|
|
|
43837
43856
|
Cover these sections (skip any without data):
|
|
43838
43857
|
- **Company Info** \u2014 name, founding year, location, team size, stage
|
|
@@ -43846,7 +43865,7 @@ Cover these sections (skip any without data):
|
|
|
43846
43865
|
|
|
43847
43866
|
If anything important remains unclear, add an **Open Questions** section at the end.
|
|
43848
43867
|
|
|
43849
|
-
Maintain a single machine-readable **
|
|
43868
|
+
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
43869
|
|
|
43851
43870
|
\`\`\`growth-plan
|
|
43852
43871
|
{
|
|
@@ -43854,25 +43873,16 @@ Maintain a single machine-readable **Growth Plan** block so the user's brand pag
|
|
|
43854
43873
|
"linkedin-networking": { "score": 45, "handle": "https://linkedin.com/company/example", "note": "Company page exists but posts infrequently" },
|
|
43855
43874
|
"x-twitter-presence": { "score": 60, "handle": "@example", "note": "Active, regular posts" },
|
|
43856
43875
|
"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
|
-
]
|
|
43876
|
+
}
|
|
43868
43877
|
}
|
|
43869
43878
|
\`\`\`
|
|
43870
43879
|
|
|
43871
|
-
Rules for the
|
|
43872
|
-
-
|
|
43880
|
+
Rules for the Presence block:
|
|
43881
|
+
- 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
43882
|
- \`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
|
-
|
|
43883
|
+
- Keep it consistent with the prose Online Presence section \u2014 the JSON is the structured mirror of those findings.
|
|
43884
|
+
|
|
43885
|
+
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
43886
|
|
|
43877
43887
|
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
43888
|
|
|
@@ -43882,21 +43892,28 @@ Guide the user one step at a time \u2014 they should always know the single next
|
|
|
43882
43892
|
|
|
43883
43893
|
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
43894
|
|
|
43885
|
-
When the user asks you to recommend how to grow (e.g. "recommend growth TODOs", the brand page's guided suggestion
|
|
43895
|
+
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
43896
|
|
|
43887
43897
|
Recommending actionable TODOs from your recommendations:
|
|
43888
|
-
|
|
43898
|
+
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
43899
|
|
|
43890
|
-
1.
|
|
43891
|
-
-
|
|
43892
|
-
- If none fits, CREATE one right there (auth is inherited from your shell):
|
|
43900
|
+
1. GET A TEMPLATE (todoregistry-cli) \u2014 generate-first, reuse only on a close fit:
|
|
43901
|
+
- Default: CREATE a template tailored to THIS business, right there (auth is inherited from your shell):
|
|
43893
43902
|
\`todoregistry-cli create --name "<name>" --description "<one line>" --body @<prompt-file>\` (or \`--body -\` for stdin). It prints the new template id on stdout.
|
|
43903
|
+
- Before creating, you MAY \`todoregistry-cli search <query>\` / \`list -c <category>\` / \`get <id>\`; reuse an existing template ONLY when it already fits this business closely with no meaningful edits. If it needs tailoring, create a new one instead \u2014 do not recommend a generic playbook that ignores this company's specifics.
|
|
43904
|
+
|
|
43905
|
+
Write every generated body to be unmistakably about THIS company \u2014 bake the specifics from the business context into the task itself: the company name, its actual competitors, the target segment, the specific channel/platform, and the concrete metric to move. A reader should never mistake it for a generic template.
|
|
43906
|
+
|
|
43907
|
+
Body structure (REQUIRED \u2014 the body is split into the agent's system prompt and the first user turn by section headings, so follow this exactly):
|
|
43908
|
+
- Start with the persona/expertise and any Rules as plain prose/paragraphs at the top (no heading needed) \u2014 this becomes the agent's system prompt.
|
|
43909
|
+
- Then the request, using these exact standalone heading lines (letters only, each on its own line, ending with a colon): \`Your task:\`, \`Steps:\`, \`Data needed:\`. Everything under these headings becomes the first user turn.
|
|
43910
|
+
- Headings must match exactly (e.g. \`Your task:\`, not \`Your task (SEO):\`) or the split breaks. Put the business-specific detail in the body text under each heading, never in the heading itself.
|
|
43894
43911
|
|
|
43895
43912
|
2. RECOMMEND (todoforai-cli \u2014 references a template id onto the project; never creates a running todo):
|
|
43896
43913
|
\`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
|
|
43914
|
+
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
43915
|
|
|
43899
|
-
3. Recommend 3-6
|
|
43916
|
+
3. Recommend 3-6 cards (highest priority first). Then reply with a one-line summary per card and tell the user they're waiting in the Recommendations column for review.`,
|
|
43900
43917
|
mcpConfigs: {},
|
|
43901
43918
|
edgesMcpConfigs: {},
|
|
43902
43919
|
permissions: {
|
|
@@ -43972,12 +43989,16 @@ var TOPICS = {
|
|
|
43972
43989
|
channel: (p) => SERVER_TO_FRONTENDS.todo.context_info_updated(p.todoId),
|
|
43973
43990
|
audience: "frontend"
|
|
43974
43991
|
},
|
|
43992
|
+
["predict:reply_updated" /* PREDICTED_REPLY_UPDATED */]: {
|
|
43993
|
+
channel: (p) => SERVER_TO_FRONTENDS.todo.predicted_reply_updated(p.todoId),
|
|
43994
|
+
audience: "frontend"
|
|
43995
|
+
},
|
|
43975
43996
|
["hexgrid:updated" /* HEXGRID_UPDATED */]: {
|
|
43976
43997
|
channel: (p) => SERVER_TO_FRONTENDS.hexGrid.updated(p.projectId),
|
|
43977
43998
|
audience: "frontend"
|
|
43978
43999
|
},
|
|
43979
|
-
["
|
|
43980
|
-
channel: (p) => SERVER_TO_FRONTENDS.
|
|
44000
|
+
["placement:updated" /* PLACEMENT_UPDATED */]: {
|
|
44001
|
+
channel: (p) => SERVER_TO_FRONTENDS.placement.updated(p.projectId),
|
|
43981
44002
|
audience: "frontend"
|
|
43982
44003
|
},
|
|
43983
44004
|
["sandbox:state" /* SANDBOX_STATE */]: {
|
|
@@ -44104,12 +44125,13 @@ Usage:
|
|
|
44104
44125
|
todoforai-cli status <todo-id> <STATUS> # Update a todo's status (run 'status --help' for the full list)
|
|
44105
44126
|
todoforai-cli delete <todo-id> # Permanently delete a todo
|
|
44106
44127
|
todoforai-cli addmessage <todo-id> "text" # Add a message to an existing todo
|
|
44107
|
-
todoforai-cli
|
|
44128
|
+
todoforai-cli show <file|-> [todo-id] # Show a file in the chat (rendered by mimetype; - reads stdin)
|
|
44129
|
+
# [--title T] [--alias A] [--mime M] [--json]
|
|
44130
|
+
todoforai-cli open <url> [todo-id] # Show a live http(s) url in the chat as a preview
|
|
44131
|
+
# [--title T] [--alias A] [--json]
|
|
44132
|
+
todoforai-cli recommend --template <id> # Add a template as a recommendation card (see 'todoregistry-cli create')
|
|
44108
44133
|
todoforai-cli claim mint --seed <projectId> [--emails a@x,b@y] [--ttl <sec>] # Mint /claim/<token> ownership links for a project you own
|
|
44109
|
-
todoforai-cli
|
|
44110
|
-
todoforai-cli steering direction "<text>" # Set the direction the generator should follow
|
|
44111
|
-
todoforai-cli steering boost|mute <cat> # Favour / avoid a category
|
|
44112
|
-
todoforai-cli next # Generate steered NEXT-column recommendations
|
|
44134
|
+
todoforai-cli next [--direction "<text>"] # Ask the analyzer for growth recommendation cards (optional free-text steer)
|
|
44113
44135
|
|
|
44114
44136
|
Options:
|
|
44115
44137
|
--path <dir> Workspace path (default: cwd)
|
|
@@ -44183,6 +44205,9 @@ function parseCliArgs() {
|
|
|
44183
44205
|
note: { type: "string" },
|
|
44184
44206
|
priority: { type: "string" },
|
|
44185
44207
|
title: { type: "string" },
|
|
44208
|
+
alias: { type: "string" },
|
|
44209
|
+
mime: { type: "string" },
|
|
44210
|
+
direction: { type: "string" },
|
|
44186
44211
|
"business-context": { type: "string" },
|
|
44187
44212
|
seed: { type: "string" },
|
|
44188
44213
|
emails: { type: "string" },
|
|
@@ -44840,6 +44865,12 @@ class ScopedConfig {
|
|
|
44840
44865
|
get data() {
|
|
44841
44866
|
return this.store.data.per_api_url[this.apiUrl];
|
|
44842
44867
|
}
|
|
44868
|
+
clearDefaultProject() {
|
|
44869
|
+
const s = this.data;
|
|
44870
|
+
s.default_project_id = null;
|
|
44871
|
+
s.default_project_name = null;
|
|
44872
|
+
this.store.save();
|
|
44873
|
+
}
|
|
44843
44874
|
setDefaultProject(id, name) {
|
|
44844
44875
|
const s = this.data;
|
|
44845
44876
|
s.default_project_id = id;
|
|
@@ -46094,7 +46125,7 @@ Usage:
|
|
|
46094
46125
|
<agent> is a name or id (unique partial name also works).
|
|
46095
46126
|
Fields map directly to agent settings; values are parsed as JSON when possible
|
|
46096
46127
|
(numbers, booleans), otherwise treated as strings. Common fields:
|
|
46097
|
-
model e.g. claude | anthropic:anthropic/claude-opus-
|
|
46128
|
+
model e.g. claude | anthropic:anthropic/claude-opus-5
|
|
46098
46129
|
('claude' is the rolling alias \u2192 latest Claude Opus)
|
|
46099
46130
|
systemMessage freeform prompt text (alias: sysmsg)
|
|
46100
46131
|
temperature number, e.g. 0.7
|
|
@@ -46103,7 +46134,7 @@ Fields map directly to agent settings; values are parsed as JSON when possible
|
|
|
46103
46134
|
|
|
46104
46135
|
Examples:
|
|
46105
46136
|
todoforai-cli agent update <agent> model=claude
|
|
46106
|
-
todoforai-cli agent update <agent> model=anthropic:anthropic/claude-opus-
|
|
46137
|
+
todoforai-cli agent update <agent> model=anthropic:anthropic/claude-opus-5 temperature=0.5
|
|
46107
46138
|
todoforai-cli agent update <agent> sysmsg="You are a terse video editor."
|
|
46108
46139
|
`);
|
|
46109
46140
|
}
|
|
@@ -46390,92 +46421,6 @@ async function listTodosCommand(api, defaultProjectId, argv) {
|
|
|
46390
46421
|
`);
|
|
46391
46422
|
}
|
|
46392
46423
|
|
|
46393
|
-
// src/steering-command.ts
|
|
46394
|
-
var RED2 = "\x1B[31m";
|
|
46395
|
-
var GREEN2 = "\x1B[32m";
|
|
46396
|
-
var DIM2 = "\x1B[2m";
|
|
46397
|
-
var RESET2 = "\x1B[0m";
|
|
46398
|
-
async function rest(ctx, method, path4, body) {
|
|
46399
|
-
const res = await fetch(`${ctx.apiUrl}/api/v1${path4}`, {
|
|
46400
|
-
method,
|
|
46401
|
-
headers: { "content-type": "application/json", "x-api-key": ctx.apiKey },
|
|
46402
|
-
...body ? { body: JSON.stringify(body) } : {},
|
|
46403
|
-
signal: AbortSignal.timeout(30000)
|
|
46404
|
-
});
|
|
46405
|
-
const text = await res.text();
|
|
46406
|
-
if (!res.ok)
|
|
46407
|
-
throw new Error(`${method} ${path4} failed: ${res.status} ${text}`);
|
|
46408
|
-
return text ? JSON.parse(text) : null;
|
|
46409
|
-
}
|
|
46410
|
-
function printSteering(s) {
|
|
46411
|
-
process.stderr.write(`${GREEN2}Direction:${RESET2} ${s.direction || DIM2 + "(none)" + RESET2}
|
|
46412
|
-
`);
|
|
46413
|
-
process.stderr.write(`${GREEN2}Boosted:${RESET2} ${s.boostedCategoryIds?.join(", ") || DIM2 + "(none)" + RESET2}
|
|
46414
|
-
`);
|
|
46415
|
-
process.stderr.write(`${GREEN2}Muted:${RESET2} ${s.mutedCategoryIds?.join(", ") || DIM2 + "(none)" + RESET2}
|
|
46416
|
-
`);
|
|
46417
|
-
}
|
|
46418
|
-
var USAGE = `${RED2}Usage:
|
|
46419
|
-
todoforai-cli steering get [--project <id>]
|
|
46420
|
-
todoforai-cli steering direction "<text>" [--project <id>] (empty text clears it)
|
|
46421
|
-
todoforai-cli steering boost <categoryId> [--project <id>]
|
|
46422
|
-
todoforai-cli steering mute <categoryId> [--project <id>]
|
|
46423
|
-
todoforai-cli steering clear [--project <id>] (clears all steering)${RESET2}`;
|
|
46424
|
-
async function steeringCommand(ctx, argv) {
|
|
46425
|
-
const sub = argv[0];
|
|
46426
|
-
const path4 = `/projects/${ctx.projectId}/recommendation-steering`;
|
|
46427
|
-
if (!sub || sub === "get") {
|
|
46428
|
-
const s = await rest(ctx, "GET", path4);
|
|
46429
|
-
if (ctx.json)
|
|
46430
|
-
console.log(JSON.stringify(s, null, 2));
|
|
46431
|
-
else
|
|
46432
|
-
printSteering(s);
|
|
46433
|
-
return;
|
|
46434
|
-
}
|
|
46435
|
-
if (sub === "direction") {
|
|
46436
|
-
const text = argv.slice(1).join(" ").trim();
|
|
46437
|
-
const s = await rest(ctx, "PATCH", path4, { projectId: ctx.projectId, direction: text || null });
|
|
46438
|
-
if (ctx.json)
|
|
46439
|
-
console.log(JSON.stringify(s, null, 2));
|
|
46440
|
-
else
|
|
46441
|
-
process.stderr.write(`${GREEN2}\u2705 ${text ? `Direction set: ${text}` : "Direction cleared"}${RESET2}
|
|
46442
|
-
`);
|
|
46443
|
-
return;
|
|
46444
|
-
}
|
|
46445
|
-
if (sub === "boost" || sub === "mute") {
|
|
46446
|
-
const categoryId = argv[1];
|
|
46447
|
-
if (!categoryId) {
|
|
46448
|
-
process.stderr.write(USAGE + `
|
|
46449
|
-
`);
|
|
46450
|
-
process.exit(2);
|
|
46451
|
-
}
|
|
46452
|
-
const current = await rest(ctx, "GET", path4);
|
|
46453
|
-
const key = sub === "boost" ? "boostedCategoryIds" : "mutedCategoryIds";
|
|
46454
|
-
const other = sub === "boost" ? "mutedCategoryIds" : "boostedCategoryIds";
|
|
46455
|
-
const next = Array.from(new Set([...current[key] ?? [], categoryId]));
|
|
46456
|
-
const nextOther = (current[other] ?? []).filter((c2) => c2 !== categoryId);
|
|
46457
|
-
const s = await rest(ctx, "PATCH", path4, { projectId: ctx.projectId, [key]: next, [other]: nextOther });
|
|
46458
|
-
if (ctx.json)
|
|
46459
|
-
console.log(JSON.stringify(s, null, 2));
|
|
46460
|
-
else
|
|
46461
|
-
process.stderr.write(`${GREEN2}\u2705 ${sub === "boost" ? "Boosted" : "Muted"} ${categoryId}${RESET2}
|
|
46462
|
-
`);
|
|
46463
|
-
return;
|
|
46464
|
-
}
|
|
46465
|
-
if (sub === "clear") {
|
|
46466
|
-
const s = await rest(ctx, "PATCH", path4, { projectId: ctx.projectId, direction: null, boostedCategoryIds: [], mutedCategoryIds: [] });
|
|
46467
|
-
if (ctx.json)
|
|
46468
|
-
console.log(JSON.stringify(s, null, 2));
|
|
46469
|
-
else
|
|
46470
|
-
process.stderr.write(`${GREEN2}\u2705 Steering cleared${RESET2}
|
|
46471
|
-
`);
|
|
46472
|
-
return;
|
|
46473
|
-
}
|
|
46474
|
-
process.stderr.write(USAGE + `
|
|
46475
|
-
`);
|
|
46476
|
-
process.exit(2);
|
|
46477
|
-
}
|
|
46478
|
-
|
|
46479
46424
|
// src/index.ts
|
|
46480
46425
|
try {
|
|
46481
46426
|
const pkgPath = path4.resolve(fileURLToPath(import.meta.url), "../../package.json");
|
|
@@ -46676,8 +46621,7 @@ Cancelled by user (Ctrl+C)
|
|
|
46676
46621
|
await deviceLogin();
|
|
46677
46622
|
return;
|
|
46678
46623
|
}
|
|
46679
|
-
|
|
46680
|
-
let apiKey = args["api-key"] || notDst(readCredential(apiUrl)) || notDst(getEnv("API_TOKEN")) || "";
|
|
46624
|
+
let apiKey = args["api-key"] || readCredential(apiUrl) || getEnv("API_TOKEN") || "";
|
|
46681
46625
|
if (!apiKey) {
|
|
46682
46626
|
apiKey = await deviceLogin();
|
|
46683
46627
|
}
|
|
@@ -46721,8 +46665,8 @@ Cancelled by user (Ctrl+C)
|
|
|
46721
46665
|
return;
|
|
46722
46666
|
}
|
|
46723
46667
|
if (positionals[0] === "addmessage") {
|
|
46724
|
-
const [, todoId, ...
|
|
46725
|
-
const content2 =
|
|
46668
|
+
const [, todoId, ...rest] = positionals;
|
|
46669
|
+
const content2 = rest.join(" ") || await readStdin();
|
|
46726
46670
|
if (!todoId || !content2) {
|
|
46727
46671
|
process.stderr.write(`${RED}Usage: todoforai-cli addmessage <todo-id> "content"${RESET}
|
|
46728
46672
|
`);
|
|
@@ -46737,6 +46681,55 @@ Cancelled by user (Ctrl+C)
|
|
|
46737
46681
|
`);
|
|
46738
46682
|
return;
|
|
46739
46683
|
}
|
|
46684
|
+
if (positionals[0] === "show") {
|
|
46685
|
+
const [, filePath, todoArg] = positionals;
|
|
46686
|
+
const todoId = todoArg || getEnv("TODO_ID") || cfgScope.data.last_todo_id;
|
|
46687
|
+
if (!filePath || !todoId) {
|
|
46688
|
+
process.stderr.write(`${RED}Usage: todoforai-cli show <file|-> [todo-id]${RESET}
|
|
46689
|
+
`);
|
|
46690
|
+
process.exit(2);
|
|
46691
|
+
}
|
|
46692
|
+
let blob, name;
|
|
46693
|
+
if (filePath === "-") {
|
|
46694
|
+
blob = new Blob([await Bun.readableStreamToArrayBuffer(Bun.stdin.stream())]);
|
|
46695
|
+
if (blob.size === 0) {
|
|
46696
|
+
process.stderr.write(`${RED}No data on stdin${RESET}
|
|
46697
|
+
`);
|
|
46698
|
+
process.exit(1);
|
|
46699
|
+
}
|
|
46700
|
+
name = "stdin";
|
|
46701
|
+
} else {
|
|
46702
|
+
const file = Bun.file(resolve3(filePath));
|
|
46703
|
+
if (!await file.exists()) {
|
|
46704
|
+
process.stderr.write(`${RED}File not found: ${filePath}${RESET}
|
|
46705
|
+
`);
|
|
46706
|
+
process.exit(1);
|
|
46707
|
+
}
|
|
46708
|
+
blob = file;
|
|
46709
|
+
name = path4.basename(filePath);
|
|
46710
|
+
}
|
|
46711
|
+
const res = await api.showFile(todoId, blob, name, { title: args.title, alias: args.alias, mime: args.mime });
|
|
46712
|
+
if (args.json)
|
|
46713
|
+
console.log(JSON.stringify(res, null, 2));
|
|
46714
|
+
else
|
|
46715
|
+
console.log(res.ref);
|
|
46716
|
+
return;
|
|
46717
|
+
}
|
|
46718
|
+
if (positionals[0] === "open") {
|
|
46719
|
+
const [, url, todoArg] = positionals;
|
|
46720
|
+
const todoId = todoArg || getEnv("TODO_ID") || cfgScope.data.last_todo_id;
|
|
46721
|
+
if (!url || !todoId) {
|
|
46722
|
+
process.stderr.write(`${RED}Usage: todoforai-cli open <url> [todo-id]${RESET}
|
|
46723
|
+
`);
|
|
46724
|
+
process.exit(2);
|
|
46725
|
+
}
|
|
46726
|
+
const res = await api.showUrl(todoId, url, { title: args.title, alias: args.alias });
|
|
46727
|
+
if (args.json)
|
|
46728
|
+
console.log(JSON.stringify(res, null, 2));
|
|
46729
|
+
else
|
|
46730
|
+
console.log(res.ref);
|
|
46731
|
+
return;
|
|
46732
|
+
}
|
|
46740
46733
|
if (positionals[0] === "recommend") {
|
|
46741
46734
|
const templateId = args.template || positionals[1];
|
|
46742
46735
|
if (!templateId) {
|
|
@@ -46798,7 +46791,7 @@ Cancelled by user (Ctrl+C)
|
|
|
46798
46791
|
process.exit(2);
|
|
46799
46792
|
}
|
|
46800
46793
|
}
|
|
46801
|
-
const res = await fetch(`${apiUrl}/
|
|
46794
|
+
const res = await fetch(`${apiUrl}${restBasePath(apiKey)}/claims/mint`, {
|
|
46802
46795
|
method: "POST",
|
|
46803
46796
|
headers: { "content-type": "application/json", "x-api-key": apiKey },
|
|
46804
46797
|
body: JSON.stringify({ seedProjectId, ...emails?.length ? { emails } : {}, ...ttlSec !== undefined ? { ttlSec } : {} }),
|
|
@@ -46819,7 +46812,7 @@ Cancelled by user (Ctrl+C)
|
|
|
46819
46812
|
`);
|
|
46820
46813
|
return;
|
|
46821
46814
|
}
|
|
46822
|
-
if (positionals[0] === "
|
|
46815
|
+
if (positionals[0] === "next") {
|
|
46823
46816
|
let projectId2 = args.project || cfgScope.data.default_project_id;
|
|
46824
46817
|
if (!projectId2) {
|
|
46825
46818
|
const projects2 = await api.listProjects();
|
|
@@ -46830,29 +46823,25 @@ Cancelled by user (Ctrl+C)
|
|
|
46830
46823
|
`);
|
|
46831
46824
|
process.exit(2);
|
|
46832
46825
|
}
|
|
46833
|
-
const
|
|
46834
|
-
|
|
46835
|
-
|
|
46836
|
-
|
|
46837
|
-
|
|
46838
|
-
|
|
46839
|
-
|
|
46840
|
-
|
|
46841
|
-
|
|
46842
|
-
|
|
46843
|
-
process.stderr.write(`${RED}Generate failed: ${res.status} ${text}${RESET}
|
|
46844
|
-
`);
|
|
46845
|
-
process.exit(1);
|
|
46846
|
-
}
|
|
46847
|
-
const out = JSON.parse(text);
|
|
46848
|
-
if (args.json)
|
|
46849
|
-
console.log(JSON.stringify(out, null, 2));
|
|
46850
|
-
else
|
|
46851
|
-
process.stderr.write(`${GREEN}\u2705 Generating recommendations \u2014 analyzer todo ${out.todoId}${RESET}
|
|
46826
|
+
const direction = args.direction || positionals.slice(1).join(" ").trim() || undefined;
|
|
46827
|
+
const res = await fetch(`${apiUrl}${restBasePath(apiKey)}/projects/${projectId2}/recommendations/generate`, {
|
|
46828
|
+
method: "POST",
|
|
46829
|
+
headers: { "content-type": "application/json", "x-api-key": apiKey },
|
|
46830
|
+
body: JSON.stringify({ projectId: projectId2, ...args["business-context"] ? { businessContextId: args["business-context"] } : {}, ...direction ? { direction } : {} }),
|
|
46831
|
+
signal: AbortSignal.timeout(30000)
|
|
46832
|
+
});
|
|
46833
|
+
const text = await res.text();
|
|
46834
|
+
if (!res.ok) {
|
|
46835
|
+
process.stderr.write(`${RED}Generate failed: ${res.status} ${text}${RESET}
|
|
46852
46836
|
`);
|
|
46853
|
-
|
|
46837
|
+
process.exit(1);
|
|
46854
46838
|
}
|
|
46855
|
-
|
|
46839
|
+
const out = JSON.parse(text);
|
|
46840
|
+
if (args.json)
|
|
46841
|
+
console.log(JSON.stringify(out, null, 2));
|
|
46842
|
+
else
|
|
46843
|
+
process.stderr.write(`${GREEN}\u2705 Generating recommendations \u2014 analyzer todo ${out.todoId}${RESET}
|
|
46844
|
+
`);
|
|
46856
46845
|
return;
|
|
46857
46846
|
}
|
|
46858
46847
|
if (args["list-agents"]) {
|
|
@@ -46925,7 +46914,8 @@ Cancelled by user (Ctrl+C)
|
|
|
46925
46914
|
const projects2 = await api.listProjects();
|
|
46926
46915
|
let projectId2 = args.project;
|
|
46927
46916
|
if (!projectId2) {
|
|
46928
|
-
|
|
46917
|
+
const cached = cfgScope.data.default_project_id;
|
|
46918
|
+
projectId2 = (cached && projects2.some((p) => getItemId(p) === cached) ? cached : null) || projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
|
|
46929
46919
|
}
|
|
46930
46920
|
if (!projectId2) {
|
|
46931
46921
|
process.stderr.write(`Error: No project found
|
|
@@ -47128,7 +47118,18 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
|
|
|
47128
47118
|
agent = { ...agent, permissions: { ...perms, allow: [...perms.allow || [], "*:*"] } };
|
|
47129
47119
|
}
|
|
47130
47120
|
cfg.addToHistory(content);
|
|
47131
|
-
|
|
47121
|
+
let todo;
|
|
47122
|
+
try {
|
|
47123
|
+
todo = await api.addMessage(projectId, content, agent);
|
|
47124
|
+
} catch (e) {
|
|
47125
|
+
if (!args.project && cfgScope.data.default_project_id === projectId && /failed: 403/.test(e.message || "")) {
|
|
47126
|
+
cfgScope.clearDefaultProject();
|
|
47127
|
+
process.stderr.write(`${RED}Not authorized for cached default project ${projectName} (${projectId}) \u2014 cleared it. Re-run to pick a project.${RESET}
|
|
47128
|
+
`);
|
|
47129
|
+
process.exit(1);
|
|
47130
|
+
}
|
|
47131
|
+
throw e;
|
|
47132
|
+
}
|
|
47132
47133
|
const actualTodoId = todo.id || crypto.randomUUID();
|
|
47133
47134
|
cfgScope.setLastTodoId(actualTodoId);
|
|
47134
47135
|
const frontendUrl = getFrontendUrl(apiUrl, projectId, actualTodoId);
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@todoforai/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
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": {
|