@lumoai/cli 1.0.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/README.md +90 -0
- package/dist/cli/src/commands/auth-login.js +55 -0
- package/dist/cli/src/commands/auth-logout.js +14 -0
- package/dist/cli/src/commands/doc-bind.js +52 -0
- package/dist/cli/src/commands/doc-create.js +138 -0
- package/dist/cli/src/commands/doc-delete.js +52 -0
- package/dist/cli/src/commands/doc-list.js +91 -0
- package/dist/cli/src/commands/doc-move.js +113 -0
- package/dist/cli/src/commands/doc-share-list.js +62 -0
- package/dist/cli/src/commands/doc-share.js +77 -0
- package/dist/cli/src/commands/doc-show.js +99 -0
- package/dist/cli/src/commands/doc-unbind.js +47 -0
- package/dist/cli/src/commands/doc-unshare.js +71 -0
- package/dist/cli/src/commands/doc-update.js +144 -0
- package/dist/cli/src/commands/hook.js +21 -0
- package/dist/cli/src/commands/milestone-create.js +84 -0
- package/dist/cli/src/commands/milestone-delete.js +96 -0
- package/dist/cli/src/commands/milestone-list.js +55 -0
- package/dist/cli/src/commands/milestone-show.js +106 -0
- package/dist/cli/src/commands/milestone-update.js +167 -0
- package/dist/cli/src/commands/project-list.js +57 -0
- package/dist/cli/src/commands/session-attach.js +80 -0
- package/dist/cli/src/commands/session-detach.js +60 -0
- package/dist/cli/src/commands/session-status.js +58 -0
- package/dist/cli/src/commands/sprint-add.js +62 -0
- package/dist/cli/src/commands/sprint-close.js +151 -0
- package/dist/cli/src/commands/sprint-create.js +80 -0
- package/dist/cli/src/commands/sprint-delete.js +88 -0
- package/dist/cli/src/commands/sprint-list.js +85 -0
- package/dist/cli/src/commands/sprint-remove.js +57 -0
- package/dist/cli/src/commands/sprint-show.js +81 -0
- package/dist/cli/src/commands/sprint-start.js +68 -0
- package/dist/cli/src/commands/sprint-summary.js +138 -0
- package/dist/cli/src/commands/sprint-update.js +148 -0
- package/dist/cli/src/commands/task-comment.js +95 -0
- package/dist/cli/src/commands/task-context.js +137 -0
- package/dist/cli/src/commands/task-create.js +202 -0
- package/dist/cli/src/commands/task-list.js +94 -0
- package/dist/cli/src/commands/task-show.js +74 -0
- package/dist/cli/src/commands/task-update.js +468 -0
- package/dist/cli/src/commands/whoami.js +16 -0
- package/dist/cli/src/index.js +492 -0
- package/dist/cli/src/lib/api.js +33 -0
- package/dist/cli/src/lib/browser.js +33 -0
- package/dist/cli/src/lib/config.js +92 -0
- package/dist/cli/src/lib/doc-input.js +80 -0
- package/dist/cli/src/lib/doc-sort-order.js +23 -0
- package/dist/cli/src/lib/doc-tree.js +54 -0
- package/dist/cli/src/lib/format.js +33 -0
- package/dist/cli/src/lib/hook-log.js +81 -0
- package/dist/cli/src/lib/hook-runner.js +156 -0
- package/dist/cli/src/lib/markdown-tiptap.js +7 -0
- package/dist/cli/src/lib/prompt.js +71 -0
- package/dist/cli/src/lib/resolve-doc-id.js +34 -0
- package/dist/cli/src/lib/resolve-doc.js +27 -0
- package/dist/cli/src/lib/resolve-member.js +58 -0
- package/dist/cli/src/lib/resolve.js +170 -0
- package/dist/cli/src/lib/tag-resolver.js +49 -0
- package/dist/shared/src/index.js +35 -0
- package/dist/shared/src/markdown-tiptap.js +267 -0
- package/package.json +48 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertSameTeam = assertSameTeam;
|
|
4
|
+
exports.normalizePriority = normalizePriority;
|
|
5
|
+
exports.formatCreatedTaskLine = formatCreatedTaskLine;
|
|
6
|
+
exports.taskCreate = taskCreate;
|
|
7
|
+
const config_1 = require("../lib/config");
|
|
8
|
+
const api_1 = require("../lib/api");
|
|
9
|
+
const tag_resolver_1 = require("../lib/tag-resolver");
|
|
10
|
+
const resolve_1 = require("../lib/resolve");
|
|
11
|
+
const ALLOWED_PRIORITIES = ['LOW', 'MEDIUM', 'HIGH', 'URGENT'];
|
|
12
|
+
/**
|
|
13
|
+
* Assert that the sprint belongs to the same team as the newly created task.
|
|
14
|
+
* Throws with a human-readable message on mismatch (caller console.errors it).
|
|
15
|
+
*/
|
|
16
|
+
function assertSameTeam(task, sprint) {
|
|
17
|
+
if (task.teamId !== sprint.teamId) {
|
|
18
|
+
throw new Error(`Sprint binding skipped: sprint #${sprint.number} belongs to team "${sprint.teamId}", task belongs to "${task.teamId}"`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Bind a newly created task to a sprint.
|
|
23
|
+
* - Resolves the sprint ref (number or UUID).
|
|
24
|
+
* - If resolveSprintId returned the UUID placeholder (teamId === ''), does a
|
|
25
|
+
* follow-up GET /api/sprints/<id> to fill in { teamId, number, name }.
|
|
26
|
+
* - Asserts same-team, then POSTs to /api/sprints/<sprintId>/tasks.
|
|
27
|
+
* Returns the sprint info on success, or throws on failure.
|
|
28
|
+
*/
|
|
29
|
+
async function bindTaskToSprint(base, token, workspaceSlug, sprintRef, task) {
|
|
30
|
+
let sprint = await (0, resolve_1.resolveSprintId)(base, token, sprintRef, undefined, workspaceSlug);
|
|
31
|
+
// UUID path: resolveSprintId returns a placeholder with teamId === ''
|
|
32
|
+
if (sprint.teamId === '') {
|
|
33
|
+
const sprintRes = await fetch(`${base}/api/sprints/${sprint.id}`, {
|
|
34
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
35
|
+
});
|
|
36
|
+
if (!sprintRes.ok) {
|
|
37
|
+
throw new Error(`sprint lookup failed (HTTP ${sprintRes.status})`);
|
|
38
|
+
}
|
|
39
|
+
const { sprint: full } = (await sprintRes.json());
|
|
40
|
+
sprint = { id: full.id, number: full.number, name: full.name, teamId: full.teamId };
|
|
41
|
+
}
|
|
42
|
+
assertSameTeam(task, sprint);
|
|
43
|
+
const bindRes = await fetch(`${base}/api/sprints/${sprint.id}/tasks`, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: {
|
|
46
|
+
Authorization: `Bearer ${token}`,
|
|
47
|
+
'Content-Type': 'application/json',
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify({ taskId: task.id }),
|
|
50
|
+
});
|
|
51
|
+
if (!bindRes.ok) {
|
|
52
|
+
let errMsg = `sprint bind failed (HTTP ${bindRes.status})`;
|
|
53
|
+
try {
|
|
54
|
+
const errBody = (await bindRes.json());
|
|
55
|
+
if (errBody.error)
|
|
56
|
+
errMsg = errBody.error;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// ignore
|
|
60
|
+
}
|
|
61
|
+
throw new Error(errMsg);
|
|
62
|
+
}
|
|
63
|
+
return sprint;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Uppercase + validate a priority string. Returns null on unknown values so
|
|
67
|
+
* the caller can print a context-aware error.
|
|
68
|
+
*/
|
|
69
|
+
function normalizePriority(value) {
|
|
70
|
+
if (!value)
|
|
71
|
+
return null;
|
|
72
|
+
const upper = value.toUpperCase();
|
|
73
|
+
return ALLOWED_PRIORITIES.includes(upper)
|
|
74
|
+
? upper
|
|
75
|
+
: null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Format the single-line success output. Title is double-quoted; embedded
|
|
79
|
+
* double-quotes are backslash-escaped so the output stays parseable as one
|
|
80
|
+
* shell-safe token group. An optional Tags line is appended when tags are
|
|
81
|
+
* present.
|
|
82
|
+
*/
|
|
83
|
+
function formatCreatedTaskLine(task) {
|
|
84
|
+
const escapedTitle = task.title.replace(/"/g, '\\"');
|
|
85
|
+
const head = `Created ${task.identifier} "${escapedTitle}" ${task.url}`;
|
|
86
|
+
if (task.tags && task.tags.length > 0) {
|
|
87
|
+
return `${head}\nTags: ${task.tags.join(', ')}`;
|
|
88
|
+
}
|
|
89
|
+
return head;
|
|
90
|
+
}
|
|
91
|
+
async function taskCreate(title, opts) {
|
|
92
|
+
if (!title || title.trim().length === 0) {
|
|
93
|
+
console.error('Error: missing <title>. Usage: lumo task create <title> [options]');
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
const creds = (0, config_1.readCredentials)();
|
|
97
|
+
if (!creds) {
|
|
98
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
// Priority: default LOW, validate if provided
|
|
102
|
+
let priority = 'LOW';
|
|
103
|
+
if (opts.priority !== undefined) {
|
|
104
|
+
const normalized = normalizePriority(opts.priority);
|
|
105
|
+
if (!normalized) {
|
|
106
|
+
console.error(`Error: invalid priority "${opts.priority}". Allowed: low, medium, high, urgent`);
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
priority = normalized;
|
|
110
|
+
}
|
|
111
|
+
const envUrl = process.env.LUMO_API_URL?.trim();
|
|
112
|
+
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
113
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
114
|
+
const url = `${base}/api/tasks`;
|
|
115
|
+
let tagIds;
|
|
116
|
+
if ((opts.tag && opts.tag.length > 0) ||
|
|
117
|
+
(opts.tagId && opts.tagId.length > 0)) {
|
|
118
|
+
try {
|
|
119
|
+
tagIds = await (0, tag_resolver_1.resolveTagRefs)({ names: opts.tag ?? [], ids: opts.tagId ?? [] }, { apiUrl, token: creds.token });
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
console.error(`Error: ${err.message}`);
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const body = {
|
|
127
|
+
title: title.trim(),
|
|
128
|
+
priority,
|
|
129
|
+
};
|
|
130
|
+
if (opts.description !== undefined)
|
|
131
|
+
body.description = opts.description;
|
|
132
|
+
if (opts.project !== undefined)
|
|
133
|
+
body.projectRef = opts.project;
|
|
134
|
+
if (opts.assignee !== undefined)
|
|
135
|
+
body.assigneeRef = opts.assignee;
|
|
136
|
+
if (opts.milestone !== undefined)
|
|
137
|
+
body.milestoneRef = opts.milestone;
|
|
138
|
+
if (tagIds && tagIds.length > 0)
|
|
139
|
+
body.tagIds = tagIds;
|
|
140
|
+
let res;
|
|
141
|
+
try {
|
|
142
|
+
res = await fetch(url, {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: {
|
|
145
|
+
Authorization: `Bearer ${creds.token}`,
|
|
146
|
+
'Content-Type': 'application/json',
|
|
147
|
+
},
|
|
148
|
+
body: JSON.stringify(body),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
153
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
156
|
+
if (res.status === 401) {
|
|
157
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
158
|
+
return 1;
|
|
159
|
+
}
|
|
160
|
+
if (res.status === 201) {
|
|
161
|
+
const data = (await res.json());
|
|
162
|
+
const responseTagNames = (data.task.taskTags ?? []).map(t => t.name);
|
|
163
|
+
process.stdout.write(formatCreatedTaskLine({ ...data.task, tags: responseTagNames }) + '\n');
|
|
164
|
+
// Sprint binding (optional): if --sprint was passed, bind the new task.
|
|
165
|
+
if (opts.sprint) {
|
|
166
|
+
const workspaceSlug = creds.workspaceSlug ?? '';
|
|
167
|
+
try {
|
|
168
|
+
const sprint = await bindTaskToSprint(base, creds.token, workspaceSlug, opts.sprint, { id: data.task.id, teamId: data.task.teamId, identifier: data.task.identifier });
|
|
169
|
+
process.stdout.write(`Sprint: #${sprint.number} "${sprint.name}"\n`);
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
173
|
+
// Spec's literal mismatch output — print bare, no "Error:" prefix
|
|
174
|
+
if (msg.startsWith('Sprint binding skipped:')) {
|
|
175
|
+
console.error(msg);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
console.error(`Error: ${msg}`);
|
|
179
|
+
}
|
|
180
|
+
return 1;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// Surface server error message when present, otherwise the status
|
|
186
|
+
let serverMsg = null;
|
|
187
|
+
try {
|
|
188
|
+
const errBody = (await res.json());
|
|
189
|
+
if (typeof errBody.error === 'string')
|
|
190
|
+
serverMsg = errBody.error;
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// Body wasn't JSON; fall through to status-only message
|
|
194
|
+
}
|
|
195
|
+
if (serverMsg) {
|
|
196
|
+
console.error(`Error: ${serverMsg}`);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.error(`Error: task create failed (HTTP ${res.status})`);
|
|
200
|
+
}
|
|
201
|
+
return 1;
|
|
202
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatTaskListTable = void 0;
|
|
4
|
+
exports.resolveTaskListEndpoint = resolveTaskListEndpoint;
|
|
5
|
+
exports.taskList = taskList;
|
|
6
|
+
const config_1 = require("../lib/config");
|
|
7
|
+
const api_1 = require("../lib/api");
|
|
8
|
+
const format_1 = require("../lib/format");
|
|
9
|
+
const resolve_1 = require("../lib/resolve");
|
|
10
|
+
var format_2 = require("../lib/format");
|
|
11
|
+
Object.defineProperty(exports, "formatTaskListTable", { enumerable: true, get: function () { return format_2.formatTaskListTable; } });
|
|
12
|
+
const ALLOWED_STATUSES = ['TODO', 'IN_PROGRESS', 'IN_REVIEW', 'DONE'];
|
|
13
|
+
function resolveTaskListEndpoint(base, milestoneId) {
|
|
14
|
+
if (!milestoneId)
|
|
15
|
+
return `${base}/api/tasks/me`;
|
|
16
|
+
return `${base}/api/milestones/${milestoneId}/tasks?assigned=me`;
|
|
17
|
+
}
|
|
18
|
+
function normalizeStatusFilter(value) {
|
|
19
|
+
if (!value)
|
|
20
|
+
return null;
|
|
21
|
+
const upper = value.toUpperCase().replace(/-/g, '_');
|
|
22
|
+
return ALLOWED_STATUSES.includes(upper)
|
|
23
|
+
? upper
|
|
24
|
+
: null;
|
|
25
|
+
}
|
|
26
|
+
async function taskList(opts) {
|
|
27
|
+
let statusFilter;
|
|
28
|
+
if (opts.status !== undefined) {
|
|
29
|
+
const n = normalizeStatusFilter(opts.status);
|
|
30
|
+
if (!n) {
|
|
31
|
+
console.error(`Error: invalid status "${opts.status}". Allowed: todo, in_progress, in_review, done`);
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
statusFilter = n;
|
|
35
|
+
}
|
|
36
|
+
const limit = opts.limit !== undefined ? parseInt(opts.limit, 10) : undefined;
|
|
37
|
+
if (limit !== undefined && (Number.isNaN(limit) || limit < 1)) {
|
|
38
|
+
console.error(`Error: invalid --limit "${opts.limit}" (expected a positive integer)`);
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
const creds = (0, config_1.readCredentials)();
|
|
42
|
+
if (!creds) {
|
|
43
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
const envUrl = process.env.LUMO_API_URL?.trim();
|
|
47
|
+
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
48
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
49
|
+
let milestoneId;
|
|
50
|
+
if (opts.milestone !== undefined) {
|
|
51
|
+
try {
|
|
52
|
+
const resolved = await (0, resolve_1.resolveMilestoneId)(base, creds.token, opts.milestone, opts.project);
|
|
53
|
+
milestoneId = resolved.id;
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const url = resolveTaskListEndpoint(base, milestoneId);
|
|
61
|
+
let res;
|
|
62
|
+
try {
|
|
63
|
+
res = await fetch(url, {
|
|
64
|
+
headers: { Authorization: `Bearer ${creds.token}` },
|
|
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.ok) {
|
|
77
|
+
console.error(`Error: task list failed (HTTP ${res.status})`);
|
|
78
|
+
return 1;
|
|
79
|
+
}
|
|
80
|
+
const data = (await res.json());
|
|
81
|
+
// Server returns the full assigned-to-me set. Filters are client-side
|
|
82
|
+
// because /api/tasks/me doesn't accept status/project query params yet
|
|
83
|
+
// — when it does, switch to query params and drop this filter block.
|
|
84
|
+
let tasks = data.tasks;
|
|
85
|
+
if (statusFilter)
|
|
86
|
+
tasks = tasks.filter(t => t.status === statusFilter);
|
|
87
|
+
if (opts.project) {
|
|
88
|
+
const ref = opts.project.toLowerCase();
|
|
89
|
+
tasks = tasks.filter(t => t.project.name.toLowerCase() === ref);
|
|
90
|
+
}
|
|
91
|
+
if (limit !== undefined)
|
|
92
|
+
tasks = tasks.slice(0, limit);
|
|
93
|
+
process.stdout.write((0, format_1.formatTaskListTable)(tasks) + '\n');
|
|
94
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatTaskShow = formatTaskShow;
|
|
4
|
+
exports.taskShow = taskShow;
|
|
5
|
+
const config_1 = require("../lib/config");
|
|
6
|
+
const api_1 = require("../lib/api");
|
|
7
|
+
/**
|
|
8
|
+
* Render task detail as a key:value block. Multi-line description is
|
|
9
|
+
* indented under a `Description:` label so the structure stays scannable.
|
|
10
|
+
*/
|
|
11
|
+
function formatTaskShow(task) {
|
|
12
|
+
const lines = [];
|
|
13
|
+
lines.push(`${task.identifier} ${task.title}`);
|
|
14
|
+
lines.push(`Status: ${task.status}`);
|
|
15
|
+
lines.push(`Priority: ${task.priority}`);
|
|
16
|
+
lines.push(`Project: ${task.project.name}`);
|
|
17
|
+
if (task.assignee) {
|
|
18
|
+
const detail = task.assignee.email && task.assignee.type === 'member'
|
|
19
|
+
? `${task.assignee.label} <${task.assignee.email}>`
|
|
20
|
+
: task.assignee.label;
|
|
21
|
+
lines.push(`Assignee: ${detail}`);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
lines.push('Assignee: (unassigned)');
|
|
25
|
+
}
|
|
26
|
+
lines.push(`URL: ${task.url}`);
|
|
27
|
+
if (task.description && task.description.trim().length > 0) {
|
|
28
|
+
lines.push('');
|
|
29
|
+
lines.push('Description:');
|
|
30
|
+
for (const line of task.description.split('\n')) {
|
|
31
|
+
lines.push(` ${line}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return lines.join('\n');
|
|
35
|
+
}
|
|
36
|
+
async function taskShow(identifier) {
|
|
37
|
+
if (!identifier) {
|
|
38
|
+
console.error('Error: missing <identifier>. Usage: lumo task show <LUM-42>');
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
const creds = (0, config_1.readCredentials)();
|
|
42
|
+
if (!creds) {
|
|
43
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
const envUrl = process.env.LUMO_API_URL?.trim();
|
|
47
|
+
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
48
|
+
const url = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/tasks/by-identifier/${encodeURIComponent(identifier)}`;
|
|
49
|
+
let res;
|
|
50
|
+
try {
|
|
51
|
+
res = await fetch(url, {
|
|
52
|
+
headers: { Authorization: `Bearer ${creds.token}` },
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
57
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
if (res.status === 401) {
|
|
61
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
if (res.status === 404) {
|
|
65
|
+
console.error(`Error: task ${identifier} not found in workspace ${creds.workspaceSlug}`);
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
console.error(`Error: task show failed (HTTP ${res.status})`);
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
const data = (await res.json());
|
|
73
|
+
process.stdout.write(formatTaskShow(data.task) + '\n');
|
|
74
|
+
}
|