@todoforai/cli 0.1.71 → 0.1.73
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 +132 -43
- package/package.json +1 -1
package/dist/todoforai-cli.js
CHANGED
|
@@ -43281,6 +43281,10 @@ class ApiClient {
|
|
|
43281
43281
|
const { projectId, ...body } = input;
|
|
43282
43282
|
return this.request("POST", `/api/v1/projects/${projectId}/recommendations`, body);
|
|
43283
43283
|
}
|
|
43284
|
+
async feedIgnore(input) {
|
|
43285
|
+
const { projectId, ...body } = input;
|
|
43286
|
+
return this.request("POST", `/api/v1/projects/${projectId}/feed/ignored`, body);
|
|
43287
|
+
}
|
|
43284
43288
|
async getRegistrySpec(specId) {
|
|
43285
43289
|
const base = this.apiUrl.replace(/\/api\/v1\/?$/, "").replace(/\/$/, "");
|
|
43286
43290
|
const res = await fetch(`${base}/cookie/v1/registry/templates/${specId}`);
|
|
@@ -44079,7 +44083,9 @@ var SERVER_TO_FRONTENDS = {
|
|
|
44079
44083
|
updated: (userId) => `${"business_context:updated" /* BUSINESS_CONTEXT_UPDATED */}:${userId}`
|
|
44080
44084
|
},
|
|
44081
44085
|
resource: {
|
|
44082
|
-
contentUpdated: (userId) => `${"resource:content_updated" /* RESOURCE_CONTENT_UPDATED */}:${userId}
|
|
44086
|
+
contentUpdated: (userId) => `${"resource:content_updated" /* RESOURCE_CONTENT_UPDATED */}:${userId}`,
|
|
44087
|
+
contentUpdatedInTodo: (todoId) => `${"resource:content_updated" /* RESOURCE_CONTENT_UPDATED */}:todo:${todoId}`,
|
|
44088
|
+
contentUpdatedInProject: (projectId) => `${"resource:content_updated" /* RESOURCE_CONTENT_UPDATED */}:project:${projectId}`
|
|
44083
44089
|
},
|
|
44084
44090
|
hexGrid: {
|
|
44085
44091
|
updated: (projectId) => `${"hexgrid:updated" /* HEXGRID_UPDATED */}:${projectId}`
|
|
@@ -44132,6 +44138,7 @@ var SERVER_TO_FRONTENDS = {
|
|
|
44132
44138
|
start_universal: (todoId) => `${"block:start_universal" /* BLOCK_START_UNIVERSAL */}:${todoId}`,
|
|
44133
44139
|
message: (todoId) => `${"block:message" /* BLOCK_MESSAGE */}:${todoId}`,
|
|
44134
44140
|
end: (todoId) => `${"block:end" /* BLOCK_END */}:${todoId}`,
|
|
44141
|
+
toolDelta: (todoId) => `${"block:tool_delta" /* BLOCK_TOOL_DELTA */}:${todoId}`,
|
|
44135
44142
|
new_message: (todoId) => `${"todo:new_message" /* TODO_NEW_MESSAGE */}:${todoId}`,
|
|
44136
44143
|
sh_msg_result: (todoId) => `${"block:sh_msg_result" /* BLOCK_SH_MSG_RESULT */}:${todoId}`,
|
|
44137
44144
|
sh_msg_start_result: (todoId) => `${"block:sh_msg_start" /* BLOCK_SH_MSG_START */}:${todoId}`,
|
|
@@ -44218,6 +44225,7 @@ var USER_HEADINGS = new Set([
|
|
|
44218
44225
|
]);
|
|
44219
44226
|
// ../packages/shared-fbe/src/bridgeProtocol.ts
|
|
44220
44227
|
var ADB_DATA_CHUNK = 40 * 1024;
|
|
44228
|
+
var SHELL_DATA_CHUNK = 40 * 1024;
|
|
44221
44229
|
// ../packages/shared-fbe/src/bashPatterns.ts
|
|
44222
44230
|
function splitShellCommands(input) {
|
|
44223
44231
|
const parts = [];
|
|
@@ -44633,32 +44641,15 @@ To materialize growth recommendations, propose todos on the project board. A pro
|
|
|
44633
44641
|
|
|
44634
44642
|
Exactly ONE \`api_recommend_template\` call per proposal: it carries both what the todo is and where it lands on the board.
|
|
44635
44643
|
|
|
44636
|
-
1. WHAT IT IS. Pass \`name\`
|
|
44644
|
+
1. WHAT IT IS. Pass \`name\`, \`description\` and \`body\`, written for THIS business.
|
|
44637
44645
|
- Reuse an existing registry template instead ONLY when it already fits this business closely with no meaningful edits: pass its id as \`templateId\` and omit \`name\`/\`body\`. If it needs tailoring, write yours inline. Do not recommend a generic playbook that ignores this company's specifics.
|
|
44638
44646
|
|
|
44639
|
-
|
|
44640
|
-
|
|
44641
|
-
|
|
44642
|
-
|
|
44643
|
-
Hard limits: body ≤80 words total. One sentence of persona. One-sentence task. 2-4 steps, each a short fragment of at most 8 words naming WHAT, not how. Data needed is a comma-separated list. If a sentence wouldn't change what the agent does, delete it.
|
|
44644
|
-
|
|
44645
|
-
\`name\`: 3-6 words, action-first like "Win Reddit comparison threads", with no company name prefix. Give \`description\` and \`short\` one tight sentence each.
|
|
44647
|
+
A person reads it at a glance and decides whether to run it, so use as few words as possible. The executing agent is capable, reads the business context at run time and asks for anything it is missing, so give no steps, no list of inputs and no background.
|
|
44648
|
+
- \`name\`: 3-6 words, action-first like "Win Reddit comparison threads", with no company name prefix.
|
|
44649
|
+
- \`description\`: one plain sentence of at most 15 words saying what the user gets, such as "Three vs-Gmail pages, drafted and ready to publish." Never repeat the name.
|
|
44650
|
+
- \`body\`: one sentence naming the expert to act as, such as "You are an SEO writer for email products." Nothing else: no headings, no task, no steps.
|
|
44646
44651
|
|
|
44647
|
-
|
|
44648
|
-
"You are a B2B outbound specialist for dev-tool companies.
|
|
44649
|
-
Your task:
|
|
44650
|
-
Land Acme in 10 agency partner conversations this month.
|
|
44651
|
-
Steps:
|
|
44652
|
-
- Shortlist 30 EU automation agencies
|
|
44653
|
-
- Draft one-line personalized openers
|
|
44654
|
-
- Send, track replies in a sheet
|
|
44655
|
-
Data needed:
|
|
44656
|
-
Acme pricing page, current partner list, founder's LinkedIn"
|
|
44657
|
-
|
|
44658
|
-
Body structure (REQUIRED). The body is split into the agent's system prompt and the first user turn by section headings, so follow this exactly:
|
|
44659
|
-
- Start with the persona, expertise and any Rules as plain prose at the top with no heading. This becomes the agent's system prompt.
|
|
44660
|
-
- Then write the request using these exact standalone heading lines, letters only, each on its own line and ending with a colon: \`Your task:\`, \`Steps:\`, \`Data needed:\`. Everything under these headings becomes the first user turn.
|
|
44661
|
-
- Headings must match exactly or the split breaks. Write \`Your task:\`, not \`Your task (SEO):\`. Put the business-specific detail in the body text under each heading, never in the heading itself.
|
|
44652
|
+
Language: write the name, description, body and note in the language the company itself communicates in. When the company is international or unclear, use English.
|
|
44662
44653
|
|
|
44663
44654
|
2. WHERE IT LANDS, in that same call. It becomes a PROPOSED todo on the project and never a running todo:
|
|
44664
44655
|
Give it a one-line \`note\` of roughly 15 words at most giving the WHY for THIS business, which becomes the proposal's tooltip, a \`priority\` of high, medium or low. Leave \`group\` empty: proposals land on the board without a group, and you never create, rename or archive groups. Project and business context default to the current session, so pass them only when targeting a different one. The proposal appears as a ✧ row on the board for the user to review before any credits are spent. Re-proposing the same \`templateId\` refreshes that open proposal instead of duplicating it. An inline proposal is always a new one, so check the board you just read before repeating a move that is already there. Dismissed proposals since your last run are fed back to you. Don't re-propose those, steer elsewhere.
|
|
@@ -45607,6 +45598,16 @@ var tool_catalog_default = {
|
|
|
45607
45598
|
installCmd: "npm install -g @todoforai/tfa-summary",
|
|
45608
45599
|
internal: true
|
|
45609
45600
|
},
|
|
45601
|
+
"tfa-cat": {
|
|
45602
|
+
category: "todoforai",
|
|
45603
|
+
pkg: "tfa-cat",
|
|
45604
|
+
installer: "system",
|
|
45605
|
+
label: "Cat",
|
|
45606
|
+
summary: "Show images and PDFs from the shell to the agent.",
|
|
45607
|
+
description: "`cat` that lets you SEE media: `tfa-cat shot.png` (or `… | tfa-cat`) attaches images/PDFs to this bash result; text prints like cat; its stdout redirected/piped → raw bytes. Also `todoforai:<path>` files (`tfa-cat todoforai:todos/<id>/data.csv > data.csv`; binaries like docx/xlsx: redirect, then edit with your tools). Big text costs context: read what you need (`| head`, `| sed -n 40,80p`, `| grep -n`).",
|
|
45608
|
+
versionCmd: "tfa-cat --version 2>/dev/null | sed -n 1p",
|
|
45609
|
+
internal: true
|
|
45610
|
+
},
|
|
45610
45611
|
"tfa-handoff": {
|
|
45611
45612
|
preinstallCloud: true,
|
|
45612
45613
|
category: "todoforai",
|
|
@@ -50352,6 +50353,10 @@ var TOPICS = {
|
|
|
50352
50353
|
channel: (p) => SERVER_TO_FRONTENDS.todo.msgStopSequence(p.todoId),
|
|
50353
50354
|
audience: "frontend"
|
|
50354
50355
|
},
|
|
50356
|
+
["block:tool_delta" /* BLOCK_TOOL_DELTA */]: {
|
|
50357
|
+
channel: (p) => SERVER_TO_FRONTENDS.todo.toolDelta(p.todoId),
|
|
50358
|
+
audience: "frontend"
|
|
50359
|
+
},
|
|
50355
50360
|
["block:diff_result" /* BLOCK_DIFF_RESULT */]: {
|
|
50356
50361
|
channel: (p) => SERVER_TO_FRONTENDS.todo.diff_result(p.todoId),
|
|
50357
50362
|
audience: "frontend"
|
|
@@ -50427,7 +50432,7 @@ var package_default = {
|
|
|
50427
50432
|
type: "git",
|
|
50428
50433
|
url: "git+https://github.com/todoforai/cli.git"
|
|
50429
50434
|
},
|
|
50430
|
-
version: "0.1.
|
|
50435
|
+
version: "0.1.73",
|
|
50431
50436
|
type: "module",
|
|
50432
50437
|
bin: {
|
|
50433
50438
|
"todoforai-cli": "bin/todoforai-cli.js",
|
|
@@ -50496,7 +50501,8 @@ Examples:
|
|
|
50496
50501
|
tfa-cli show list|ref|rm … # List / re-surface / take down shown files
|
|
50497
50502
|
tfa-cli open <url> [todo-id] # Live url preview in the chat
|
|
50498
50503
|
tfa-cli connect <key>… [--reason "why"] # Sign-in card for connectors on this machine (several keys = one choice)
|
|
50499
|
-
tfa-cli recommend --template <id>
|
|
50504
|
+
tfa-cli recommend --template <id> | --title "…" [--info] [--source "label|ref|url"] # ✧ proposal card, or an ⓘ info row
|
|
50505
|
+
tfa-cli ignore --title "…" [--source "label|ref|url|from"] [--note "why"] # Log an inbound item as noise (folded under the Feed card)
|
|
50500
50506
|
tfa-cli inbox [needs-you|messages|activity] [--unread] # Read the notification feed (the bell)
|
|
50501
50507
|
tfa-cli inbox seen [tab] # Mark notifications read
|
|
50502
50508
|
tfa-cli notify [--to <email>] "Title" "message" [--href /t/<id>] [--url <link>] # Note to your own user, or --to a project member (Messages tab + phone push)
|
|
@@ -50624,6 +50630,8 @@ function parseCliArgs() {
|
|
|
50624
50630
|
inspect: { type: "string", short: "i" },
|
|
50625
50631
|
template: { type: "string", short: "t" },
|
|
50626
50632
|
note: { type: "string" },
|
|
50633
|
+
info: { type: "boolean", default: false },
|
|
50634
|
+
source: { type: "string" },
|
|
50627
50635
|
priority: { type: "string" },
|
|
50628
50636
|
title: { type: "string" },
|
|
50629
50637
|
alias: { type: "string" },
|
|
@@ -51400,6 +51408,16 @@ class ScopedConfig {
|
|
|
51400
51408
|
s.recent_projects = [{ id, name: name || id }, ...s.recent_projects.filter((p) => p.id !== id)].slice(0, 10);
|
|
51401
51409
|
this.store.save();
|
|
51402
51410
|
}
|
|
51411
|
+
cachedUserId(tokenFp) {
|
|
51412
|
+
const s = this.data;
|
|
51413
|
+
return s.user_id && s.user_id_token_fp === tokenFp ? s.user_id : null;
|
|
51414
|
+
}
|
|
51415
|
+
setUserId(id, tokenFp) {
|
|
51416
|
+
const s = this.data;
|
|
51417
|
+
s.user_id = id;
|
|
51418
|
+
s.user_id_token_fp = tokenFp;
|
|
51419
|
+
this.store.save();
|
|
51420
|
+
}
|
|
51403
51421
|
setDefaultAgent(name, settings) {
|
|
51404
51422
|
const s = this.data;
|
|
51405
51423
|
s.default_agent_name = name;
|
|
@@ -53447,10 +53465,9 @@ Examples:
|
|
|
53447
53465
|
tfa-cli agent update <agent> model=anthropic:anthropic/claude-opus-5 temperature=0.5
|
|
53448
53466
|
tfa-cli agent update <agent> sysmsg="You are a terse video editor."
|
|
53449
53467
|
tfa-cli agent create name=Reviewer model=claude sysmsg="Review diffs only."
|
|
53468
|
+
tfa-cli agent update <agent> judgeSysmsg="Ask before sending email." (approval checker; ""=off, null=inherit global)
|
|
53450
53469
|
|
|
53451
|
-
|
|
53452
|
-
|
|
53453
|
-
From an agent shell (dst_ token) permissions, judgeSysmsg, edgesMcpConfigs, devicesConfig and delete are refused by the backend.
|
|
53470
|
+
From an agent shell (dst_ token) permissions, edgesMcpConfigs, devicesConfig and delete are refused by the backend.
|
|
53454
53471
|
`);
|
|
53455
53472
|
}
|
|
53456
53473
|
var FIELD_ALIASES = {
|
|
@@ -53852,6 +53869,7 @@ Fields (PUT /todos/{id}):
|
|
|
53852
53869
|
schedule unix ms timestamp, 0 = immediate (alias: scheduledTimestamp)
|
|
53853
53870
|
agent agentSettingsId (alias: agentSettingsId)
|
|
53854
53871
|
public true|false — refused from an agent shell (isPublic)
|
|
53872
|
+
kind task|info — promote an ⓘ info row to a task (or back)
|
|
53855
53873
|
|
|
53856
53874
|
tfa-cli todo message <todo-id|-> <message-id> <field=value>…
|
|
53857
53875
|
Edit a SCHEDULED (not yet run) message: content, schedule (unix ms;
|
|
@@ -54469,19 +54487,22 @@ var VALID_STATUS = new Set(Object.values(TodoStatus));
|
|
|
54469
54487
|
var isOpen = (s) => !CLOSED.has(s);
|
|
54470
54488
|
function printListTodosHelp() {
|
|
54471
54489
|
process.stderr.write(`
|
|
54472
|
-
todoforai-cli list — list todos in a project (recent first)
|
|
54490
|
+
todoforai-cli list — list YOUR todos in a project (recent first)
|
|
54491
|
+
|
|
54492
|
+
Default: only todos you opened or wrote in. --project-wide shows everyone's.
|
|
54473
54493
|
|
|
54474
54494
|
Usage:
|
|
54475
54495
|
todoforai-cli list [flags]
|
|
54476
54496
|
|
|
54477
54497
|
Flags:
|
|
54498
|
+
--project-wide Every member's todos, not just yours
|
|
54478
54499
|
-n, --limit <n> Max rows to show (default: 30)
|
|
54479
54500
|
--cursor <n> Fetch todos older than this cursor (sort field, epoch ms)
|
|
54480
54501
|
--since <t> Only todos with sort field >= t (inclusive)
|
|
54481
54502
|
--until <t> Only todos with sort field < t (exclusive)
|
|
54482
54503
|
t = epoch ms | YYYY-MM-DD (local midnight) | ISO datetime | 12h / 3d ago
|
|
54483
54504
|
--sort <key> activity (lastActivityAt, default) | created (createdAt)
|
|
54484
|
-
--contributor <u> Only todos a member opened/wrote in (id or email; comma = any)
|
|
54505
|
+
--contributor <u> Only todos a member opened/wrote in (id or email; comma = any; replaces the "mine" default)
|
|
54485
54506
|
--page-size <n> Backend page size per request (default: min(limit, 400), max: 400)
|
|
54486
54507
|
--search <text> Search content/status on the backend
|
|
54487
54508
|
-s, --status <S[,S2]> Filter by status (comma-separated, union).
|
|
@@ -54491,7 +54512,8 @@ Flags:
|
|
|
54491
54512
|
-h, --help Show this help
|
|
54492
54513
|
|
|
54493
54514
|
Examples:
|
|
54494
|
-
todoforai-cli list # 30 most recent open todos
|
|
54515
|
+
todoforai-cli list # 30 most recent open todos of yours
|
|
54516
|
+
todoforai-cli list --project-wide # … of everyone in the project
|
|
54495
54517
|
todoforai-cli list -n 50 # last 50 open
|
|
54496
54518
|
todoforai-cli list --cursor 1719234567890 # next page
|
|
54497
54519
|
todoforai-cli list --all # include DONE
|
|
@@ -54583,7 +54605,7 @@ function matchesRequestedStatus(t, requested, wanted, wantOpen) {
|
|
|
54583
54605
|
const s = String(t.status).toUpperCase();
|
|
54584
54606
|
return wantOpen && isOpen(s) || (wanted?.has(s) ?? false);
|
|
54585
54607
|
}
|
|
54586
|
-
async function listTodosCommand(api, defaultProjectId, argv) {
|
|
54608
|
+
async function listTodosCommand(api, defaultProjectId, argv, resolveUserId) {
|
|
54587
54609
|
const { values } = parseArgs2({
|
|
54588
54610
|
args: argv,
|
|
54589
54611
|
options: {
|
|
@@ -54595,6 +54617,7 @@ async function listTodosCommand(api, defaultProjectId, argv) {
|
|
|
54595
54617
|
until: { type: "string" },
|
|
54596
54618
|
sort: { type: "string" },
|
|
54597
54619
|
contributor: { type: "string" },
|
|
54620
|
+
"project-wide": { type: "boolean", default: false },
|
|
54598
54621
|
status: { type: "string", short: "s" },
|
|
54599
54622
|
all: { type: "boolean", short: "A", default: false },
|
|
54600
54623
|
project: { type: "string" },
|
|
@@ -54627,7 +54650,15 @@ async function listTodosCommand(api, defaultProjectId, argv) {
|
|
|
54627
54650
|
if (until !== undefined)
|
|
54628
54651
|
cursor = cursor === undefined ? until : Math.min(cursor, until);
|
|
54629
54652
|
const search = values.search ? String(values.search) : undefined;
|
|
54630
|
-
|
|
54653
|
+
let contributorIds = values.contributor ? await resolveContributors(api, projectId, String(values.contributor)) : undefined;
|
|
54654
|
+
let mineOnly = false;
|
|
54655
|
+
if (!contributorIds && !values["project-wide"] && resolveUserId) {
|
|
54656
|
+
const me = await resolveUserId();
|
|
54657
|
+
if (me) {
|
|
54658
|
+
contributorIds = [me];
|
|
54659
|
+
mineOnly = true;
|
|
54660
|
+
}
|
|
54661
|
+
}
|
|
54631
54662
|
const requested = values.status ? String(values.status).split(",").map((s) => s.trim().toUpperCase()).filter(Boolean) : null;
|
|
54632
54663
|
const unknown = requested?.filter((s) => s !== "OPEN" && !VALID_STATUS.has(s)) ?? [];
|
|
54633
54664
|
if (unknown.length)
|
|
@@ -54683,8 +54714,9 @@ async function listTodosCommand(api, defaultProjectId, argv) {
|
|
|
54683
54714
|
`);
|
|
54684
54715
|
return;
|
|
54685
54716
|
}
|
|
54717
|
+
const scopeHint = mineOnly ? " of yours (--project-wide for everyone's)" : "";
|
|
54686
54718
|
if (!visible.length) {
|
|
54687
|
-
process.stderr.write(`${DIM}(no todos)${RESET}
|
|
54719
|
+
process.stderr.write(`${DIM}(no todos${scopeHint})${RESET}
|
|
54688
54720
|
`);
|
|
54689
54721
|
if (nextCursor !== undefined)
|
|
54690
54722
|
process.stderr.write(`${DIM}Next cursor: ${nextCursor} (re-run with the same filters plus --cursor ${nextCursor})${RESET}
|
|
@@ -54701,7 +54733,7 @@ async function listTodosCommand(api, defaultProjectId, argv) {
|
|
|
54701
54733
|
process.stdout.write(`${sc}${status.padEnd(statusW)}${RESET} ${DIM}${ts}${RESET} ${t.id} ${title}
|
|
54702
54734
|
`);
|
|
54703
54735
|
}
|
|
54704
|
-
process.stderr.write(`${DIM}${visible.length} todo(s)${RESET}
|
|
54736
|
+
process.stderr.write(`${DIM}${visible.length} todo(s)${scopeHint}${RESET}
|
|
54705
54737
|
`);
|
|
54706
54738
|
if (nextCursor !== undefined)
|
|
54707
54739
|
process.stderr.write(`${DIM}Next cursor: ${nextCursor} (re-run with the same filters plus --cursor ${nextCursor})${RESET}
|
|
@@ -55656,7 +55688,7 @@ password=${apiKey}
|
|
|
55656
55688
|
}
|
|
55657
55689
|
|
|
55658
55690
|
// src/index.ts
|
|
55659
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
55691
|
+
import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
|
|
55660
55692
|
|
|
55661
55693
|
// src/ensure-bridge.ts
|
|
55662
55694
|
import { spawn as spawn4, spawnSync as spawnSync4 } from "child_process";
|
|
@@ -80374,15 +80406,49 @@ async function main() {
|
|
|
80374
80406
|
console.log(res.ref);
|
|
80375
80407
|
return;
|
|
80376
80408
|
}
|
|
80409
|
+
if (positionals[0] === "ignore") {
|
|
80410
|
+
const title = args.title?.trim();
|
|
80411
|
+
if (!title) {
|
|
80412
|
+
process.stderr.write(`${RED3}Usage: tfa-cli ignore --title "..." [--source "label|ref|url|from"] [--note "why"] [--project <id>]${RESET2}
|
|
80413
|
+
`);
|
|
80414
|
+
process.exit(2);
|
|
80415
|
+
}
|
|
80416
|
+
const source = args.source ? (() => {
|
|
80417
|
+
const [label, ref, url2, from] = String(args.source).split("|");
|
|
80418
|
+
return label && ref ? { label, ref, ...url2 ? { url: url2 } : {}, ...from ? { from } : {} } : undefined;
|
|
80419
|
+
})() : undefined;
|
|
80420
|
+
const projectId2 = args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
|
|
80421
|
+
if (!projectId2) {
|
|
80422
|
+
process.stderr.write(`${RED3}No project found \u2014 pass --project <id>${RESET2}
|
|
80423
|
+
`);
|
|
80424
|
+
process.exit(2);
|
|
80425
|
+
}
|
|
80426
|
+
await api2.feedIgnore({ projectId: projectId2, title, ...source ? { source } : {}, ...args.note ? { why: args.note } : {} });
|
|
80427
|
+
process.stderr.write(`${DIM2}ignored: ${title}${RESET2}
|
|
80428
|
+
`);
|
|
80429
|
+
return;
|
|
80430
|
+
}
|
|
80377
80431
|
if (positionals[0] === "recommend") {
|
|
80378
80432
|
const templateId = args.template || positionals[1];
|
|
80379
|
-
|
|
80380
|
-
|
|
80433
|
+
const inlineTitle = !templateId && args.title?.trim();
|
|
80434
|
+
if (!templateId && !inlineTitle) {
|
|
80435
|
+
process.stderr.write(`${RED3}Usage: tfa-cli recommend --template <id> | --title "..." [--info] [--source "label|ref|url|from"] [--note "why"] [--priority high|medium|low] [--group <slug> [--group-name "..."] [--group-description "..."]] [--project <id>]${RESET2}
|
|
80381
80436
|
`);
|
|
80382
|
-
process.stderr.write(`${DIM2}
|
|
80437
|
+
process.stderr.write(`${DIM2}--info: an information row (no \u2727, nothing to accept). --source: provenance; the same label|ref never creates a second row.${RESET2}
|
|
80383
80438
|
`);
|
|
80384
80439
|
process.exit(2);
|
|
80385
80440
|
}
|
|
80441
|
+
const source = (() => {
|
|
80442
|
+
if (!args.source)
|
|
80443
|
+
return;
|
|
80444
|
+
const [label, ref, url2, from] = String(args.source).split("|");
|
|
80445
|
+
if (!label || !ref) {
|
|
80446
|
+
process.stderr.write(`${RED3}--source needs at least "label|ref"${RESET2}
|
|
80447
|
+
`);
|
|
80448
|
+
process.exit(2);
|
|
80449
|
+
}
|
|
80450
|
+
return { label, ref, ...url2 ? { url: url2 } : {}, ...from ? { from } : {} };
|
|
80451
|
+
})();
|
|
80386
80452
|
let projectId2 = args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
|
|
80387
80453
|
if (!projectId2) {
|
|
80388
80454
|
const projects2 = await api2.listProjects();
|
|
@@ -80401,7 +80467,17 @@ async function main() {
|
|
|
80401
80467
|
}
|
|
80402
80468
|
const rec = await api2.recommend({
|
|
80403
80469
|
projectId: projectId2,
|
|
80404
|
-
specId: templateId
|
|
80470
|
+
...templateId ? { specId: templateId } : { template: {
|
|
80471
|
+
name: inlineTitle,
|
|
80472
|
+
description: args.note || (source ? `From ${source.label}` : inlineTitle),
|
|
80473
|
+
body: `Handle this item: ${inlineTitle}${args.note ? `
|
|
80474
|
+
|
|
80475
|
+
${args.note}` : ""}${source?.url ? `
|
|
80476
|
+
|
|
80477
|
+
Original: ${source.url}` : ""}`
|
|
80478
|
+
} },
|
|
80479
|
+
...args.info ? { todoKind: "info" } : {},
|
|
80480
|
+
...source ? { source } : {},
|
|
80405
80481
|
...args.title ? { title: args.title } : {},
|
|
80406
80482
|
...args.note ? { note: args.note } : {},
|
|
80407
80483
|
...priority ? { priority } : {},
|
|
@@ -80412,7 +80488,7 @@ async function main() {
|
|
|
80412
80488
|
if (args.json)
|
|
80413
80489
|
console.log(JSON.stringify(rec, null, 2));
|
|
80414
80490
|
else
|
|
80415
|
-
process.stderr.write(`${GREEN2}\u2705
|
|
80491
|
+
process.stderr.write(`${GREEN2}\u2705 ${args.info ? "\u24D8 info" : "\u2727 proposal"} ${rec?.id ?? ""} on project ${projectId2}${RESET2}
|
|
80416
80492
|
`);
|
|
80417
80493
|
return;
|
|
80418
80494
|
}
|
|
@@ -80515,7 +80591,20 @@ async function main() {
|
|
|
80515
80591
|
defaultProjectId = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
|
|
80516
80592
|
}
|
|
80517
80593
|
const sub = process.argv.slice(process.argv.indexOf(positionals[0]) + 1);
|
|
80518
|
-
|
|
80594
|
+
const tokenFp = createHash3("sha256").update(apiKey).digest("hex").slice(0, 16);
|
|
80595
|
+
const resolveUserId = async () => {
|
|
80596
|
+
const envId = getEnv("USER_ID");
|
|
80597
|
+
if (envId)
|
|
80598
|
+
return envId;
|
|
80599
|
+
const cached2 = cfgScope.cachedUserId(tokenFp);
|
|
80600
|
+
if (cached2)
|
|
80601
|
+
return cached2;
|
|
80602
|
+
const v = await api2.validateApiKey();
|
|
80603
|
+
if (v.userId)
|
|
80604
|
+
cfgScope.setUserId(v.userId, tokenFp);
|
|
80605
|
+
return v.userId;
|
|
80606
|
+
};
|
|
80607
|
+
await listTodosCommand(api2, defaultProjectId ?? undefined, sub, resolveUserId);
|
|
80519
80608
|
return;
|
|
80520
80609
|
}
|
|
80521
80610
|
if (args.inspect !== undefined) {
|