@lumoai/cli 1.54.0 → 1.56.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 +78 -1
- package/assets/skill/references/priority.md +6 -5
- package/assets/skill/references/tasks.md +15 -3
- package/dist/cli/src/commands/idea-comment.js +127 -0
- package/dist/cli/src/commands/idea-figma-add.js +30 -0
- package/dist/cli/src/commands/idea-figma-context.js +65 -0
- package/dist/cli/src/commands/idea-figma-list.js +32 -0
- package/dist/cli/src/commands/idea-figma-refresh.js +40 -0
- package/dist/cli/src/commands/idea-figma-rm.js +19 -0
- package/dist/cli/src/commands/idea-slack-add.js +66 -0
- package/dist/cli/src/commands/idea-slack-rm.js +63 -0
- package/dist/cli/src/commands/idea-slack-show.js +65 -0
- package/dist/cli/src/commands/idea-web-add.js +66 -0
- package/dist/cli/src/commands/idea-web-rm.js +63 -0
- package/dist/cli/src/commands/idea-web-show.js +71 -0
- package/dist/cli/src/commands/idea.js +140 -0
- package/dist/cli/src/commands/initiative.js +132 -0
- package/dist/cli/src/commands/next.js +9 -1
- package/dist/cli/src/commands/plan.js +157 -0
- package/dist/cli/src/commands/session-attach.js +4 -0
- package/dist/cli/src/commands/setup.js +17 -8
- package/dist/cli/src/commands/task-comment-list.js +2 -1
- package/dist/cli/src/commands/task-context.js +5 -1
- package/dist/cli/src/commands/task-create.js +18 -1
- package/dist/cli/src/commands/task-update.js +16 -0
- package/dist/cli/src/commands/worktree-add.js +6 -2
- package/dist/cli/src/index.js +128 -3
- package/dist/cli/src/lib/idea-figma-api.js +65 -0
- package/dist/cli/src/lib/next-steps.js +55 -0
- package/package.json +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ideaSlackShow = ideaSlackShow;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
7
|
+
const report_pull_1 = require("../lib/report-pull");
|
|
8
|
+
/**
|
|
9
|
+
* `lumo idea slack show <LUM-I1> <context-id>`
|
|
10
|
+
*
|
|
11
|
+
* Tier-2 retrieval for the cheap inline Slack card on an idea. Fetches the
|
|
12
|
+
* stored thread snapshot (no live Slack call) from
|
|
13
|
+
* `/api/ideas/:id/slack-contexts/:contextId/snapshot` and prints one line per
|
|
14
|
+
* message as `author: text`, falling back to `@<userId>` when the display
|
|
15
|
+
* name is missing. Mirrors `taskSlackShow` (LUM-681).
|
|
16
|
+
*/
|
|
17
|
+
async function ideaSlackShow(identifier, contextId) {
|
|
18
|
+
if (!identifier || !contextId) {
|
|
19
|
+
console.error('Error: usage: lumo idea slack show <LUM-I1> <context-id>');
|
|
20
|
+
return 1;
|
|
21
|
+
}
|
|
22
|
+
const creds = (0, config_1.readCredentials)();
|
|
23
|
+
if (!creds) {
|
|
24
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
28
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
29
|
+
let res;
|
|
30
|
+
try {
|
|
31
|
+
res = await fetch(`${base}/api/ideas/${encodeURIComponent(identifier)}/slack-contexts/${encodeURIComponent(contextId)}/snapshot`, { headers: { Authorization: `Bearer ${creds.token}` } });
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
35
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
36
|
+
return 1;
|
|
37
|
+
}
|
|
38
|
+
if (res.status === 401) {
|
|
39
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
if (res.status === 404) {
|
|
43
|
+
console.error(`Error: idea ${identifier} or slack context ${contextId} not found in workspace ${creds.workspaceSlug}`);
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
if (!res.ok) {
|
|
47
|
+
console.error(`Error: slack show failed (HTTP ${res.status})`);
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
const { snapshot } = (await res.json());
|
|
51
|
+
const messages = snapshot?.messages ?? [];
|
|
52
|
+
if (messages.length === 0) {
|
|
53
|
+
console.log('(no messages in stored snapshot)');
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
for (const m of messages) {
|
|
57
|
+
const author = (0, sanitize_1.sanitizeField)(m.userName ?? '@' + m.userId);
|
|
58
|
+
console.log(`${author}: ${(0, sanitize_1.sanitizeField)(m.text)}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// LUM-681: mirrors LUM-500 disclosure-funnel stamping for idea fragments.
|
|
62
|
+
// The contextId arg == lineage SLACK_CONTEXT fragmentId. Fire-and-forget —
|
|
63
|
+
// never blocks, swallows failures.
|
|
64
|
+
await (0, report_pull_1.reportPull)({ fragmentType: 'SLACK_CONTEXT', fragmentId: contextId });
|
|
65
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ideaWebAdd = ideaWebAdd;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
7
|
+
/**
|
|
8
|
+
* `lumo idea web add <LUM-I1> <url>`
|
|
9
|
+
*
|
|
10
|
+
* Attaches a web link to an idea by POSTing the url to
|
|
11
|
+
* `/api/ideas/:id/web-links`. Mirrors the task-side flow (LUM-681).
|
|
12
|
+
*/
|
|
13
|
+
async function ideaWebAdd(identifier, url) {
|
|
14
|
+
if (!identifier || !url) {
|
|
15
|
+
console.error('Error: usage: lumo idea web add <LUM-I1> <url>');
|
|
16
|
+
return 1;
|
|
17
|
+
}
|
|
18
|
+
const creds = (0, config_1.readCredentials)();
|
|
19
|
+
if (!creds) {
|
|
20
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
24
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
25
|
+
let res;
|
|
26
|
+
try {
|
|
27
|
+
res = await fetch(`${base}/api/ideas/${encodeURIComponent(identifier)}/web-links`, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: {
|
|
30
|
+
Authorization: `Bearer ${creds.token}`,
|
|
31
|
+
'Content-Type': 'application/json',
|
|
32
|
+
},
|
|
33
|
+
body: JSON.stringify({ url }),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
38
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
if (res.status === 401) {
|
|
42
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
if (res.status === 404) {
|
|
46
|
+
console.error(`Error: idea ${identifier} not found in workspace ${creds.workspaceSlug}`);
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
let serverMsg = null;
|
|
51
|
+
try {
|
|
52
|
+
const errBody = (await res.json());
|
|
53
|
+
if (typeof errBody.error === 'string')
|
|
54
|
+
serverMsg = errBody.error;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
/* not JSON */
|
|
58
|
+
}
|
|
59
|
+
console.error(serverMsg
|
|
60
|
+
? `Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`
|
|
61
|
+
: `Error: web add failed (HTTP ${res.status})`);
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
const { webLink } = (await res.json());
|
|
65
|
+
console.log(`Linked web page to ${identifier} (link ${webLink.id})`);
|
|
66
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ideaWebRm = ideaWebRm;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
7
|
+
/**
|
|
8
|
+
* `lumo idea web rm <LUM-I1> <link-id>`
|
|
9
|
+
*
|
|
10
|
+
* Removes a web link from an idea via
|
|
11
|
+
* `DELETE /api/ideas/:id/web-links/:linkId`. Mirrors the task-side shape
|
|
12
|
+
* (LUM-681); no task-side `rm` equivalent exists yet, so this follows the
|
|
13
|
+
* DELETE route contract directly.
|
|
14
|
+
*/
|
|
15
|
+
async function ideaWebRm(identifier, linkId) {
|
|
16
|
+
if (!identifier || !linkId) {
|
|
17
|
+
console.error('Error: usage: lumo idea web rm <LUM-I1> <link-id>');
|
|
18
|
+
return 1;
|
|
19
|
+
}
|
|
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
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
26
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
27
|
+
let res;
|
|
28
|
+
try {
|
|
29
|
+
res = await fetch(`${base}/api/ideas/${encodeURIComponent(identifier)}/web-links/${encodeURIComponent(linkId)}`, {
|
|
30
|
+
method: 'DELETE',
|
|
31
|
+
headers: { Authorization: `Bearer ${creds.token}` },
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
36
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
37
|
+
return 1;
|
|
38
|
+
}
|
|
39
|
+
if (res.status === 401) {
|
|
40
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
if (res.status === 404) {
|
|
44
|
+
console.error(`Error: idea ${identifier} or web link ${linkId} not found in workspace ${creds.workspaceSlug}`);
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
if (!res.ok && res.status !== 204) {
|
|
48
|
+
let serverMsg = null;
|
|
49
|
+
try {
|
|
50
|
+
const errBody = (await res.json());
|
|
51
|
+
if (typeof errBody.error === 'string')
|
|
52
|
+
serverMsg = errBody.error;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
/* not JSON */
|
|
56
|
+
}
|
|
57
|
+
console.error(serverMsg
|
|
58
|
+
? `Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`
|
|
59
|
+
: `Error: web rm failed (HTTP ${res.status})`);
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
console.log(`Removed web link ${linkId} from ${identifier}`);
|
|
63
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ideaWebShow = ideaWebShow;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
7
|
+
const report_pull_1 = require("../lib/report-pull");
|
|
8
|
+
/**
|
|
9
|
+
* `lumo idea web show <LUM-I1> <link-id>`
|
|
10
|
+
*
|
|
11
|
+
* Tier-2 retrieval for the cheap inline WebLink card on an idea. Fetches the
|
|
12
|
+
* page body (cached, or fetched behind the SSRF guard on first read) from
|
|
13
|
+
* `/api/ideas/:id/web-links/:linkId/body` and prints it as plain text.
|
|
14
|
+
* Mirrors `taskWebShow` (LUM-681).
|
|
15
|
+
*/
|
|
16
|
+
async function ideaWebShow(identifier, linkId) {
|
|
17
|
+
if (!identifier || !linkId) {
|
|
18
|
+
console.error('Error: usage: lumo idea web show <LUM-I1> <link-id>');
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
const creds = (0, config_1.readCredentials)();
|
|
22
|
+
if (!creds) {
|
|
23
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
27
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
28
|
+
let res;
|
|
29
|
+
try {
|
|
30
|
+
res = await fetch(`${base}/api/ideas/${encodeURIComponent(identifier)}/web-links/${encodeURIComponent(linkId)}/body`, { headers: { Authorization: `Bearer ${creds.token}` } });
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
34
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
if (res.status === 401) {
|
|
38
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
if (res.status === 404) {
|
|
42
|
+
console.error(`Error: idea ${identifier} or web link ${linkId} not found in workspace ${creds.workspaceSlug}`);
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
let serverMsg = null;
|
|
47
|
+
try {
|
|
48
|
+
const errBody = (await res.json());
|
|
49
|
+
if (typeof errBody.error === 'string')
|
|
50
|
+
serverMsg = errBody.error;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
/* not JSON */
|
|
54
|
+
}
|
|
55
|
+
console.error(serverMsg
|
|
56
|
+
? `Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`
|
|
57
|
+
: `Error: web show failed (HTTP ${res.status})`);
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
const { body } = (await res.json());
|
|
61
|
+
if (!body || body.trim().length === 0) {
|
|
62
|
+
console.log('(empty body)');
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log((0, sanitize_1.sanitizeField)(body));
|
|
66
|
+
}
|
|
67
|
+
// LUM-681: mirrors LUM-500 disclosure-funnel stamping for idea fragments.
|
|
68
|
+
// The linkId arg == lineage WEB_LINK fragmentId. Fire-and-forget — never
|
|
69
|
+
// blocks output, swallows failures.
|
|
70
|
+
await (0, report_pull_1.reportPull)({ fragmentType: 'WEB_LINK', fragmentId: linkId });
|
|
71
|
+
}
|
|
@@ -1,11 +1,43 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatIdeaList = formatIdeaList;
|
|
3
4
|
exports.formatCapturedIdeaLine = formatCapturedIdeaLine;
|
|
4
5
|
exports.ideaCapture = ideaCapture;
|
|
6
|
+
exports.ideaList = ideaList;
|
|
7
|
+
exports.ideaUpdate = ideaUpdate;
|
|
5
8
|
const config_1 = require("../lib/config");
|
|
6
9
|
const api_1 = require("../lib/api");
|
|
7
10
|
const resolve_bound_task_1 = require("../lib/resolve-bound-task");
|
|
8
11
|
const sanitize_1 = require("../lib/sanitize");
|
|
12
|
+
/**
|
|
13
|
+
* The four Idea lifecycle states (LUM-679), lower-cased for CLI input. The
|
|
14
|
+
* command normalizes case before sending, so `--status Developing` and
|
|
15
|
+
* `--status developing` both resolve. Mirrors `lib/validation/idea.ts`.
|
|
16
|
+
*/
|
|
17
|
+
const IDEA_STATUS_VALUES = [
|
|
18
|
+
'captured',
|
|
19
|
+
'developing',
|
|
20
|
+
'planned',
|
|
21
|
+
'dropped',
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Render the idea pool as aligned `<id> <status> <statement>` lines. The
|
|
25
|
+
* server already returns the pool newest-first, so this preserves order.
|
|
26
|
+
* Empty pool → a friendly one-liner.
|
|
27
|
+
*/
|
|
28
|
+
function formatIdeaList(ideas) {
|
|
29
|
+
if (ideas.length === 0)
|
|
30
|
+
return 'No ideas.\n';
|
|
31
|
+
const idWidth = Math.max(...ideas.map(i => i.identifier.length));
|
|
32
|
+
const statusWidth = Math.max(...ideas.map(i => i.status.length));
|
|
33
|
+
return (ideas
|
|
34
|
+
.map(i => {
|
|
35
|
+
const id = i.identifier.padEnd(idWidth);
|
|
36
|
+
const status = i.status.padEnd(statusWidth);
|
|
37
|
+
return `${id} ${status} ${(0, sanitize_1.sanitizeField)(i.statement)}`;
|
|
38
|
+
})
|
|
39
|
+
.join('\n') + '\n');
|
|
40
|
+
}
|
|
9
41
|
/**
|
|
10
42
|
* Success line for a captured idea. Echoes the human-referenceable id
|
|
11
43
|
* (`<TEAM>-I<n>`, e.g. `LUM-I42`) — the `I` prefix keeps it visibly distinct
|
|
@@ -87,3 +119,111 @@ async function ideaCapture(statement, opts) {
|
|
|
87
119
|
}
|
|
88
120
|
return 1;
|
|
89
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* `lumo idea list` — print the team's idea pool newest-first, each line
|
|
124
|
+
* showing the `<TEAM>-I<n>` id, status (CAPTURED|PLANNED) and statement.
|
|
125
|
+
* Reads `GET /api/ideas`, which returns the whole pool already ordered
|
|
126
|
+
* newest-first.
|
|
127
|
+
*/
|
|
128
|
+
async function ideaList() {
|
|
129
|
+
const creds = (0, config_1.readCredentials)();
|
|
130
|
+
if (!creds) {
|
|
131
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
132
|
+
return 1;
|
|
133
|
+
}
|
|
134
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
135
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
136
|
+
const url = `${base}/api/ideas`;
|
|
137
|
+
let res;
|
|
138
|
+
try {
|
|
139
|
+
res = await fetch(url, {
|
|
140
|
+
headers: { Authorization: `Bearer ${creds.token}` },
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
145
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
146
|
+
return 1;
|
|
147
|
+
}
|
|
148
|
+
if (res.status === 401) {
|
|
149
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
150
|
+
return 1;
|
|
151
|
+
}
|
|
152
|
+
if (!res.ok) {
|
|
153
|
+
console.error(`Error: idea list failed (HTTP ${res.status})`);
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
156
|
+
const data = (await res.json());
|
|
157
|
+
process.stdout.write(formatIdeaList(data.ideas));
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* `lumo idea update <id> --status <STATUS>` — move an idea through its
|
|
161
|
+
* lifecycle (LUM-679). `<id>` is the `<TEAM>-I<n>` identifier (a bare `<n>`
|
|
162
|
+
* also resolves). The status is case-insensitive; the server enforces the
|
|
163
|
+
* allowed transitions (CAPTURED↔DEVELOPING→PLANNED, any→DROPPED, DROPPED
|
|
164
|
+
* terminal) and rejects an illegal move with a clear error.
|
|
165
|
+
*/
|
|
166
|
+
async function ideaUpdate(idRef, opts) {
|
|
167
|
+
if (!idRef || idRef.trim().length === 0) {
|
|
168
|
+
console.error('Error: missing <id>. Usage: lumo idea update <LUM-I42> --status <captured|developing|planned|dropped>');
|
|
169
|
+
return 1;
|
|
170
|
+
}
|
|
171
|
+
const status = opts.status?.trim().toLowerCase();
|
|
172
|
+
if (!status) {
|
|
173
|
+
console.error('Error: --status is required. One of: captured | developing | planned | dropped');
|
|
174
|
+
return 1;
|
|
175
|
+
}
|
|
176
|
+
if (!IDEA_STATUS_VALUES.includes(status)) {
|
|
177
|
+
console.error(`Error: invalid status "${(0, sanitize_1.sanitizeField)(opts.status ?? '')}". One of: captured | developing | planned | dropped`);
|
|
178
|
+
return 1;
|
|
179
|
+
}
|
|
180
|
+
const creds = (0, config_1.readCredentials)();
|
|
181
|
+
if (!creds) {
|
|
182
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
183
|
+
return 1;
|
|
184
|
+
}
|
|
185
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
186
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
187
|
+
const url = `${base}/api/ideas/${encodeURIComponent(idRef.trim())}`;
|
|
188
|
+
let res;
|
|
189
|
+
try {
|
|
190
|
+
res = await fetch(url, {
|
|
191
|
+
method: 'PATCH',
|
|
192
|
+
headers: {
|
|
193
|
+
Authorization: `Bearer ${creds.token}`,
|
|
194
|
+
'Content-Type': 'application/json',
|
|
195
|
+
},
|
|
196
|
+
body: JSON.stringify({ status: status.toUpperCase() }),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
201
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
202
|
+
return 1;
|
|
203
|
+
}
|
|
204
|
+
if (res.status === 401) {
|
|
205
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
206
|
+
return 1;
|
|
207
|
+
}
|
|
208
|
+
if (res.ok) {
|
|
209
|
+
const data = (await res.json());
|
|
210
|
+
process.stdout.write(`✓ 想法 ${data.idea.identifier} → ${data.idea.status}\n`);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
let serverMsg = null;
|
|
214
|
+
try {
|
|
215
|
+
const errBody = (await res.json());
|
|
216
|
+
if (typeof errBody.error === 'string')
|
|
217
|
+
serverMsg = errBody.error;
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
// Body wasn't JSON; fall through to status-only message
|
|
221
|
+
}
|
|
222
|
+
if (serverMsg) {
|
|
223
|
+
console.error(`Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
console.error(`Error: idea update failed (HTTP ${res.status})`);
|
|
227
|
+
}
|
|
228
|
+
return 1;
|
|
229
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatCreatedInitiativeLine = formatCreatedInitiativeLine;
|
|
4
|
+
exports.formatInitiativeList = formatInitiativeList;
|
|
5
|
+
exports.initiativeCreate = initiativeCreate;
|
|
6
|
+
exports.initiativeList = initiativeList;
|
|
7
|
+
const config_1 = require("../lib/config");
|
|
8
|
+
const api_1 = require("../lib/api");
|
|
9
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
10
|
+
/**
|
|
11
|
+
* Success line for a created initiative. Echoes the human-referenceable id
|
|
12
|
+
* (`<TEAM>-INIT-<n>`, e.g. `LUM-INIT-3`) — the `INIT` infix keeps it visibly
|
|
13
|
+
* distinct from task (`LUM-3`) and idea (`LUM-I3`) ids.
|
|
14
|
+
*/
|
|
15
|
+
function formatCreatedInitiativeLine(init) {
|
|
16
|
+
return `✓ Initiative ${init.identifier} created`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Render the initiatives as aligned `<id> <status> <goal>` lines. The server
|
|
20
|
+
* returns them newest-first, so this preserves order. Empty → a friendly line.
|
|
21
|
+
*/
|
|
22
|
+
function formatInitiativeList(initiatives) {
|
|
23
|
+
if (initiatives.length === 0)
|
|
24
|
+
return 'No initiatives.\n';
|
|
25
|
+
const idWidth = Math.max(...initiatives.map(i => i.identifier.length));
|
|
26
|
+
const statusWidth = Math.max(...initiatives.map(i => i.status.length));
|
|
27
|
+
return (initiatives
|
|
28
|
+
.map(i => {
|
|
29
|
+
const id = i.identifier.padEnd(idWidth);
|
|
30
|
+
const status = i.status.padEnd(statusWidth);
|
|
31
|
+
return `${id} ${status} ${(0, sanitize_1.sanitizeField)(i.name)}`;
|
|
32
|
+
})
|
|
33
|
+
.join('\n') + '\n');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* `lumo initiative create "<goal>" [--assumption <text>]` — create a
|
|
37
|
+
* team-level Initiative directly, without a converter plan run. Prints the
|
|
38
|
+
* created `<TEAM>-INIT-<n>` id.
|
|
39
|
+
*/
|
|
40
|
+
async function initiativeCreate(goal, opts) {
|
|
41
|
+
if (!goal || goal.trim().length === 0) {
|
|
42
|
+
console.error('Error: missing <goal>. Usage: lumo initiative create "<goal>" [--assumption <text>]');
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
const creds = (0, config_1.readCredentials)();
|
|
46
|
+
if (!creds) {
|
|
47
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
51
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
52
|
+
const url = `${base}/api/initiatives`;
|
|
53
|
+
const body = { name: goal.trim() };
|
|
54
|
+
if (opts.assumption !== undefined)
|
|
55
|
+
body.assumptionNote = opts.assumption;
|
|
56
|
+
let res;
|
|
57
|
+
try {
|
|
58
|
+
res = await fetch(url, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: {
|
|
61
|
+
Authorization: `Bearer ${creds.token}`,
|
|
62
|
+
'Content-Type': 'application/json',
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify(body),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
69
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
if (res.status === 401) {
|
|
73
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
if (res.status === 201) {
|
|
77
|
+
const data = (await res.json());
|
|
78
|
+
process.stdout.write(formatCreatedInitiativeLine(data.initiative) + '\n');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
let serverMsg = null;
|
|
82
|
+
try {
|
|
83
|
+
const errBody = (await res.json());
|
|
84
|
+
if (typeof errBody.error === 'string')
|
|
85
|
+
serverMsg = errBody.error;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Body wasn't JSON; fall through to status-only message
|
|
89
|
+
}
|
|
90
|
+
if (serverMsg) {
|
|
91
|
+
console.error(`Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
console.error(`Error: initiative create failed (HTTP ${res.status})`);
|
|
95
|
+
}
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* `lumo initiative list` — print the team's initiatives newest-first, each
|
|
100
|
+
* line showing the `<TEAM>-INIT-<n>` id, status (ACTIVE|DONE|DROPPED) and goal.
|
|
101
|
+
*/
|
|
102
|
+
async function initiativeList() {
|
|
103
|
+
const creds = (0, config_1.readCredentials)();
|
|
104
|
+
if (!creds) {
|
|
105
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
106
|
+
return 1;
|
|
107
|
+
}
|
|
108
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
109
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
110
|
+
const url = `${base}/api/initiatives`;
|
|
111
|
+
let res;
|
|
112
|
+
try {
|
|
113
|
+
res = await fetch(url, {
|
|
114
|
+
headers: { Authorization: `Bearer ${creds.token}` },
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
119
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
120
|
+
return 1;
|
|
121
|
+
}
|
|
122
|
+
if (res.status === 401) {
|
|
123
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
124
|
+
return 1;
|
|
125
|
+
}
|
|
126
|
+
if (!res.ok) {
|
|
127
|
+
console.error(`Error: initiative list failed (HTTP ${res.status})`);
|
|
128
|
+
return 1;
|
|
129
|
+
}
|
|
130
|
+
const data = (await res.json());
|
|
131
|
+
process.stdout.write(formatInitiativeList(data.initiatives));
|
|
132
|
+
}
|
|
@@ -7,6 +7,7 @@ const config_1 = require("../lib/config");
|
|
|
7
7
|
const api_1 = require("../lib/api");
|
|
8
8
|
const sanitize_1 = require("../lib/sanitize");
|
|
9
9
|
const rank_tasks_1 = require("../lib/rank-tasks");
|
|
10
|
+
const next_steps_1 = require("../lib/next-steps");
|
|
10
11
|
const claimable_filter_1 = require("../lib/claimable-filter");
|
|
11
12
|
/**
|
|
12
13
|
* `lumo next [-n, --count <N>]` — recommend the next task(s) to work on.
|
|
@@ -132,7 +133,14 @@ poolNoun = 'open') {
|
|
|
132
133
|
lines.push(` ↳ ${r.reasons.join(' · ')}`);
|
|
133
134
|
});
|
|
134
135
|
lines.push('');
|
|
135
|
-
|
|
136
|
+
// LUM-686: was a hand-written string here — the original drift source this
|
|
137
|
+
// feature exists to remove. Same advice, now through the shared renderer.
|
|
138
|
+
lines.push((0, next_steps_1.formatNextSteps)([
|
|
139
|
+
{
|
|
140
|
+
command: `lumo session attach ${first.identifier} && lumo task context ${first.identifier}`,
|
|
141
|
+
why: 'start on the top-ranked task',
|
|
142
|
+
},
|
|
143
|
+
]));
|
|
136
144
|
if (ranked.length > 1) {
|
|
137
145
|
lines.push('(or pick any other LUM-N from the list)');
|
|
138
146
|
}
|