@todoforai/cli 0.1.19 → 0.1.20
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/todoai.js +183 -41
- package/package.json +2 -1
package/dist/todoai.js
CHANGED
|
@@ -43147,6 +43147,7 @@ var package_default = {
|
|
|
43147
43147
|
postinstall: "rm -rf node_modules/@todoforai/edge && ln -s ../../../edge/bun node_modules/@todoforai/edge"
|
|
43148
43148
|
},
|
|
43149
43149
|
dependencies: {
|
|
43150
|
+
"@shared/credentials": "file:../packages/shared-credentials",
|
|
43150
43151
|
"@todoforai/update-notifier": "^0.1.0",
|
|
43151
43152
|
"cli-highlight": "^2.1.11",
|
|
43152
43153
|
"diff-match-patch": "^1.0.5",
|
|
@@ -43336,7 +43337,8 @@ var NON_SHELL_TYPES = new Set([
|
|
|
43336
43337
|
var USER_INPUT_BLOCK_TYPES = new Set([
|
|
43337
43338
|
"question" /* Question */,
|
|
43338
43339
|
"login_prompt" /* Login */,
|
|
43339
|
-
"business_onboarding" /* BusinessOnboarding
|
|
43340
|
+
"business_onboarding" /* BusinessOnboarding */,
|
|
43341
|
+
"open_browser" /* OpenBrowser */
|
|
43340
43342
|
]);
|
|
43341
43343
|
var EDGE_TOOL_TYPES = new Set([
|
|
43342
43344
|
"read" /* Read */,
|
|
@@ -43363,7 +43365,6 @@ var API_TOOL_TYPES = new Set([
|
|
|
43363
43365
|
"api_get_agent" /* ApiGetAgent */,
|
|
43364
43366
|
"api_update_agent" /* ApiUpdateAgent */,
|
|
43365
43367
|
"api_list_business_contexts" /* ApiListBusinessContexts */,
|
|
43366
|
-
"api_get_business_context" /* ApiGetBusinessContext */,
|
|
43367
43368
|
"api_get_context_item" /* ApiGetContextItem */,
|
|
43368
43369
|
"api_create_context_item" /* ApiCreateContextItem */,
|
|
43369
43370
|
"api_update_context_item" /* ApiUpdateContextItem */,
|
|
@@ -43396,6 +43397,9 @@ var SERVER_TO_FRONTENDS = {
|
|
|
43396
43397
|
hexGrid: {
|
|
43397
43398
|
updated: (projectId) => `${"hexgrid:updated" /* HEXGRID_UPDATED */}:${projectId}`
|
|
43398
43399
|
},
|
|
43400
|
+
recommendation: {
|
|
43401
|
+
updated: (projectId) => `${"recommendation:updated" /* RECOMMENDATION_UPDATED */}:${projectId}`
|
|
43402
|
+
},
|
|
43399
43403
|
sandbox: {
|
|
43400
43404
|
state: (userId) => `${"sandbox:state" /* SANDBOX_STATE */}:${userId}`
|
|
43401
43405
|
},
|
|
@@ -43839,15 +43843,70 @@ Cover these sections (skip any without data):
|
|
|
43839
43843
|
|
|
43840
43844
|
If anything important remains unclear, add an **Open Questions** section at the end.
|
|
43841
43845
|
|
|
43846
|
+
Maintain a single machine-readable **Growth Plan** block so the user's brand page can render a live presence map and a ranked, actionable growth plan. Write it as one fenced code block tagged \`growth-plan\` containing JSON (the user never sees this raw \u2014 it powers the presence hexes and the Growth Plan panel). Keep exactly one such block; rewrite it in full each time you update it. Concrete example (emit strictly valid JSON like this \u2014 no comments, no ranges, no "|" alternatives):
|
|
43847
|
+
|
|
43848
|
+
\`\`\`growth-plan
|
|
43849
|
+
{
|
|
43850
|
+
"presence": {
|
|
43851
|
+
"linkedin-networking": { "score": 45, "handle": "https://linkedin.com/company/example", "note": "Company page exists but posts infrequently" },
|
|
43852
|
+
"x-twitter-presence": { "score": 60, "handle": "@example", "note": "Active, regular posts" },
|
|
43853
|
+
"newsletter": { "score": null, "note": "No newsletter signup found" }
|
|
43854
|
+
},
|
|
43855
|
+
"recommendations": [
|
|
43856
|
+
{
|
|
43857
|
+
"activityId": "linkedin-networking",
|
|
43858
|
+
"title": "Publish two founder-led LinkedIn posts each week",
|
|
43859
|
+
"rationale": "Target buyers are active on LinkedIn, but the page is nearly dormant.",
|
|
43860
|
+
"angle": "Explain the operational pain through short customer scenarios.",
|
|
43861
|
+
"track": "Qualified profile visits and demo requests per week",
|
|
43862
|
+
"priority": "high"
|
|
43863
|
+
}
|
|
43864
|
+
]
|
|
43865
|
+
}
|
|
43866
|
+
\`\`\`
|
|
43867
|
+
|
|
43868
|
+
Rules for the Growth Plan block:
|
|
43869
|
+
- \`activityId\` 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.
|
|
43870
|
+
- \`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.
|
|
43871
|
+
- \`recommendations\`: 3-6 items. \`priority\` is exactly one of "high", "medium", "low"; put the single highest-leverage action first. Choose channels that fit THIS business's model, audience, and stage \u2014 don't recommend all 20; prioritize gaps where presence is weak/absent but impact is high. Make \`title\` a concrete next action, \`angle\` the actual message/positioning to use, and \`track\` a specific metric or cadence to record.
|
|
43872
|
+
- Keep it consistent with the prose Online Presence and Next Steps sections \u2014 the JSON is the structured mirror of that advice.
|
|
43873
|
+
|
|
43874
|
+
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.)
|
|
43875
|
+
|
|
43842
43876
|
Guide the user one step at a time \u2014 they should always know the single next thing to do:
|
|
43843
43877
|
- When you need input (brand voice, tone, target audience, which channel to prioritize), prefer the question tool with short multi-choice options over asking open-ended prose questions.
|
|
43844
43878
|
- After completing any analysis or context update, always close with a short **Next Steps** section: 2-4 concrete, prioritized tips tailored to this specific business (e.g. SEO/content gaps, social channels worth activating, positioning against the competitors you found, conversion or messaging improvements). Base tips on what you actually observed, keep each to one or two sentences, and lead with the one you'd do first.
|
|
43845
43879
|
|
|
43846
|
-
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
|
|
43880
|
+
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.
|
|
43881
|
+
|
|
43882
|
+
When the user asks you to recommend how to grow (e.g. "recommend growth TODOs", the brand page's guided suggestion), treat it as a growth-plan request: read the current business context (including any existing \`growth-plan\` block), research only where information is missing or stale, then update the business context with a refreshed Growth Plan block and reply in chat with the same ranked recommendations in plain language \u2014 lead with the single highest-leverage action and why. This must work at any point in the conversation, not just after a full website analysis.
|
|
43883
|
+
|
|
43884
|
+
Recommending actionable TODOs from your recommendations:
|
|
43885
|
+
When the user asks you to generate/recommend TODOs (e.g. "generate TODOs", the board's Generate button), turn your top recommendations into recommendation cards in the NEXT column. You NEVER create running todos \u2014 you only recommend templates by reference. Two tools, strict roles:
|
|
43886
|
+
|
|
43887
|
+
1. FIND OR CREATE A TEMPLATE (todoregistry-cli):
|
|
43888
|
+
- \`todoregistry-cli search <query>\` / \`todoregistry-cli list -c <category>\` / \`todoregistry-cli get <id>\` \u2014 prefer a matching existing template (proven playbook).
|
|
43889
|
+
- If none fits, CREATE one right there (auth is inherited from your shell):
|
|
43890
|
+
\`todoregistry-cli create --name "<name>" --description "<one line>" --body @<prompt-file>\` (or \`--body -\` for stdin). It prints the new template id on stdout.
|
|
43891
|
+
|
|
43892
|
+
2. RECOMMEND (todoforai-cli \u2014 references a template id onto the project; never creates a running todo):
|
|
43893
|
+
\`todoforai-cli recommend --template <id> --project <projectId> --business-context <businessContextId> --note "<why>" --priority high|medium|low\`
|
|
43894
|
+
Take <projectId> and <businessContextId> from your current session context and pass them explicitly. The card appears PAUSED in the NEXT column for the user to review before any credits are spent.
|
|
43895
|
+
|
|
43896
|
+
3. Recommend 3-6 templates matching your Growth Plan (highest priority first), then reply with a one-line summary per card and tell the user they're waiting in the NEXT column for review.`,
|
|
43847
43897
|
mcpConfigs: {},
|
|
43848
43898
|
edgesMcpConfigs: {},
|
|
43849
43899
|
permissions: {
|
|
43850
|
-
allow: [
|
|
43900
|
+
allow: [
|
|
43901
|
+
"builtin:webfetch",
|
|
43902
|
+
"builtin:google_search",
|
|
43903
|
+
"cloud:google_rag",
|
|
43904
|
+
"builtin:todoforai_api",
|
|
43905
|
+
"builtin:question",
|
|
43906
|
+
"cloud:BASH(cmd: todoregistry-cli *)",
|
|
43907
|
+
"cloud:BASH(cmd: todoforai-cli *)",
|
|
43908
|
+
"cloud:BASH(cmd: curl *)"
|
|
43909
|
+
],
|
|
43851
43910
|
ask: [],
|
|
43852
43911
|
deny: []
|
|
43853
43912
|
},
|
|
@@ -43914,6 +43973,10 @@ var TOPICS = {
|
|
|
43914
43973
|
channel: (p) => SERVER_TO_FRONTENDS.hexGrid.updated(p.projectId),
|
|
43915
43974
|
audience: "frontend"
|
|
43916
43975
|
},
|
|
43976
|
+
["recommendation:updated" /* RECOMMENDATION_UPDATED */]: {
|
|
43977
|
+
channel: (p) => SERVER_TO_FRONTENDS.recommendation.updated(p.projectId),
|
|
43978
|
+
audience: "frontend"
|
|
43979
|
+
},
|
|
43917
43980
|
["sandbox:state" /* SANDBOX_STATE */]: {
|
|
43918
43981
|
channel: (p) => SERVER_TO_FRONTENDS.sandbox.state(p.userId),
|
|
43919
43982
|
audience: "frontend"
|
|
@@ -44037,6 +44100,7 @@ Usage:
|
|
|
44037
44100
|
todoforai-cli status <todo-id> <STATUS> # Update a todo's status (run 'status --help' for the full list)
|
|
44038
44101
|
todoforai-cli delete <todo-id> # Permanently delete a todo
|
|
44039
44102
|
todoforai-cli addmessage <todo-id> "text" # Add a message to an existing todo
|
|
44103
|
+
todoforai-cli recommend --template <id> # Add a template as a NEXT-column card (see 'todoregistry-cli create')
|
|
44040
44104
|
|
|
44041
44105
|
Options:
|
|
44042
44106
|
--path <dir> Workspace path (default: cwd)
|
|
@@ -44045,6 +44109,7 @@ Options:
|
|
|
44045
44109
|
--list-agents List available agents (name, id, workspace paths) and exit
|
|
44046
44110
|
--api-url <url> API URL
|
|
44047
44111
|
--api-key <key> API key
|
|
44112
|
+
--user-id <id> Admin HTTP impersonation; requires --no-watch
|
|
44048
44113
|
--inspect, -i <todo-id>[@<slice>] Print chat log (read-only)
|
|
44049
44114
|
--template, -t <id> Start from a registry template
|
|
44050
44115
|
--input <key=value> Template input (repeatable)
|
|
@@ -44100,9 +44165,14 @@ function parseCliArgs() {
|
|
|
44100
44165
|
"list-agents": { type: "boolean", default: false },
|
|
44101
44166
|
"api-url": { type: "string" },
|
|
44102
44167
|
"api-key": { type: "string" },
|
|
44168
|
+
"user-id": { type: "string" },
|
|
44103
44169
|
inspect: { type: "string", short: "i" },
|
|
44104
44170
|
template: { type: "string", short: "t" },
|
|
44105
44171
|
input: { type: "string", multiple: true },
|
|
44172
|
+
note: { type: "string" },
|
|
44173
|
+
priority: { type: "string" },
|
|
44174
|
+
title: { type: "string" },
|
|
44175
|
+
"business-context": { type: "string" },
|
|
44106
44176
|
resume: { type: "string", short: "r" },
|
|
44107
44177
|
continue: { type: "boolean", short: "c", default: false },
|
|
44108
44178
|
"non-interactive": { type: "boolean", short: "n", default: false },
|
|
@@ -44550,17 +44620,22 @@ function ensureBridgeCredentials(apiUrl) {
|
|
|
44550
44620
|
}
|
|
44551
44621
|
function bridgeAlreadyRunning() {
|
|
44552
44622
|
if (process.platform === "win32")
|
|
44553
|
-
return false;
|
|
44554
|
-
|
|
44555
|
-
|
|
44623
|
+
return Promise.resolve(false);
|
|
44624
|
+
return new Promise((resolve) => {
|
|
44625
|
+
const r = spawn("pgrep", ["-f", "todoforai-bridge"]);
|
|
44626
|
+
let out = "";
|
|
44627
|
+
r.stdout.on("data", (d) => out += d);
|
|
44628
|
+
r.on("error", () => resolve(false));
|
|
44629
|
+
r.on("close", (code) => resolve(code === 0 && out.trim().length > 0));
|
|
44630
|
+
});
|
|
44556
44631
|
}
|
|
44557
|
-
function ensureBridgeRunning(apiUrl, _apiKey) {
|
|
44632
|
+
async function ensureBridgeRunning(apiUrl, _apiKey) {
|
|
44633
|
+
if (await bridgeAlreadyRunning())
|
|
44634
|
+
return;
|
|
44558
44635
|
if (!hasBridge()) {
|
|
44559
44636
|
console.error("\x1B[2mBridge not started: `todoforai-bridge` was not found on PATH. Install TODOforAI Bridge, or pass --no-bridge (or deprecated --no-edge) to silence this.\x1B[0m");
|
|
44560
44637
|
return;
|
|
44561
44638
|
}
|
|
44562
|
-
if (bridgeAlreadyRunning())
|
|
44563
|
-
return;
|
|
44564
44639
|
if (!ensureBridgeCredentials(apiUrl)) {
|
|
44565
44640
|
console.error("\x1B[33mBridge not started: `todoforai-bridge login` did not complete successfully.\x1B[0m");
|
|
44566
44641
|
return;
|
|
@@ -44784,40 +44859,38 @@ class ScopedConfig {
|
|
|
44784
44859
|
}
|
|
44785
44860
|
}
|
|
44786
44861
|
|
|
44787
|
-
// src/
|
|
44788
|
-
import {
|
|
44789
|
-
import {
|
|
44862
|
+
// ../packages/shared-credentials/src/index.ts
|
|
44863
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, chmodSync } from "fs";
|
|
44864
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
44790
44865
|
import { homedir as homedir2, platform as platform2 } from "os";
|
|
44791
44866
|
function credentialsPath2() {
|
|
44792
|
-
|
|
44793
|
-
if (sys === "win32")
|
|
44794
|
-
return join2(homedir2(), "AppData", "Roaming", "todoforai", "credentials.json");
|
|
44795
|
-
if (sys === "darwin")
|
|
44796
|
-
return join2(homedir2(), "Library", "Application Support", "todoforai", "credentials.json");
|
|
44797
|
-
const xdg = process.env.XDG_CONFIG_HOME || join2(homedir2(), ".config");
|
|
44798
|
-
return join2(xdg, "todoforai", "credentials.json");
|
|
44867
|
+
return platform2() === "win32" ? join2(homedir2(), "AppData", "Roaming", "todoforai", "credentials.json") : platform2() === "darwin" ? join2(homedir2(), "Library", "Application Support", "todoforai", "credentials.json") : join2(process.env.XDG_CONFIG_HOME || join2(homedir2(), ".config"), "todoforai", "credentials.json");
|
|
44799
44868
|
}
|
|
44800
|
-
var CREDENTIALS_PATH2 = credentialsPath2();
|
|
44801
44869
|
function read() {
|
|
44802
|
-
if (!existsSync2(CREDENTIALS_PATH2))
|
|
44803
|
-
return {};
|
|
44804
44870
|
try {
|
|
44805
|
-
return JSON.parse(readFileSync2(
|
|
44871
|
+
return JSON.parse(readFileSync2(credentialsPath2(), "utf-8"));
|
|
44806
44872
|
} catch {
|
|
44807
44873
|
return {};
|
|
44808
44874
|
}
|
|
44809
44875
|
}
|
|
44876
|
+
function writeSecure(data) {
|
|
44877
|
+
const p = credentialsPath2();
|
|
44878
|
+
mkdirSync2(dirname2(p), { recursive: true });
|
|
44879
|
+
writeFileSync2(p, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
44880
|
+
try {
|
|
44881
|
+
chmodSync(p, 384);
|
|
44882
|
+
} catch {}
|
|
44883
|
+
}
|
|
44810
44884
|
function readCredential(apiUrl) {
|
|
44811
|
-
const
|
|
44885
|
+
const c2 = read();
|
|
44886
|
+
const v = c2[apiUrl] || c2.apiToken || c2.apiKey;
|
|
44812
44887
|
return typeof v === "string" ? v : "";
|
|
44813
44888
|
}
|
|
44814
44889
|
function writeCredential(apiUrl, apiKey) {
|
|
44815
|
-
const
|
|
44816
|
-
|
|
44817
|
-
|
|
44818
|
-
writeFileSync2(CREDENTIALS_PATH2, JSON.stringify(data, null, 2), "utf-8");
|
|
44890
|
+
const c2 = read();
|
|
44891
|
+
c2[apiUrl] = apiKey;
|
|
44892
|
+
writeSecure(c2);
|
|
44819
44893
|
}
|
|
44820
|
-
|
|
44821
44894
|
// src/logo.ts
|
|
44822
44895
|
var LETTERS = {
|
|
44823
44896
|
t: [" x ", "xxxx", " x ", " xll", " xll", " xxx"],
|
|
@@ -44828,11 +44901,10 @@ var LETTERS = {
|
|
|
44828
44901
|
c: [" ", "xxxx", "x ", "xlll", "xlll", "xxxx"],
|
|
44829
44902
|
e: [" ", "xxxx", "x x", "xxxx", "xlll", "xxxx"],
|
|
44830
44903
|
a: [" ", "xxxx", " x", "xxxx", "xllx", "xxxx"],
|
|
44831
|
-
i: ["x", " ", "x", "x", "x", "x"]
|
|
44832
|
-
"4": [" ", " x ", " xx ", "xlxl", "xxxx", "llxl"]
|
|
44904
|
+
i: ["x", " ", "x", "x", "x", "x"]
|
|
44833
44905
|
};
|
|
44834
44906
|
var GAP = " ";
|
|
44835
|
-
var WORD = "
|
|
44907
|
+
var WORD = "todoforai";
|
|
44836
44908
|
function renderHalfBlock(top, bot) {
|
|
44837
44909
|
const W = c("\x1B[38;2;249;110;46m");
|
|
44838
44910
|
const G = c("\x1B[38;2;140;60;20m");
|
|
@@ -46419,8 +46491,8 @@ Cancelled by user (Ctrl+C)
|
|
|
46419
46491
|
return;
|
|
46420
46492
|
}
|
|
46421
46493
|
if (args["reset-config"]) {
|
|
46422
|
-
const { existsSync:
|
|
46423
|
-
if (
|
|
46494
|
+
const { existsSync: existsSync2, unlinkSync } = await import("fs");
|
|
46495
|
+
if (existsSync2(cfg.path)) {
|
|
46424
46496
|
unlinkSync(cfg.path);
|
|
46425
46497
|
console.log(`Configuration reset: ${formatPathWithTilde(cfg.path)}`);
|
|
46426
46498
|
} else
|
|
@@ -46429,6 +46501,15 @@ Cancelled by user (Ctrl+C)
|
|
|
46429
46501
|
}
|
|
46430
46502
|
const apiUrl = normalizeApiUrl(args["api-url"] || getEnv("API_URL") || DEFAULT_API_URL);
|
|
46431
46503
|
const cfgScope = cfg.scope(apiUrl);
|
|
46504
|
+
if (args["user-id"]) {
|
|
46505
|
+
const url = new URL(apiUrl);
|
|
46506
|
+
const loopback = ["localhost", "127.0.0.1", "::1", "[::1]"].includes(url.hostname);
|
|
46507
|
+
if (url.protocol !== "http:" || !loopback) {
|
|
46508
|
+
process.stderr.write(`Error: --user-id is restricted to HTTP loopback URLs
|
|
46509
|
+
`);
|
|
46510
|
+
process.exit(2);
|
|
46511
|
+
}
|
|
46512
|
+
}
|
|
46432
46513
|
async function deviceLogin() {
|
|
46433
46514
|
const loginApi = new ApiClient(apiUrl, "");
|
|
46434
46515
|
const { code, url, expiresIn } = await loginApi.initDeviceLogin("edge");
|
|
@@ -46488,7 +46569,22 @@ Cancelled by user (Ctrl+C)
|
|
|
46488
46569
|
if (!apiKey) {
|
|
46489
46570
|
apiKey = await deviceLogin();
|
|
46490
46571
|
}
|
|
46491
|
-
const api = new ApiClient(apiUrl, apiKey);
|
|
46572
|
+
const api = new ApiClient(apiUrl, apiKey, args["user-id"]);
|
|
46573
|
+
if (args["user-id"] && args.safe) {
|
|
46574
|
+
process.stderr.write(`Error: --safe is not supported with --user-id
|
|
46575
|
+
`);
|
|
46576
|
+
process.exit(2);
|
|
46577
|
+
}
|
|
46578
|
+
if (args["user-id"] && (args.resume || args.continue)) {
|
|
46579
|
+
process.stderr.write(`Error: resume is not supported with --user-id
|
|
46580
|
+
`);
|
|
46581
|
+
process.exit(2);
|
|
46582
|
+
}
|
|
46583
|
+
if (args["user-id"] && args.template && !args["no-watch"]) {
|
|
46584
|
+
process.stderr.write(`Error: --user-id uses admin HTTP impersonation and requires --no-watch
|
|
46585
|
+
`);
|
|
46586
|
+
process.exit(2);
|
|
46587
|
+
}
|
|
46492
46588
|
if (positionals[0] === "status") {
|
|
46493
46589
|
const [, todoId, status] = positionals;
|
|
46494
46590
|
if (!todoId || !status) {
|
|
@@ -46526,6 +46622,46 @@ Cancelled by user (Ctrl+C)
|
|
|
46526
46622
|
console.log(JSON.stringify(msg, null, 2));
|
|
46527
46623
|
else
|
|
46528
46624
|
process.stderr.write(`${GREEN}\u2705 Message added to ${todoId}${RESET}
|
|
46625
|
+
`);
|
|
46626
|
+
return;
|
|
46627
|
+
}
|
|
46628
|
+
if (positionals[0] === "recommend") {
|
|
46629
|
+
const templateId = args.template || positionals[1];
|
|
46630
|
+
if (!templateId) {
|
|
46631
|
+
process.stderr.write(`${RED}Usage: todoforai-cli recommend --template <id> [--note "why"] [--priority high|medium|low] [--title "..."] [--project <id>]${RESET}
|
|
46632
|
+
`);
|
|
46633
|
+
process.stderr.write(`${DIM}Create a template first with: todoregistry-cli create --name ... --description ... --body @prompt.md${RESET}
|
|
46634
|
+
`);
|
|
46635
|
+
process.exit(2);
|
|
46636
|
+
}
|
|
46637
|
+
let projectId2 = args.project || cfgScope.data.default_project_id;
|
|
46638
|
+
if (!projectId2) {
|
|
46639
|
+
const projects2 = await api.listProjects();
|
|
46640
|
+
projectId2 = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
|
|
46641
|
+
}
|
|
46642
|
+
if (!projectId2) {
|
|
46643
|
+
process.stderr.write(`${RED}No project found \u2014 pass --project <id>${RESET}
|
|
46644
|
+
`);
|
|
46645
|
+
process.exit(2);
|
|
46646
|
+
}
|
|
46647
|
+
const priority = args.priority;
|
|
46648
|
+
if (priority && !["high", "medium", "low"].includes(priority)) {
|
|
46649
|
+
process.stderr.write(`${RED}--priority must be high, medium, or low${RESET}
|
|
46650
|
+
`);
|
|
46651
|
+
process.exit(2);
|
|
46652
|
+
}
|
|
46653
|
+
const rec = await api.recommend({
|
|
46654
|
+
projectId: projectId2,
|
|
46655
|
+
templateId,
|
|
46656
|
+
...args.title ? { title: args.title } : {},
|
|
46657
|
+
...args.note ? { note: args.note } : {},
|
|
46658
|
+
...priority ? { priority } : {},
|
|
46659
|
+
...args["business-context"] ? { businessContextId: args["business-context"] } : {}
|
|
46660
|
+
});
|
|
46661
|
+
if (args.json)
|
|
46662
|
+
console.log(JSON.stringify(rec, null, 2));
|
|
46663
|
+
else
|
|
46664
|
+
process.stderr.write(`${GREEN}\u2705 Recommended template ${templateId} on project ${projectId2}${RESET}
|
|
46529
46665
|
`);
|
|
46530
46666
|
return;
|
|
46531
46667
|
}
|
|
@@ -46706,6 +46842,15 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
|
|
|
46706
46842
|
await ws2.close();
|
|
46707
46843
|
return;
|
|
46708
46844
|
}
|
|
46845
|
+
if (args["user-id"] && !args["no-watch"]) {
|
|
46846
|
+
process.stderr.write(`Error: --user-id uses admin HTTP impersonation and requires --no-watch
|
|
46847
|
+
`);
|
|
46848
|
+
process.exit(2);
|
|
46849
|
+
}
|
|
46850
|
+
const ws = args["no-watch"] ? null : new FrontendWebSocket(apiUrl, apiKey);
|
|
46851
|
+
const wsReady = ws ? ws.connect() : null;
|
|
46852
|
+
if (!args["no-bridge"] && !args["no-watch"])
|
|
46853
|
+
ensureBridgeRunning(apiUrl, apiKey);
|
|
46709
46854
|
let preMatchedAgent = null;
|
|
46710
46855
|
let agents = null;
|
|
46711
46856
|
if (args.agent) {
|
|
@@ -46757,8 +46902,6 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
|
|
|
46757
46902
|
}
|
|
46758
46903
|
process.stderr.write(`${DIM}Tip: ${randomTip()}${RESET}
|
|
46759
46904
|
`);
|
|
46760
|
-
if (!args["no-bridge"] && !args["no-watch"])
|
|
46761
|
-
ensureBridgeRunning(apiUrl, apiKey);
|
|
46762
46905
|
let content;
|
|
46763
46906
|
if (positionals.length > 0) {
|
|
46764
46907
|
content = positionals.join(" ");
|
|
@@ -46800,9 +46943,8 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
|
|
|
46800
46943
|
} else {
|
|
46801
46944
|
agent = await selectAgent(agents, cfgScope.data.default_agent_name, (name, settings) => cfgScope.setDefaultAgent(name, settings));
|
|
46802
46945
|
}
|
|
46803
|
-
|
|
46804
|
-
|
|
46805
|
-
await ws.connect();
|
|
46946
|
+
if (wsReady)
|
|
46947
|
+
await wsReady;
|
|
46806
46948
|
if (args.model)
|
|
46807
46949
|
agent = { ...agent, model: args.model };
|
|
46808
46950
|
if (args["raw-sysmsg"]) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@todoforai/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"todoforai-cli": "dist/todoai.js",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"dev": "bun run src/index.ts"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"@shared/credentials": "file:../packages/shared-credentials",
|
|
19
20
|
"@todoforai/update-notifier": "^0.1.0",
|
|
20
21
|
"cli-highlight": "^2.1.11",
|
|
21
22
|
"diff-match-patch": "^1.0.5",
|