@lumoai/cli 1.54.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 +7 -1
- package/dist/cli/src/commands/plan.js +157 -0
- package/dist/cli/src/index.js +10 -0
- package/package.json +1 -1
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, ideas, 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".'
|
|
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
|
|
@@ -75,6 +75,12 @@ The command catalog below is a **map**: it lists every command grouped by domain
|
|
|
75
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
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
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
|
+
|
|
78
84
|
**Task dependencies** — see [task-deps.md](references/task-deps.md)
|
|
79
85
|
|
|
80
86
|
- `lumo task deps list <id>` — list dependency edges both directions, grouped CONFIRMED / SUGGESTED / DISMISSED (each row: short edge id + other task + detected evidence)
|
|
@@ -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
|
+
}
|
package/dist/cli/src/index.js
CHANGED
|
@@ -47,6 +47,7 @@ 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
49
|
const idea_1 = require("./commands/idea");
|
|
50
|
+
const plan_1 = require("./commands/plan");
|
|
50
51
|
const cost_1 = require("./commands/cost");
|
|
51
52
|
const priority_1 = require("./commands/priority");
|
|
52
53
|
const criteria_audit_1 = require("./commands/criteria-audit");
|
|
@@ -283,6 +284,15 @@ program
|
|
|
283
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.')
|
|
284
285
|
.option('-c, --context <text>', 'Free-text origin context for the idea')
|
|
285
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)()));
|
|
286
296
|
program
|
|
287
297
|
.command('cost')
|
|
288
298
|
.description('Show per-operation (per-tool) token cost. Defaults to a workspace 30-day window; scope with --task / --session')
|