@todoforai/cli 0.1.28 → 0.1.30
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 +114 -17
- package/package.json +1 -1
package/dist/todoforai-cli.js
CHANGED
|
@@ -42893,6 +42893,65 @@ class ApiClient {
|
|
|
42893
42893
|
throw new Error(`API POST /resources/open failed: ${res.status} ${await res.text()}`);
|
|
42894
42894
|
return res.json();
|
|
42895
42895
|
}
|
|
42896
|
+
async listShows(opts = {}) {
|
|
42897
|
+
const qs = new URLSearchParams;
|
|
42898
|
+
if (opts.todoId)
|
|
42899
|
+
qs.set("todoId", opts.todoId);
|
|
42900
|
+
if (opts.projectId)
|
|
42901
|
+
qs.set("projectId", opts.projectId);
|
|
42902
|
+
if (opts.card)
|
|
42903
|
+
qs.set("card", opts.card);
|
|
42904
|
+
const url = `${this.apiUrl}${restBasePath(this.apiKey)}/resources/show?${qs}`;
|
|
42905
|
+
const res = await fetch(url, { headers: { "x-api-key": this.apiKey }, signal: AbortSignal.timeout(30000) });
|
|
42906
|
+
if (!res.ok)
|
|
42907
|
+
throw new Error(`API GET /resources/show failed: ${res.status} ${await res.text()}`);
|
|
42908
|
+
return res.json();
|
|
42909
|
+
}
|
|
42910
|
+
async upsertCard(projectId, name, html, opts = {}) {
|
|
42911
|
+
const form = new FormData;
|
|
42912
|
+
form.append("file", html, `${name}.html`);
|
|
42913
|
+
for (const [k, v] of Object.entries(opts))
|
|
42914
|
+
if (v)
|
|
42915
|
+
form.append(k, v);
|
|
42916
|
+
const url = `${this.apiUrl}${restBasePath(this.apiKey)}/resources/cards/${projectId}/${name}`;
|
|
42917
|
+
const res = await fetch(url, {
|
|
42918
|
+
method: "PUT",
|
|
42919
|
+
headers: { "x-api-key": this.apiKey },
|
|
42920
|
+
body: form,
|
|
42921
|
+
signal: AbortSignal.timeout(60000)
|
|
42922
|
+
});
|
|
42923
|
+
if (!res.ok)
|
|
42924
|
+
throw new Error(`API PUT cards failed: ${res.status} ${await res.text()}`);
|
|
42925
|
+
return res.json();
|
|
42926
|
+
}
|
|
42927
|
+
async listCards(projectId) {
|
|
42928
|
+
const url = `${this.apiUrl}${restBasePath(this.apiKey)}/resources/cards/${projectId}`;
|
|
42929
|
+
const res = await fetch(url, { headers: { "x-api-key": this.apiKey }, signal: AbortSignal.timeout(30000) });
|
|
42930
|
+
if (!res.ok)
|
|
42931
|
+
throw new Error(`API GET cards failed: ${res.status} ${await res.text()}`);
|
|
42932
|
+
return res.json();
|
|
42933
|
+
}
|
|
42934
|
+
async getCard(projectId, name) {
|
|
42935
|
+
const url = `${this.apiUrl}${restBasePath(this.apiKey)}/resources/cards/${projectId}/${name}`;
|
|
42936
|
+
const res = await fetch(url, { headers: { "x-api-key": this.apiKey }, signal: AbortSignal.timeout(30000) });
|
|
42937
|
+
if (!res.ok)
|
|
42938
|
+
throw new Error(`API GET card failed: ${res.status} ${await res.text()}`);
|
|
42939
|
+
return res.json();
|
|
42940
|
+
}
|
|
42941
|
+
async getCardBody(projectId, name) {
|
|
42942
|
+
const url = `${this.apiUrl}${restBasePath(this.apiKey)}/resources/cards/${projectId}/${name}?body=1`;
|
|
42943
|
+
const res = await fetch(url, { headers: { "x-api-key": this.apiKey }, signal: AbortSignal.timeout(30000) });
|
|
42944
|
+
if (!res.ok)
|
|
42945
|
+
throw new Error(`API GET card body failed: ${res.status} ${await res.text()}`);
|
|
42946
|
+
return res.text();
|
|
42947
|
+
}
|
|
42948
|
+
async deleteCard(projectId, name) {
|
|
42949
|
+
const url = `${this.apiUrl}${restBasePath(this.apiKey)}/resources/cards/${projectId}/${name}`;
|
|
42950
|
+
const res = await fetch(url, { method: "DELETE", headers: { "x-api-key": this.apiKey }, signal: AbortSignal.timeout(30000) });
|
|
42951
|
+
if (!res.ok)
|
|
42952
|
+
throw new Error(`API DELETE card failed: ${res.status} ${await res.text()}`);
|
|
42953
|
+
return res.json();
|
|
42954
|
+
}
|
|
42896
42955
|
patchEdgeConfig(edgeId, updates) {
|
|
42897
42956
|
return this.request("PATCH", `/api/v1/edges/${edgeId}`, { updates });
|
|
42898
42957
|
}
|
|
@@ -43199,6 +43258,8 @@ class FrontendWebSocket {
|
|
|
43199
43258
|
// ../packages/shared-fbe/src/enums.ts
|
|
43200
43259
|
var TodoStatus;
|
|
43201
43260
|
((TodoStatus2) => {
|
|
43261
|
+
TodoStatus2["PROPOSED"] = "PROPOSED";
|
|
43262
|
+
TodoStatus2["DISMISSED"] = "DISMISSED";
|
|
43202
43263
|
TodoStatus2["TODO"] = "TODO";
|
|
43203
43264
|
TodoStatus2["SCHEDULED"] = "SCHEDULED";
|
|
43204
43265
|
TodoStatus2["PAUSED"] = "PAUSED";
|
|
@@ -43479,9 +43540,6 @@ var SERVER_TO_FRONTENDS = {
|
|
|
43479
43540
|
hexGrid: {
|
|
43480
43541
|
updated: (projectId) => `${"hexgrid:updated" /* HEXGRID_UPDATED */}:${projectId}`
|
|
43481
43542
|
},
|
|
43482
|
-
placement: {
|
|
43483
|
-
updated: (projectId) => `${"placement:updated" /* PLACEMENT_UPDATED */}:${projectId}`
|
|
43484
|
-
},
|
|
43485
43543
|
sandbox: {
|
|
43486
43544
|
state: (userId) => `${"sandbox:state" /* SANDBOX_STATE */}:${userId}`
|
|
43487
43545
|
},
|
|
@@ -43939,7 +43997,7 @@ Rules for the Presence block:
|
|
|
43939
43997
|
- \`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.
|
|
43940
43998
|
- Keep it consistent with the prose Online Presence section \u2014 the JSON is the structured mirror of those findings.
|
|
43941
43999
|
|
|
43942
|
-
Growth recommendations are NOT part of this block \u2014 they live
|
|
44000
|
+
Growth recommendations are NOT part of this block \u2014 they live on the project board as proposed todos (\u2727 rows on their group cards; see "Recommending actionable TODOs" below). After any analysis that surfaces growth opportunities, materialize your top recommendations as proposals there; the prose Next Steps section stays human-readable advice.
|
|
43943
44001
|
|
|
43944
44002
|
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.)
|
|
43945
44003
|
|
|
@@ -43951,8 +44009,14 @@ In follow-up conversations, act as an ongoing advisor: answer growth and digital
|
|
|
43951
44009
|
|
|
43952
44010
|
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.
|
|
43953
44011
|
|
|
44012
|
+
Strategic workstreams (board groups):
|
|
44013
|
+
The project board is organized into group cards, and each group should be a strategic workstream for THIS company \u2014 a front the business is (or should be) pushing on, e.g. "SEO & Content", "Outbound to agencies", "Community on Reddit", "Retention & Email". Your recommendation-generation prompt includes the board's existing groups as JSON (\`slug\`, \`name\`, and \`description\` when set \u2014 the description states the workstream's strategic intent). Treat them as the company's current strategic map:
|
|
44014
|
+
- FIRST fit each recommendation into an existing group when it genuinely advances that workstream \u2014 pass its slug as the \`group\` parameter of \`api_recommend_template\`. Match on strategy (read the descriptions), not keywords.
|
|
44015
|
+
- When your analysis surfaces a strategic direction the board doesn't cover yet, introduce it: pass a new short kebab-case \`group\` slug plus a human \`groupName\` (2-4 words, strategy-level \u2014 "Partner Channel", not "Misc" or "Growth") and a one-sentence \`groupDescription\` stating the strategic bet and its target outcome for THIS company (e.g. "Win comparison searches against Zapier and n8n to capture switchers"). The group is created with the card.
|
|
44016
|
+
- Keep the map tight: 3-7 workstreams total is healthy. Never invent a new group when an existing one fits, never create catch-all groups, and never mirror priority or channel-lists as groups \u2014 a group is a strategic bet, its cards are the moves.
|
|
44017
|
+
|
|
43954
44018
|
Recommending actionable TODOs from your recommendations:
|
|
43955
|
-
To materialize growth recommendations,
|
|
44019
|
+
To materialize growth recommendations, propose todos on the project board. A proposal is a real todo in PROPOSED state \u2014 it sits as a \u2727 row on its group card until the user starts (accepts) or dismisses it. You NEVER create running todos \u2014 you only propose. Two tools, strict roles:
|
|
43956
44020
|
|
|
43957
44021
|
1. GET A TEMPLATE (api_create_template) \u2014 generate-first, reuse only on a close fit:
|
|
43958
44022
|
- Default: CREATE a template tailored to THIS business with the \`api_create_template\` tool. It returns the new template id.
|
|
@@ -43965,12 +44029,12 @@ To materialize growth recommendations, create recommendation cards in the projec
|
|
|
43965
44029
|
- 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.
|
|
43966
44030
|
- 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.
|
|
43967
44031
|
|
|
43968
|
-
2. RECOMMEND (api_recommend_template \u2014
|
|
43969
|
-
Call \`api_recommend_template\` with the template id, a one-line \`note\` (the WHY for THIS business \u2014 it becomes the
|
|
44032
|
+
2. RECOMMEND (api_recommend_template \u2014 proposes the template as a PROPOSED todo on the project; never creates a running todo):
|
|
44033
|
+
Call \`api_recommend_template\` with the template id, a one-line \`note\` (the WHY for THIS business \u2014 it becomes the proposal's tooltip), a \`priority\` of high, medium or low, and the \`group\` slug of the strategic workstream it belongs to (see "Strategic workstreams" above \u2014 reuse an existing slug when one fits; a new slug + \`groupName\` creates the workstream). Project and business context default to the current session, so pass them only when targeting a different one. The proposal appears as a \u2727 row on that group's board card for the user to review before any credits are spent. Re-proposing the same template refreshes the open proposal instead of duplicating it; dismissed proposals since your last run are fed back to you \u2014 don't re-propose those, steer elsewhere.
|
|
43970
44034
|
|
|
43971
44035
|
Both tools run in-process and need no machine or shell \u2014 never shell out to \`todoregistry-cli\` / \`todoforai-cli\` for this.
|
|
43972
44036
|
|
|
43973
|
-
3.
|
|
44037
|
+
3. Propose 3-6 todos (highest priority first). Then reply with a one-line summary per proposal grouped by workstream, and tell the user they're waiting on the board for review.`,
|
|
43974
44038
|
mcpConfigs: {},
|
|
43975
44039
|
edgesMcpConfigs: {},
|
|
43976
44040
|
permissions: {
|
|
@@ -44014,6 +44078,9 @@ var OUTPUT_POLICIES = {
|
|
|
44014
44078
|
full: { firstLimit: Infinity, lastLimit: 0, hardCap: RUN_OUTPUT_CAP, lineLimit: MAX_LINE_LEN },
|
|
44015
44079
|
raw: { firstLimit: Infinity, lastLimit: 0, hardCap: Infinity, lineLimit: Infinity }
|
|
44016
44080
|
};
|
|
44081
|
+
// ../packages/shared-fbe/src/toolInstallCommand.ts
|
|
44082
|
+
var PYTHON_MIN_MINOR = 10;
|
|
44083
|
+
var PYTHON_VERSION_CHECK = `-c 'import sys; sys.exit(0 if sys.version_info[:2] >= (3, ${PYTHON_MIN_MINOR}) else 1)'`;
|
|
44017
44084
|
// ../packages/shared-fbe/src/realtime/topics.ts
|
|
44018
44085
|
var TOPICS = {
|
|
44019
44086
|
["todo:new" /* NEW_TODO */]: {
|
|
@@ -44052,10 +44119,6 @@ var TOPICS = {
|
|
|
44052
44119
|
channel: (p) => SERVER_TO_FRONTENDS.hexGrid.updated(p.projectId),
|
|
44053
44120
|
audience: "frontend"
|
|
44054
44121
|
},
|
|
44055
|
-
["placement:updated" /* PLACEMENT_UPDATED */]: {
|
|
44056
|
-
channel: (p) => SERVER_TO_FRONTENDS.placement.updated(p.projectId),
|
|
44057
|
-
audience: "frontend"
|
|
44058
|
-
},
|
|
44059
44122
|
["sandbox:state" /* SANDBOX_STATE */]: {
|
|
44060
44123
|
channel: (p) => SERVER_TO_FRONTENDS.sandbox.state(p.userId),
|
|
44061
44124
|
audience: "frontend"
|
|
@@ -44158,7 +44221,7 @@ import { parseArgs } from "util";
|
|
|
44158
44221
|
// package.json
|
|
44159
44222
|
var package_default = {
|
|
44160
44223
|
name: "@todoforai/cli",
|
|
44161
|
-
version: "0.1.
|
|
44224
|
+
version: "0.1.30",
|
|
44162
44225
|
type: "module",
|
|
44163
44226
|
bin: {
|
|
44164
44227
|
"todoforai-cli": "bin/todoforai-cli.js",
|
|
@@ -44215,7 +44278,10 @@ Usage:
|
|
|
44215
44278
|
todoforai-cli delete <todo-id> # Permanently delete a todo
|
|
44216
44279
|
todoforai-cli addmessage <todo-id> "text" # Add a message to an existing todo
|
|
44217
44280
|
todoforai-cli show <file|-> [todo-id] # Show a file in the chat (rendered by mimetype; - reads stdin)
|
|
44218
|
-
# [--title T] [--alias A] [--mime M] [--json]
|
|
44281
|
+
# [--title T] [--alias A] [--mime M] [--card <name>] [--json]
|
|
44282
|
+
todoforai-cli show list [todo-id] # List show blocks (ref, title, mime/url, card)
|
|
44283
|
+
# [--project <id>] [--card <name>] [--json]
|
|
44284
|
+
# no todo-id + --project (or $TODOFORAI_PROJECT_ID) = every todo
|
|
44219
44285
|
todoforai-cli open <url> [todo-id] # Show a live http(s) url in the chat as a preview
|
|
44220
44286
|
# [--title T] [--alias A] [--json]
|
|
44221
44287
|
todoforai-cli recommend --template <id> # Add a template as a recommendation card (see 'todoregistry-cli create')
|
|
@@ -44288,6 +44354,7 @@ function parseCliArgs() {
|
|
|
44288
44354
|
model: { type: "string" },
|
|
44289
44355
|
group: { type: "string" },
|
|
44290
44356
|
"group-name": { type: "string" },
|
|
44357
|
+
"group-description": { type: "string" },
|
|
44291
44358
|
"list-agents": { type: "boolean", default: false },
|
|
44292
44359
|
"list-models": { type: "boolean", default: false },
|
|
44293
44360
|
"api-url": { type: "string" },
|
|
@@ -44300,6 +44367,7 @@ function parseCliArgs() {
|
|
|
44300
44367
|
title: { type: "string" },
|
|
44301
44368
|
alias: { type: "string" },
|
|
44302
44369
|
mime: { type: "string" },
|
|
44370
|
+
card: { type: "string" },
|
|
44303
44371
|
direction: { type: "string" },
|
|
44304
44372
|
"business-context": { type: "string" },
|
|
44305
44373
|
seed: { type: "string" },
|
|
@@ -46772,6 +46840,32 @@ Cancelled by user (Ctrl+C)
|
|
|
46772
46840
|
`);
|
|
46773
46841
|
return;
|
|
46774
46842
|
}
|
|
46843
|
+
if (positionals[0] === "show" && positionals[1] === "list") {
|
|
46844
|
+
const explicitProject = !!args.project;
|
|
46845
|
+
const todoId = positionals[2] || (!explicitProject ? getEnv("TODO_ID") || cfgScope.data.last_todo_id : undefined);
|
|
46846
|
+
const projectId2 = todoId ? undefined : args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
|
|
46847
|
+
if (!todoId && !projectId2) {
|
|
46848
|
+
process.stderr.write(`${RED}Usage: todoforai-cli show list [todo-id] [--project <id>] [--card <name>]${RESET}
|
|
46849
|
+
`);
|
|
46850
|
+
process.exit(2);
|
|
46851
|
+
}
|
|
46852
|
+
const { items } = await api.listShows({ todoId, projectId: projectId2, card: args.card });
|
|
46853
|
+
if (args.json) {
|
|
46854
|
+
console.log(JSON.stringify(items, null, 2));
|
|
46855
|
+
return;
|
|
46856
|
+
}
|
|
46857
|
+
if (items.length === 0) {
|
|
46858
|
+
process.stderr.write(`${DIM}No show blocks in ${todoId || `project ${projectId2}`}${RESET}
|
|
46859
|
+
`);
|
|
46860
|
+
return;
|
|
46861
|
+
}
|
|
46862
|
+
for (const it of items) {
|
|
46863
|
+
const kind = it.url ? `url ${it.url}` : it.mime || "";
|
|
46864
|
+
const card = it.cardRef ? ` card=${it.cardRef}` : "";
|
|
46865
|
+
console.log(`${it.ref} ${it.title || it.filename || ""} ${kind}${card}`);
|
|
46866
|
+
}
|
|
46867
|
+
return;
|
|
46868
|
+
}
|
|
46775
46869
|
if (positionals[0] === "show") {
|
|
46776
46870
|
const [, filePath, todoArg] = positionals;
|
|
46777
46871
|
const todoId = todoArg || getEnv("TODO_ID") || cfgScope.data.last_todo_id;
|
|
@@ -46799,7 +46893,7 @@ Cancelled by user (Ctrl+C)
|
|
|
46799
46893
|
blob = file;
|
|
46800
46894
|
name = path3.basename(filePath);
|
|
46801
46895
|
}
|
|
46802
|
-
const res = await api.showFile(todoId, blob, name, { title: args.title, alias: args.alias, mime: args.mime });
|
|
46896
|
+
const res = await api.showFile(todoId, blob, name, { title: args.title, alias: args.alias, mime: args.mime, card: args.card });
|
|
46803
46897
|
if (args.json)
|
|
46804
46898
|
console.log(JSON.stringify(res, null, 2));
|
|
46805
46899
|
else
|
|
@@ -46824,7 +46918,7 @@ Cancelled by user (Ctrl+C)
|
|
|
46824
46918
|
if (positionals[0] === "recommend") {
|
|
46825
46919
|
const templateId = args.template || positionals[1];
|
|
46826
46920
|
if (!templateId) {
|
|
46827
|
-
process.stderr.write(`${RED}Usage: todoforai-cli recommend --template <id> [--note "why"] [--priority high|medium|low] [--title "..."] [--project <id>]${RESET}
|
|
46921
|
+
process.stderr.write(`${RED}Usage: todoforai-cli recommend --template <id> [--note "why"] [--priority high|medium|low] [--title "..."] [--group <slug> [--group-name "..."] [--group-description "..."]] [--project <id>]${RESET}
|
|
46828
46922
|
`);
|
|
46829
46923
|
process.stderr.write(`${DIM}Create a template first with: todoregistry-cli create --name ... --description ... --body @prompt.md${RESET}
|
|
46830
46924
|
`);
|
|
@@ -46852,7 +46946,10 @@ Cancelled by user (Ctrl+C)
|
|
|
46852
46946
|
...args.title ? { title: args.title } : {},
|
|
46853
46947
|
...args.note ? { note: args.note } : {},
|
|
46854
46948
|
...priority ? { priority } : {},
|
|
46855
|
-
...args["business-context"] ? { businessContextId: args["business-context"] } : {}
|
|
46949
|
+
...args["business-context"] ? { businessContextId: args["business-context"] } : {},
|
|
46950
|
+
...args.group ? { group: args.group } : {},
|
|
46951
|
+
...args["group-name"] ? { groupName: args["group-name"] } : {},
|
|
46952
|
+
...args["group-description"] ? { groupDescription: args["group-description"] } : {}
|
|
46856
46953
|
});
|
|
46857
46954
|
if (args.json)
|
|
46858
46955
|
console.log(JSON.stringify(rec, null, 2));
|