@lumoai/cli 1.53.0 → 1.55.0
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/assets/skill/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lumo
|
|
3
|
-
description: 'Use when the user mentions a Lumo task id (LUM-N, or any team prefix like SPEC-12) or the `lumo` CLI, in any language; is starting, resuming, or about to claim completion of a task; asks what to work on next; or works with any Lumo resource — task context, sessions, acceptance criteria, machine verification, tasks, projects, milestones, sprints, docs, artifacts, Figma links, dependencies, team memory, or worktrees. Key triggers: "LUM-", "lumo", "task context", "session attach", "verify", "task status", "acceptance criteria", "what should I work on", "resume task".'
|
|
3
|
+
description: 'Use when the user mentions a Lumo task id (LUM-N, or any team prefix like SPEC-12) or the `lumo` CLI, in any language; is starting, resuming, or about to claim completion of a task; asks what to work on next; or works with any Lumo resource — task context, sessions, acceptance criteria, machine verification, tasks, ideas, plan runs (the idea→plan converter), projects, milestones, sprints, docs, artifacts, Figma links, dependencies, team memory, priorities, or worktrees. Key triggers: "LUM-", "lumo", "task context", "session attach", "verify", "task status", "acceptance criteria", "what should I work on", "resume task".'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
## Prerequisites
|
|
@@ -33,6 +33,7 @@ The command catalog below is a **map**: it lists every command grouped by domain
|
|
|
33
33
|
| `cost` — per-operation (per-tool) token cost read-out; `task lineage` Top-5 | [references/task-context.md](references/task-context.md) |
|
|
34
34
|
| `outcome show/record/rate` — post-hoc outcome well (falsifier oracle, rejection-only) | [references/outcome.md](references/outcome.md) |
|
|
35
35
|
| `fidelity show` — per-delivery grounding + independence change-pattern read-out | [references/fidelity.md](references/fidelity.md) |
|
|
36
|
+
| `priority [text]`, `priority list` — team priority declaration (append-only) | [references/priority.md](references/priority.md) |
|
|
36
37
|
| `project list`, `milestone*` | [references/milestones.md](references/milestones.md) |
|
|
37
38
|
| `doc create/update/list/move/bind/share/import` (CRUD) | [references/docs.md](references/docs.md) |
|
|
38
39
|
| `doc show --raw/--section`, `doc patch/append/diff/rebuild-source` (editing live docs) | [references/doc-editing.md](references/doc-editing.md) |
|
|
@@ -69,6 +70,17 @@ The command catalog below is a **map**: it lists every command grouped by domain
|
|
|
69
70
|
- `lumo task show <id>` — print one task's detail
|
|
70
71
|
- `lumo task comment <id> <body>` — leave a comment
|
|
71
72
|
|
|
73
|
+
**Ideas (team-level capture pool)**
|
|
74
|
+
|
|
75
|
+
- `lumo idea "<statement>" [--context <text>]` — capture a team-level idea into the pool in <10s. Provenance is grabbed opportunistically and never blocks the capture: the current `CLAUDE_CODE_SESSION_ID` (→ sourceSessionId) and the session's bound task (→ sourceTaskId, null when nothing is bound). Prints an **I-prefixed** id (`✓ 想法 LUM-I42 已入池`) — the `I` keeps ideas from colliding with task ids (`LUM-42`) in the same team namespace. Ideas are team-scoped (no project); the workspace's default team owns them. The unprocessed (CAPTURED) pool is what the Phase-2 transformer consumes.
|
|
76
|
+
- **When to suggest**: the user has a stray idea/thought/improvement they want to park without derailing the current task ("记一下这个想法", "capture this idea", "add to the backlog of ideas"). Prefer this over `task create` for un-triaged sparks — an idea is above projects and isn't yet actionable work.
|
|
77
|
+
|
|
78
|
+
**Plan runs (design-thinking 转换器)**
|
|
79
|
+
|
|
80
|
+
- `lumo plan [--abandon-active]` — start a converter run that turns the team's captured idea pool + current priority into an executable plan through a 3-gate recoverable state machine (聚类→对齐→成计划). Freezes the input snapshot at start and enforces **one active run per team**: if a run is already active it refuses and points to `lumo plan status`; `--abandon-active` abandons the prior run first, then starts fresh. On success it prints (and opens) the **gate-A** web deep-link (`/workspace/<slug>/plan/<runId>`). The CLI only triggers — **all editing and gate confirmation happen in web**.
|
|
81
|
+
- `lumo plan status` — print the active run's current stage (working segment or the open `*_READY` gate) and the deep-link to act on its next gate; with no active run, prompts to run `lumo plan`.
|
|
82
|
+
- **When to suggest**: the user wants to turn the parked idea pool into a plan / "跑一下转换器" / "start a planning run" / "把想法变成计划"; or asks where an in-flight plan run stands ("plan 到哪一步了", "plan status"). The gate work itself is web-only — the CLI hands off the deep-link.
|
|
83
|
+
|
|
72
84
|
**Task dependencies** — see [task-deps.md](references/task-deps.md)
|
|
73
85
|
|
|
74
86
|
- `lumo task deps list <id>` — list dependency edges both directions, grouped CONFIRMED / SUGGESTED / DISMISSED (each row: short edge id + other task + detected evidence)
|
|
@@ -94,6 +106,11 @@ The command catalog below is a **map**: it lists every command grouped by domain
|
|
|
94
106
|
|
|
95
107
|
- `lumo cost [--task <id>|--session <id>|--since <date>] [--by tool|model|member|session] [--json]` — per-operation token cost read-out, attributing each model step's token delta to the tool(s) it ran. Scope is mutually exclusive (default = workspace last-30-days). For the per-task Top-5 inline, see `lumo task lineage`.
|
|
96
108
|
|
|
109
|
+
**Priority (team-level declaration)** — see [priority.md](references/priority.md)
|
|
110
|
+
|
|
111
|
+
- `lumo priority [text]` — declare the team's current priority (a 1-3 sentence plain-text statement). With `text`, records it directly; with no argument, prompts interactively. **Append-only**: a new declaration supersedes the old by a later `effectiveFrom` — nothing is ever overwritten or deleted.
|
|
112
|
+
- `lumo priority list` — print the append history newest-first (the current declaration is marked `*`).
|
|
113
|
+
|
|
97
114
|
**Criteria audit** — workspace-level referent-kind analytics
|
|
98
115
|
|
|
99
116
|
- `lumo criteria audit` — workspace-level read-out: count of acceptance criteria by effective referent kind (EXTERNAL_FACT / AGENT_CONSTRUCTED_STATE / PENDING_OUTCOME / UNVERIFIED_ASSERTION / UNCLASSIFIED) + the self-confirming-green ratio
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Priority (team-level declaration)
|
|
2
|
+
|
|
3
|
+
The team **priority** is a short (1-3 sentence) plain-text statement of what the
|
|
4
|
+
team is currently focused on. It is **team-level** — scoped to the caller's
|
|
5
|
+
workspace default team, with no project or milestone binding.
|
|
6
|
+
|
|
7
|
+
Priority is **append-only by design**: every declaration inserts a new row.
|
|
8
|
+
A newer statement supersedes an older one by a later `effectiveFrom`; the
|
|
9
|
+
history is **never mutated or deleted**. There is deliberately no edit/remove
|
|
10
|
+
command — to change the priority, declare a new one.
|
|
11
|
+
|
|
12
|
+
## `lumo priority [text]`
|
|
13
|
+
|
|
14
|
+
Declare the current team priority.
|
|
15
|
+
|
|
16
|
+
- **With `text`** — records it directly:
|
|
17
|
+
```bash
|
|
18
|
+
lumo priority "Ship the idea-capture MVP; defer the converter to Spec 2."
|
|
19
|
+
```
|
|
20
|
+
- **With no argument** — prompts interactively for a 1-3 sentence statement:
|
|
21
|
+
```bash
|
|
22
|
+
lumo priority
|
|
23
|
+
# Priority (1-3 sentences): _
|
|
24
|
+
```
|
|
25
|
+
An empty prompt records nothing and exits non-zero.
|
|
26
|
+
|
|
27
|
+
On success it prints `Recorded priority: <statement>`. A blank statement is
|
|
28
|
+
rejected (the API's Zod layer returns 400).
|
|
29
|
+
|
|
30
|
+
## `lumo priority list`
|
|
31
|
+
|
|
32
|
+
Print the append history, newest-first. The current declaration (the row with
|
|
33
|
+
the latest `effectiveFrom`) is marked with a leading `*`:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
* 2026-02-01 Now focus on the converter state machine
|
|
37
|
+
2026-01-01 Ship the idea-capture MVP
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Prints `No priorities declared yet.` when the team has none.
|
|
41
|
+
|
|
42
|
+
## When to suggest
|
|
43
|
+
|
|
44
|
+
- The user wants to **set or change the team's focus/priority** in plain words
|
|
45
|
+
("我们现在的重点是…", "set our team priority to…", "记录一下当前优先级").
|
|
46
|
+
- The user asks **what the team is currently prioritizing** → `lumo priority list`.
|
|
47
|
+
- Do **not** confuse this with task priority (`lumo task update --priority
|
|
48
|
+
low|medium|high|urgent`), which is a per-task enum, not a team declaration.
|
|
49
|
+
|
|
50
|
+
## Under the hood
|
|
51
|
+
|
|
52
|
+
- `POST /api/priorities` `{ statement }` → 201 `{ priority }` (append).
|
|
53
|
+
- `GET /api/priorities` → `{ current, history }` (history newest-first).
|
|
54
|
+
- Dual-auth (bearer or Clerk); the team is resolved as the caller's workspace
|
|
55
|
+
default team. Layers: `priority.repository` → `priority.service` → route → CLI.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatCapturedIdeaLine = formatCapturedIdeaLine;
|
|
4
|
+
exports.ideaCapture = ideaCapture;
|
|
5
|
+
const config_1 = require("../lib/config");
|
|
6
|
+
const api_1 = require("../lib/api");
|
|
7
|
+
const resolve_bound_task_1 = require("../lib/resolve-bound-task");
|
|
8
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
9
|
+
/**
|
|
10
|
+
* Success line for a captured idea. Echoes the human-referenceable id
|
|
11
|
+
* (`<TEAM>-I<n>`, e.g. `LUM-I42`) — the `I` prefix keeps it visibly distinct
|
|
12
|
+
* from task ids (`LUM-42`).
|
|
13
|
+
*/
|
|
14
|
+
function formatCapturedIdeaLine(idea) {
|
|
15
|
+
return `✓ 想法 ${idea.identifier} 已入池`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* `lumo idea "<statement>"` — a <10s frictionless capture of a team-level idea.
|
|
19
|
+
*
|
|
20
|
+
* Provenance is gathered opportunistically and never blocks the capture:
|
|
21
|
+
* the current `CLAUDE_CODE_SESSION_ID` (→ sourceSessionId) and the task the
|
|
22
|
+
* session is bound to (→ sourceTaskId, null when nothing is bound). An
|
|
23
|
+
* optional `--context` note becomes sourceContext.
|
|
24
|
+
*/
|
|
25
|
+
async function ideaCapture(statement, opts) {
|
|
26
|
+
if (!statement || statement.trim().length === 0) {
|
|
27
|
+
console.error('Error: missing <statement>. Usage: lumo idea "<your idea>" [--context <text>]');
|
|
28
|
+
return 1;
|
|
29
|
+
}
|
|
30
|
+
const creds = (0, config_1.readCredentials)();
|
|
31
|
+
if (!creds) {
|
|
32
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
36
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
37
|
+
const url = `${base}/api/ideas`;
|
|
38
|
+
const sourceSessionId = process.env.CLAUDE_CODE_SESSION_ID ?? null;
|
|
39
|
+
const sourceTaskId = await (0, resolve_bound_task_1.resolveBoundTaskIdentifier)(apiUrl, creds.token);
|
|
40
|
+
const body = { statement: statement.trim() };
|
|
41
|
+
if (opts.context !== undefined)
|
|
42
|
+
body.sourceContext = opts.context;
|
|
43
|
+
if (sourceSessionId)
|
|
44
|
+
body.sourceSessionId = sourceSessionId;
|
|
45
|
+
if (sourceTaskId)
|
|
46
|
+
body.sourceTaskId = sourceTaskId;
|
|
47
|
+
const headers = {
|
|
48
|
+
Authorization: `Bearer ${creds.token}`,
|
|
49
|
+
'Content-Type': 'application/json',
|
|
50
|
+
};
|
|
51
|
+
let res;
|
|
52
|
+
try {
|
|
53
|
+
res = await fetch(url, {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers,
|
|
56
|
+
body: JSON.stringify(body),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
61
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
if (res.status === 401) {
|
|
65
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
if (res.status === 201) {
|
|
69
|
+
const data = (await res.json());
|
|
70
|
+
process.stdout.write(formatCapturedIdeaLine(data.idea) + '\n');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
let serverMsg = null;
|
|
74
|
+
try {
|
|
75
|
+
const errBody = (await res.json());
|
|
76
|
+
if (typeof errBody.error === 'string')
|
|
77
|
+
serverMsg = errBody.error;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Body wasn't JSON; fall through to status-only message
|
|
81
|
+
}
|
|
82
|
+
if (serverMsg) {
|
|
83
|
+
console.error(`Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.error(`Error: idea capture failed (HTTP ${res.status})`);
|
|
87
|
+
}
|
|
88
|
+
return 1;
|
|
89
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatPlanDeepLink = formatPlanDeepLink;
|
|
4
|
+
exports.stageLabel = stageLabel;
|
|
5
|
+
exports.plan = plan;
|
|
6
|
+
exports.planStatus = planStatus;
|
|
7
|
+
const config_1 = require("../lib/config");
|
|
8
|
+
const api_1 = require("../lib/api");
|
|
9
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
10
|
+
const browser_1 = require("../lib/browser");
|
|
11
|
+
/**
|
|
12
|
+
* Web deep-link to a run's converter page: `/workspace/<slug>/plan/<runId>`.
|
|
13
|
+
* The page routes to whichever gate is open on the run — so the same link is
|
|
14
|
+
* "gate A" at start (run in CLUSTERING) and "the next gate" mid-run. Editing
|
|
15
|
+
* and gate confirmation happen in the web UI; the CLI only carries the human
|
|
16
|
+
* there.
|
|
17
|
+
*/
|
|
18
|
+
function formatPlanDeepLink(base, workspaceSlug, runId) {
|
|
19
|
+
return `${(0, api_1.trimTrailingSlash)(base)}/workspace/${workspaceSlug}/plan/${runId}`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Human label for a run stage. The gates (`*_READY`) name the decision waiting
|
|
23
|
+
* on the human; the working stages name the LLM segment in flight. Unknown /
|
|
24
|
+
* future stages fall back to the raw enum so nothing is silently swallowed.
|
|
25
|
+
*/
|
|
26
|
+
function stageLabel(stage) {
|
|
27
|
+
const LABELS = {
|
|
28
|
+
CLUSTERING: '聚类中 (clustering)',
|
|
29
|
+
CLUSTERS_READY: '闸门 A · 聚类待确认 (gate A — confirm clusters)',
|
|
30
|
+
ALIGNING: '对齐中 (aligning)',
|
|
31
|
+
ALIGNMENT_READY: '闸门 B · 对齐待确认 (gate B — confirm alignment)',
|
|
32
|
+
GENERATING: '成计划中 (generating)',
|
|
33
|
+
DRAFT_READY: '闸门 C · 草案待确认 (gate C — confirm draft)',
|
|
34
|
+
FAILED: '失败 · 可重试 (failed — retryable)',
|
|
35
|
+
};
|
|
36
|
+
return LABELS[stage] ?? stage;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* `lumo plan [--abandon-active]` — start a design-thinking 转换器 run.
|
|
40
|
+
*
|
|
41
|
+
* Starts a run and prints (and opens) the gate-A web deep-link. When a run is
|
|
42
|
+
* already active the server refuses (409) — we point the user at
|
|
43
|
+
* `lumo plan status` or `--abandon-active`. `--abandon-active` first abandons
|
|
44
|
+
* the prior run, then starts a fresh one. All editing/confirmation is in web;
|
|
45
|
+
* the CLI only triggers and hands off the deep-link.
|
|
46
|
+
*/
|
|
47
|
+
async function plan(opts = {}) {
|
|
48
|
+
const creds = (0, config_1.readCredentials)();
|
|
49
|
+
if (!creds) {
|
|
50
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
54
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
55
|
+
const body = {};
|
|
56
|
+
if (opts.abandonActive)
|
|
57
|
+
body.abandonActive = true;
|
|
58
|
+
let res;
|
|
59
|
+
try {
|
|
60
|
+
res = await fetch(`${base}/api/plan-runs`, {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: {
|
|
63
|
+
Authorization: `Bearer ${creds.token}`,
|
|
64
|
+
'Content-Type': 'application/json',
|
|
65
|
+
},
|
|
66
|
+
body: JSON.stringify(body),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
71
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
if (res.status === 401) {
|
|
75
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
78
|
+
if (res.status === 409) {
|
|
79
|
+
console.error('Error: a plan run is already active for your team.\n' +
|
|
80
|
+
' Check it with `lumo plan status`, or start over with ' +
|
|
81
|
+
'`lumo plan --abandon-active`.');
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
if (res.status === 201) {
|
|
85
|
+
const { run } = (await res.json());
|
|
86
|
+
const deepLink = formatPlanDeepLink(base, creds.workspaceSlug, run.id);
|
|
87
|
+
process.stdout.write(`✓ 计划转换 run 已启动 (${run.id}) — 聚类进行中。\n` +
|
|
88
|
+
'到闸门 A 在 web 端确认聚类结果:\n' +
|
|
89
|
+
` ${(0, sanitize_1.sanitizeField)(deepLink)}\n`);
|
|
90
|
+
(0, browser_1.openBrowser)(deepLink);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
return reportServerError(res);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* `lumo plan status` — where the active run stands.
|
|
97
|
+
*
|
|
98
|
+
* Prints the active run's current stage and the deep-link to act on its next
|
|
99
|
+
* gate. With no active run, prompts the user to run `lumo plan`.
|
|
100
|
+
*/
|
|
101
|
+
async function planStatus() {
|
|
102
|
+
const creds = (0, config_1.readCredentials)();
|
|
103
|
+
if (!creds) {
|
|
104
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
107
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
108
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
109
|
+
let res;
|
|
110
|
+
try {
|
|
111
|
+
res = await fetch(`${base}/api/plan-runs/active`, {
|
|
112
|
+
headers: { Authorization: `Bearer ${creds.token}` },
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
117
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
118
|
+
return 1;
|
|
119
|
+
}
|
|
120
|
+
if (res.status === 401) {
|
|
121
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
122
|
+
return 1;
|
|
123
|
+
}
|
|
124
|
+
if (!res.ok) {
|
|
125
|
+
return reportServerError(res);
|
|
126
|
+
}
|
|
127
|
+
const { run } = (await res.json());
|
|
128
|
+
if (!run) {
|
|
129
|
+
process.stdout.write('没有进行中的计划转换 run。运行 `lumo plan` 启动一个。\n');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const deepLink = formatPlanDeepLink(base, creds.workspaceSlug, run.id);
|
|
133
|
+
process.stdout.write(`计划转换 run ${run.id}\n` +
|
|
134
|
+
` 当前 stage: ${stageLabel(run.stage)}\n` +
|
|
135
|
+
' 下一个闸门在 web 端处理:\n' +
|
|
136
|
+
` ${(0, sanitize_1.sanitizeField)(deepLink)}\n`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
/** Print a server error body (or status-only fallback) to stderr; returns 1. */
|
|
140
|
+
async function reportServerError(res) {
|
|
141
|
+
let serverMsg = null;
|
|
142
|
+
try {
|
|
143
|
+
const errBody = (await res.json());
|
|
144
|
+
if (typeof errBody.error === 'string')
|
|
145
|
+
serverMsg = errBody.error;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Body wasn't JSON; fall through to a status-only message.
|
|
149
|
+
}
|
|
150
|
+
if (serverMsg) {
|
|
151
|
+
console.error(`Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
console.error(`Error: plan command failed (HTTP ${res.status})`);
|
|
155
|
+
}
|
|
156
|
+
return 1;
|
|
157
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.priority = priority;
|
|
4
|
+
exports.priorityList = priorityList;
|
|
5
|
+
exports.formatPriorityHistory = formatPriorityHistory;
|
|
6
|
+
const config_1 = require("../lib/config");
|
|
7
|
+
const api_1 = require("../lib/api");
|
|
8
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
9
|
+
const line_prompt_1 = require("../lib/line-prompt");
|
|
10
|
+
/**
|
|
11
|
+
* `lumo priority [text]`
|
|
12
|
+
*
|
|
13
|
+
* With `text`: records it directly as the team's current priority.
|
|
14
|
+
* Without `text`: interactively prompts for a 1-3 sentence statement.
|
|
15
|
+
*
|
|
16
|
+
* Priority is append-only — recording a new one supersedes the old without
|
|
17
|
+
* deleting it. Read the append history with `lumo priority list`.
|
|
18
|
+
*/
|
|
19
|
+
async function priority(text) {
|
|
20
|
+
const creds = (0, config_1.readCredentials)();
|
|
21
|
+
if (!creds) {
|
|
22
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
23
|
+
return 1;
|
|
24
|
+
}
|
|
25
|
+
let statement = (text ?? '').trim();
|
|
26
|
+
if (!statement) {
|
|
27
|
+
statement = (await (0, line_prompt_1.promptLine)('Priority (1-3 sentences): ')).trim();
|
|
28
|
+
}
|
|
29
|
+
if (!statement) {
|
|
30
|
+
console.error('Error: no priority provided. Nothing recorded.');
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
34
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
35
|
+
let res;
|
|
36
|
+
try {
|
|
37
|
+
res = await fetch(`${base}/api/priorities`, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: {
|
|
40
|
+
Authorization: `Bearer ${creds.token}`,
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
},
|
|
43
|
+
body: JSON.stringify({ statement }),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
48
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
if (res.status === 401) {
|
|
52
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
if (res.status !== 201) {
|
|
56
|
+
console.error(await errorMessage(res, 'record priority'));
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
const data = (await res.json());
|
|
60
|
+
process.stdout.write(`Recorded priority: ${(0, sanitize_1.sanitizeField)(data.priority.statement)}\n`);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* `lumo priority list`
|
|
64
|
+
*
|
|
65
|
+
* Prints the append history newest-first, marking the current declaration
|
|
66
|
+
* (the row with the latest effectiveFrom).
|
|
67
|
+
*/
|
|
68
|
+
async function priorityList() {
|
|
69
|
+
const creds = (0, config_1.readCredentials)();
|
|
70
|
+
if (!creds) {
|
|
71
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
75
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
76
|
+
let res;
|
|
77
|
+
try {
|
|
78
|
+
res = await fetch(`${base}/api/priorities`, {
|
|
79
|
+
headers: { Authorization: `Bearer ${creds.token}` },
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
84
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
if (res.status === 401) {
|
|
88
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
if (!res.ok) {
|
|
92
|
+
console.error(await errorMessage(res, 'list priorities'));
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
95
|
+
const data = (await res.json());
|
|
96
|
+
if (data.history.length === 0) {
|
|
97
|
+
process.stdout.write('No priorities declared yet.\n');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const currentId = data.current?.id;
|
|
101
|
+
process.stdout.write(formatPriorityHistory(data.history, currentId));
|
|
102
|
+
}
|
|
103
|
+
/** Render the append history newest-first; the current row is marked `*`. */
|
|
104
|
+
function formatPriorityHistory(history, currentId) {
|
|
105
|
+
return (history
|
|
106
|
+
.map(p => {
|
|
107
|
+
const marker = p.id === currentId ? '*' : ' ';
|
|
108
|
+
const when = p.effectiveFrom.slice(0, 10);
|
|
109
|
+
return `${marker} ${when} ${(0, sanitize_1.sanitizeField)(p.statement)}`;
|
|
110
|
+
})
|
|
111
|
+
.join('\n') + '\n');
|
|
112
|
+
}
|
|
113
|
+
/** Best-effort extraction of a server `{ error }` message for a failed fetch. */
|
|
114
|
+
async function errorMessage(res, action) {
|
|
115
|
+
let serverMsg = null;
|
|
116
|
+
try {
|
|
117
|
+
const errBody = (await res.json());
|
|
118
|
+
if (typeof errBody.error === 'string')
|
|
119
|
+
serverMsg = errBody.error;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Body wasn't JSON
|
|
123
|
+
}
|
|
124
|
+
return serverMsg
|
|
125
|
+
? `Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`
|
|
126
|
+
: `Error: could not ${action} (HTTP ${res.status})`;
|
|
127
|
+
}
|
package/dist/cli/src/index.js
CHANGED
|
@@ -46,7 +46,10 @@ const hook_1 = require("./commands/hook");
|
|
|
46
46
|
const session_attach_1 = require("./commands/session-attach");
|
|
47
47
|
const session_status_1 = require("./commands/session-status");
|
|
48
48
|
const next_1 = require("./commands/next");
|
|
49
|
+
const idea_1 = require("./commands/idea");
|
|
50
|
+
const plan_1 = require("./commands/plan");
|
|
49
51
|
const cost_1 = require("./commands/cost");
|
|
52
|
+
const priority_1 = require("./commands/priority");
|
|
50
53
|
const criteria_audit_1 = require("./commands/criteria-audit");
|
|
51
54
|
const fidelity_1 = require("./commands/fidelity");
|
|
52
55
|
const verify_1 = require("./commands/verify");
|
|
@@ -276,6 +279,20 @@ program
|
|
|
276
279
|
.option('-n, --count <N>', 'Number of tasks to recommend (default 3)')
|
|
277
280
|
.option('--claimable', 'Only recommend agent-claimable tasks that are unblocked (F3) and within milestone budget (F2)')
|
|
278
281
|
.action(wrap(options => (0, next_1.nextCommand)(options)));
|
|
282
|
+
program
|
|
283
|
+
.command('idea <statement>')
|
|
284
|
+
.description('Capture a team-level idea into the pool (<10s, no friction). Grabs the current Claude Code session id and its bound task as provenance; prints an I-prefixed id (e.g. LUM-I42). The transformer (Spec 2) consumes the pool.')
|
|
285
|
+
.option('-c, --context <text>', 'Free-text origin context for the idea')
|
|
286
|
+
.action(wrap((statement, options) => (0, idea_1.ideaCapture)(statement, options)));
|
|
287
|
+
const planCmd = program
|
|
288
|
+
.command('plan')
|
|
289
|
+
.description('Start a design-thinking 转换器 run (聚类→对齐→成计划) and print the gate-A web deep-link. Refuses when a run is already active (see `lumo plan status`); --abandon-active abandons the prior run first. Editing/confirmation happen in web.')
|
|
290
|
+
.option('--abandon-active', 'Abandon the team’s existing active run before starting a fresh one')
|
|
291
|
+
.action(wrap(options => (0, plan_1.plan)(options)));
|
|
292
|
+
planCmd
|
|
293
|
+
.command('status')
|
|
294
|
+
.description('Print the active plan run’s current stage and the deep-link to its next gate; with no active run, prompts to run `lumo plan`.')
|
|
295
|
+
.action(wrap(() => (0, plan_1.planStatus)()));
|
|
279
296
|
program
|
|
280
297
|
.command('cost')
|
|
281
298
|
.description('Show per-operation (per-tool) token cost. Defaults to a workspace 30-day window; scope with --task / --session')
|
|
@@ -285,6 +302,14 @@ program
|
|
|
285
302
|
.option('--by <dim>', 'Headline grouping: tool | model | member | session (case-insensitive; default tool)')
|
|
286
303
|
.option('--json', 'Emit the versioned payload as JSON')
|
|
287
304
|
.action(wrap(options => (0, cost_1.cost)(options)));
|
|
305
|
+
const priorityCmd = program
|
|
306
|
+
.command('priority [text]')
|
|
307
|
+
.description('Declare the team priority (append-only). With [text] records it directly; with no argument prompts interactively for 1-3 sentences. A newer declaration supersedes the old without deleting it.')
|
|
308
|
+
.action(wrap((text) => (0, priority_1.priority)(text)));
|
|
309
|
+
priorityCmd
|
|
310
|
+
.command('list')
|
|
311
|
+
.description('Print the team priority append history, newest-first (the current declaration is marked with *).')
|
|
312
|
+
.action(wrap(() => (0, priority_1.priorityList)()));
|
|
288
313
|
const criteria = program
|
|
289
314
|
.command('criteria')
|
|
290
315
|
.description('Workspace-level acceptance-criteria analytics');
|