@lumoai/cli 1.55.0 → 1.57.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 +73 -2
- package/assets/skill/references/criteria.md +1 -1
- package/assets/skill/references/priority.md +6 -5
- package/assets/skill/references/tasks.md +15 -3
- package/assets/skill/references/verify.md +18 -13
- package/assets/skill/references/worktree.md +19 -5
- 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/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 +16 -4
- package/dist/cli/src/commands/worktree-list.js +1 -1
- package/dist/cli/src/commands/worktree-rm.js +10 -2
- package/dist/cli/src/index.js +118 -3
- package/dist/cli/src/lib/idea-figma-api.js +65 -0
- package/dist/cli/src/lib/next-steps.js +55 -0
- package/dist/cli/src/lib/worktree-ref.js +53 -0
- package/dist/cli/src/lib/worktree.js +13 -11
- package/package.json +1 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ideaSlackAdd = ideaSlackAdd;
|
|
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 slack add <LUM-I1> <permalink>`
|
|
9
|
+
*
|
|
10
|
+
* Attaches a Slack thread to an idea by POSTing the permalink to
|
|
11
|
+
* `/api/ideas/:id/slack-contexts`. Mirrors the task-side flow (LUM-681).
|
|
12
|
+
*/
|
|
13
|
+
async function ideaSlackAdd(identifier, permalink) {
|
|
14
|
+
if (!identifier || !permalink) {
|
|
15
|
+
console.error('Error: usage: lumo idea slack add <LUM-I1> <permalink>');
|
|
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)}/slack-contexts`, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: {
|
|
30
|
+
Authorization: `Bearer ${creds.token}`,
|
|
31
|
+
'Content-Type': 'application/json',
|
|
32
|
+
},
|
|
33
|
+
body: JSON.stringify({ permalink }),
|
|
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: slack add failed (HTTP ${res.status})`);
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
const { context } = (await res.json());
|
|
65
|
+
console.log(`Linked Slack thread to ${identifier} (context ${context.id})`);
|
|
66
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ideaSlackRm = ideaSlackRm;
|
|
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 slack rm <LUM-I1> <context-id>`
|
|
9
|
+
*
|
|
10
|
+
* Removes a Slack context from an idea via
|
|
11
|
+
* `DELETE /api/ideas/:id/slack-contexts/:contextId`. Mirrors the task-side
|
|
12
|
+
* shape (LUM-681); no task-side `rm` equivalent exists yet, so this follows
|
|
13
|
+
* the DELETE route contract directly.
|
|
14
|
+
*/
|
|
15
|
+
async function ideaSlackRm(identifier, contextId) {
|
|
16
|
+
if (!identifier || !contextId) {
|
|
17
|
+
console.error('Error: usage: lumo idea slack rm <LUM-I1> <context-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)}/slack-contexts/${encodeURIComponent(contextId)}`, {
|
|
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 slack context ${contextId} 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: slack rm failed (HTTP ${res.status})`);
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
console.log(`Removed Slack context ${contextId} from ${identifier}`);
|
|
63
|
+
}
|
|
@@ -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
|
+
}
|