@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.
Files changed (61) hide show
  1. package/README.md +90 -0
  2. package/dist/cli/src/commands/auth-login.js +55 -0
  3. package/dist/cli/src/commands/auth-logout.js +14 -0
  4. package/dist/cli/src/commands/doc-bind.js +52 -0
  5. package/dist/cli/src/commands/doc-create.js +138 -0
  6. package/dist/cli/src/commands/doc-delete.js +52 -0
  7. package/dist/cli/src/commands/doc-list.js +91 -0
  8. package/dist/cli/src/commands/doc-move.js +113 -0
  9. package/dist/cli/src/commands/doc-share-list.js +62 -0
  10. package/dist/cli/src/commands/doc-share.js +77 -0
  11. package/dist/cli/src/commands/doc-show.js +99 -0
  12. package/dist/cli/src/commands/doc-unbind.js +47 -0
  13. package/dist/cli/src/commands/doc-unshare.js +71 -0
  14. package/dist/cli/src/commands/doc-update.js +144 -0
  15. package/dist/cli/src/commands/hook.js +21 -0
  16. package/dist/cli/src/commands/milestone-create.js +84 -0
  17. package/dist/cli/src/commands/milestone-delete.js +96 -0
  18. package/dist/cli/src/commands/milestone-list.js +55 -0
  19. package/dist/cli/src/commands/milestone-show.js +106 -0
  20. package/dist/cli/src/commands/milestone-update.js +167 -0
  21. package/dist/cli/src/commands/project-list.js +57 -0
  22. package/dist/cli/src/commands/session-attach.js +80 -0
  23. package/dist/cli/src/commands/session-detach.js +60 -0
  24. package/dist/cli/src/commands/session-status.js +58 -0
  25. package/dist/cli/src/commands/sprint-add.js +62 -0
  26. package/dist/cli/src/commands/sprint-close.js +151 -0
  27. package/dist/cli/src/commands/sprint-create.js +80 -0
  28. package/dist/cli/src/commands/sprint-delete.js +88 -0
  29. package/dist/cli/src/commands/sprint-list.js +85 -0
  30. package/dist/cli/src/commands/sprint-remove.js +57 -0
  31. package/dist/cli/src/commands/sprint-show.js +81 -0
  32. package/dist/cli/src/commands/sprint-start.js +68 -0
  33. package/dist/cli/src/commands/sprint-summary.js +138 -0
  34. package/dist/cli/src/commands/sprint-update.js +148 -0
  35. package/dist/cli/src/commands/task-comment.js +95 -0
  36. package/dist/cli/src/commands/task-context.js +137 -0
  37. package/dist/cli/src/commands/task-create.js +202 -0
  38. package/dist/cli/src/commands/task-list.js +94 -0
  39. package/dist/cli/src/commands/task-show.js +74 -0
  40. package/dist/cli/src/commands/task-update.js +468 -0
  41. package/dist/cli/src/commands/whoami.js +16 -0
  42. package/dist/cli/src/index.js +492 -0
  43. package/dist/cli/src/lib/api.js +33 -0
  44. package/dist/cli/src/lib/browser.js +33 -0
  45. package/dist/cli/src/lib/config.js +92 -0
  46. package/dist/cli/src/lib/doc-input.js +80 -0
  47. package/dist/cli/src/lib/doc-sort-order.js +23 -0
  48. package/dist/cli/src/lib/doc-tree.js +54 -0
  49. package/dist/cli/src/lib/format.js +33 -0
  50. package/dist/cli/src/lib/hook-log.js +81 -0
  51. package/dist/cli/src/lib/hook-runner.js +156 -0
  52. package/dist/cli/src/lib/markdown-tiptap.js +7 -0
  53. package/dist/cli/src/lib/prompt.js +71 -0
  54. package/dist/cli/src/lib/resolve-doc-id.js +34 -0
  55. package/dist/cli/src/lib/resolve-doc.js +27 -0
  56. package/dist/cli/src/lib/resolve-member.js +58 -0
  57. package/dist/cli/src/lib/resolve.js +170 -0
  58. package/dist/cli/src/lib/tag-resolver.js +49 -0
  59. package/dist/shared/src/index.js +35 -0
  60. package/dist/shared/src/markdown-tiptap.js +267 -0
  61. package/package.json +48 -0
@@ -0,0 +1,468 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decideSprintAction = decideSprintAction;
4
+ exports.normalizeStatus = normalizeStatus;
5
+ exports.buildUpdatePayload = buildUpdatePayload;
6
+ exports.formatUpdatedTaskLine = formatUpdatedTaskLine;
7
+ exports.taskUpdate = taskUpdate;
8
+ const config_1 = require("../lib/config");
9
+ const api_1 = require("../lib/api");
10
+ const task_create_1 = require("./task-create");
11
+ const tag_resolver_1 = require("../lib/tag-resolver");
12
+ const resolve_1 = require("../lib/resolve");
13
+ const ALLOWED_STATUSES = ['TODO', 'IN_PROGRESS', 'IN_REVIEW', 'DONE'];
14
+ /**
15
+ * Pure function: given the task's current sprint binding and the resolved
16
+ * sprint id from the --sprint flag ('' to clear), decide what action to take.
17
+ *
18
+ * `flag` is the **resolved** sprint id (or '' to clear). It is NOT the raw
19
+ * CLI flag value — the caller must resolve the sprint ref before calling this.
20
+ */
21
+ function decideSprintAction(input) {
22
+ const { currentSprintId, flag } = input;
23
+ if (flag === '') {
24
+ // Clear intent
25
+ if (currentSprintId === null)
26
+ return { type: 'noop-clear' };
27
+ return { type: 'unbind', oldSprintId: currentSprintId };
28
+ }
29
+ // Bind intent (non-empty resolved sprint id)
30
+ if (currentSprintId === null)
31
+ return { type: 'bind', newSprintId: flag };
32
+ if (currentSprintId === flag)
33
+ return { type: 'noop-same', sprintId: flag };
34
+ return { type: 'rebind', oldSprintId: currentSprintId, newSprintId: flag };
35
+ }
36
+ /**
37
+ * Normalize a user-supplied status value. Accepts both `IN_PROGRESS` and
38
+ * `in-progress` forms; returns null on unknown values so the caller can
39
+ * print a context-aware error.
40
+ */
41
+ function normalizeStatus(value) {
42
+ if (!value)
43
+ return null;
44
+ const upper = value.toUpperCase().replace(/-/g, '_');
45
+ return ALLOWED_STATUSES.includes(upper)
46
+ ? upper
47
+ : null;
48
+ }
49
+ /**
50
+ * Translate parsed CLI flags into the wire payload for
51
+ * `PATCH /api/tasks/by-identifier/[identifier]`. Empty-string description /
52
+ * assignee become `null` (clear). Caller is responsible for normalizing
53
+ * status / priority before passing them in.
54
+ */
55
+ function buildUpdatePayload(opts) {
56
+ const payload = {};
57
+ const flagsGiven = [];
58
+ if (opts.title !== undefined) {
59
+ payload.title = opts.title;
60
+ flagsGiven.push('--title');
61
+ }
62
+ if (opts.description !== undefined) {
63
+ payload.description = opts.description === '' ? null : opts.description;
64
+ flagsGiven.push('--description');
65
+ }
66
+ if (opts.status !== undefined) {
67
+ payload.status = opts.status;
68
+ flagsGiven.push('--status');
69
+ }
70
+ if (opts.priority !== undefined) {
71
+ payload.priority = opts.priority;
72
+ flagsGiven.push('--priority');
73
+ }
74
+ if (opts.assignee !== undefined) {
75
+ payload.assigneeRef = opts.assignee === '' ? null : opts.assignee;
76
+ flagsGiven.push('--assignee');
77
+ }
78
+ if (opts.milestone !== undefined) {
79
+ payload.milestoneRef = opts.milestone === '' ? null : opts.milestone;
80
+ flagsGiven.push('--milestone');
81
+ }
82
+ return { payload, flagsGiven };
83
+ }
84
+ /**
85
+ * Single-line success output. Mirrors `formatCreatedTaskLine` for grep-
86
+ * ability — embedded double-quotes are backslash-escaped.
87
+ * Appends a Tags line when tags are present.
88
+ */
89
+ function formatUpdatedTaskLine(task) {
90
+ const escapedTitle = task.title.replace(/"/g, '\\"');
91
+ const head = `Updated ${task.identifier} "${escapedTitle}" ${task.url}`;
92
+ if (task.tags && task.tags.length > 0) {
93
+ return `${head}\nTags: ${task.tags.join(', ')}`;
94
+ }
95
+ return head;
96
+ }
97
+ async function taskUpdate(identifier, opts) {
98
+ if (!identifier || identifier.trim().length === 0) {
99
+ console.error('Error: missing <identifier>. Usage: lumo task update <LUM-42> [options]');
100
+ return 1;
101
+ }
102
+ // CLI-side mutex check: --tag/--tag-id (bulk replace) vs --add-tag/--add-tag-id/--remove-tag/--remove-tag-id (incremental)
103
+ // Must run BEFORE any network call.
104
+ const hasBulk = (opts.tag && opts.tag.length > 0) || (opts.tagId && opts.tagId.length > 0);
105
+ const hasIncremental = (opts.addTag && opts.addTag.length > 0) ||
106
+ (opts.addTagId && opts.addTagId.length > 0) ||
107
+ (opts.removeTag && opts.removeTag.length > 0) ||
108
+ (opts.removeTagId && opts.removeTagId.length > 0);
109
+ if (hasBulk && hasIncremental) {
110
+ console.error('Error: --tag/--tag-id are mutually exclusive with --add-tag/--add-tag-id/--remove-tag/--remove-tag-id');
111
+ return 1;
112
+ }
113
+ // Normalize status / priority before payload build.
114
+ let status;
115
+ if (opts.status !== undefined) {
116
+ const n = normalizeStatus(opts.status);
117
+ if (!n) {
118
+ console.error(`Error: invalid status "${opts.status}". Allowed: todo, in_progress, in_review, done`);
119
+ return 1;
120
+ }
121
+ status = n;
122
+ }
123
+ let priority;
124
+ if (opts.priority !== undefined) {
125
+ const n = (0, task_create_1.normalizePriority)(opts.priority);
126
+ if (!n) {
127
+ console.error(`Error: invalid priority "${opts.priority}". Allowed: low, medium, high, urgent`);
128
+ return 1;
129
+ }
130
+ priority = n;
131
+ }
132
+ const { payload, flagsGiven } = buildUpdatePayload({
133
+ ...opts,
134
+ ...(status !== undefined ? { status } : {}),
135
+ ...(priority !== undefined ? { priority } : {}),
136
+ });
137
+ // Tag fields count as "something to update" even when no other flags given
138
+ const hasTagFields = hasBulk || hasIncremental;
139
+ const hasSprintFlag = opts.sprint !== undefined;
140
+ if (flagsGiven.length === 0 && !hasTagFields && !hasSprintFlag) {
141
+ console.error('Error: provide at least one field to update (--title, --description, --status, --priority, --assignee, --milestone, --sprint, --tag, --add-tag, --remove-tag)');
142
+ return 1;
143
+ }
144
+ const creds = (0, config_1.readCredentials)();
145
+ if (!creds) {
146
+ console.error('Error: not logged in. Run `lumo auth login` first.');
147
+ return 1;
148
+ }
149
+ const envUrl = process.env.LUMO_API_URL?.trim();
150
+ const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
151
+ // Resolve tag refs into ids
152
+ let tagIds;
153
+ let addTagIds;
154
+ let removeTagIds;
155
+ const deps = { apiUrl, token: creds.token };
156
+ try {
157
+ if (hasBulk) {
158
+ tagIds = await (0, tag_resolver_1.resolveTagRefs)({ names: opts.tag ?? [], ids: opts.tagId ?? [] }, deps);
159
+ }
160
+ else if (hasIncremental) {
161
+ if ((opts.addTag && opts.addTag.length > 0) ||
162
+ (opts.addTagId && opts.addTagId.length > 0)) {
163
+ addTagIds = await (0, tag_resolver_1.resolveTagRefs)({ names: opts.addTag ?? [], ids: opts.addTagId ?? [] }, deps);
164
+ }
165
+ if ((opts.removeTag && opts.removeTag.length > 0) ||
166
+ (opts.removeTagId && opts.removeTagId.length > 0)) {
167
+ // NOTE: --remove-tag <unknown-name> creates the Tag via find-or-create, then
168
+ // the detach is a no-op. Net effect: orphan Tag row. Acceptable v1 trade-off.
169
+ removeTagIds = await (0, tag_resolver_1.resolveTagRefs)({ names: opts.removeTag ?? [], ids: opts.removeTagId ?? [] }, deps);
170
+ }
171
+ }
172
+ }
173
+ catch (err) {
174
+ console.error(`Error: ${err.message}`);
175
+ return 1;
176
+ }
177
+ if (tagIds !== undefined)
178
+ payload.tagIds = tagIds;
179
+ if (addTagIds !== undefined)
180
+ payload.addTagIds = addTagIds;
181
+ if (removeTagIds !== undefined)
182
+ payload.removeTagIds = removeTagIds;
183
+ const base = (0, api_1.trimTrailingSlash)(apiUrl);
184
+ // Only do the PATCH when there are actual task fields to update.
185
+ // If --sprint is the only flag given, skip the PATCH entirely.
186
+ const hasPatchFields = flagsGiven.length > 0 || hasTagFields;
187
+ if (hasPatchFields) {
188
+ const patchUrl = `${base}/api/tasks/by-identifier/${encodeURIComponent(identifier)}`;
189
+ let res;
190
+ try {
191
+ res = await fetch(patchUrl, {
192
+ method: 'PATCH',
193
+ headers: {
194
+ Authorization: `Bearer ${creds.token}`,
195
+ 'Content-Type': 'application/json',
196
+ },
197
+ body: JSON.stringify(payload),
198
+ });
199
+ }
200
+ catch (err) {
201
+ const msg = err instanceof Error ? err.message : String(err);
202
+ console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
203
+ return 1;
204
+ }
205
+ if (res.status === 401) {
206
+ console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
207
+ return 1;
208
+ }
209
+ if (res.status === 200) {
210
+ const data = (await res.json());
211
+ // Read tag names off the response (present when tags were changed)
212
+ const responseTagNames = tagIds !== undefined ||
213
+ addTagIds !== undefined ||
214
+ removeTagIds !== undefined
215
+ ? (data.task.taskTags ?? []).map(t => t.name)
216
+ : undefined;
217
+ const taskLine = {
218
+ id: data.task.id,
219
+ identifier: data.task.identifier,
220
+ title: data.task.title,
221
+ url: data.task.url,
222
+ };
223
+ if (responseTagNames !== undefined)
224
+ taskLine.tags = responseTagNames;
225
+ process.stdout.write(formatUpdatedTaskLine(taskLine) + '\n');
226
+ }
227
+ else {
228
+ let serverMsg = null;
229
+ try {
230
+ const errBody = (await res.json());
231
+ if (typeof errBody.error === 'string')
232
+ serverMsg = errBody.error;
233
+ }
234
+ catch {
235
+ // Body wasn't JSON; fall through to status-only message
236
+ }
237
+ if (serverMsg) {
238
+ console.error(`Error: ${serverMsg}`);
239
+ }
240
+ else {
241
+ console.error(`Error: task update failed (HTTP ${res.status})`);
242
+ }
243
+ return 1;
244
+ }
245
+ }
246
+ // Sprint flag handling — runs after successful PATCH (or as standalone op)
247
+ if (hasSprintFlag) {
248
+ // GET /api/tasks/by-identifier/<id> to learn currentSprintId and teamId
249
+ const showUrl = `${base}/api/tasks/by-identifier/${encodeURIComponent(identifier)}`;
250
+ let showRes;
251
+ try {
252
+ showRes = await fetch(showUrl, {
253
+ headers: { Authorization: `Bearer ${creds.token}` },
254
+ });
255
+ }
256
+ catch (err) {
257
+ const msg = err instanceof Error ? err.message : String(err);
258
+ console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
259
+ return 1;
260
+ }
261
+ if (showRes.status === 401) {
262
+ console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
263
+ return 1;
264
+ }
265
+ if (!showRes.ok) {
266
+ console.error(`Error: task lookup failed (HTTP ${showRes.status})`);
267
+ return 1;
268
+ }
269
+ const showData = (await showRes.json());
270
+ const task = showData.task;
271
+ // Resolve new sprint id ('' → clear, non-empty → resolve ref).
272
+ // Also store the sprint number/name once resolved so we don't need extra
273
+ // GETs for output formatting on bind/rebind/noop-same.
274
+ let resolvedSprintId = '';
275
+ let resolvedSprintNumber = null;
276
+ if (opts.sprint !== '') {
277
+ const workspaceSlug = creds.workspaceSlug ?? '';
278
+ let sprint;
279
+ try {
280
+ sprint = await (0, resolve_1.resolveSprintId)(base, creds.token, opts.sprint, undefined, workspaceSlug);
281
+ }
282
+ catch (err) {
283
+ console.error(`Error: ${err.message}`);
284
+ return 1;
285
+ }
286
+ // UUID path: resolveSprintId returns placeholder with teamId === ''
287
+ if (sprint.teamId === '') {
288
+ let sprintRes;
289
+ try {
290
+ sprintRes = await fetch(`${base}/api/sprints/${sprint.id}`, {
291
+ headers: { Authorization: `Bearer ${creds.token}` },
292
+ });
293
+ }
294
+ catch (err) {
295
+ const msg = err instanceof Error ? err.message : String(err);
296
+ console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
297
+ return 1;
298
+ }
299
+ if (!sprintRes.ok) {
300
+ console.error(`Error: sprint lookup failed (HTTP ${sprintRes.status})`);
301
+ return 1;
302
+ }
303
+ const { sprint: full } = (await sprintRes.json());
304
+ sprint = {
305
+ id: full.id,
306
+ number: full.number,
307
+ name: full.name,
308
+ teamId: full.teamId,
309
+ };
310
+ }
311
+ // Cross-team check
312
+ if (sprint.teamId !== task.teamId) {
313
+ console.error(`Error: Sprint binding skipped: sprint #${sprint.number} belongs to a different team than task ${task.identifier}`);
314
+ return 1;
315
+ }
316
+ resolvedSprintId = sprint.id;
317
+ resolvedSprintNumber = sprint.number;
318
+ }
319
+ const action = decideSprintAction({
320
+ currentSprintId: task.sprintId,
321
+ flag: resolvedSprintId,
322
+ });
323
+ if (action.type === 'noop-clear') {
324
+ process.stdout.write(`Task ${task.identifier} has no sprint binding\n`);
325
+ return;
326
+ }
327
+ if (action.type === 'noop-same') {
328
+ // resolvedSprintNumber is always set when we reach this branch
329
+ const label = resolvedSprintNumber !== null
330
+ ? `#${resolvedSprintNumber}`
331
+ : action.sprintId;
332
+ process.stdout.write(`Task ${task.identifier} already in sprint ${label}\n`);
333
+ return;
334
+ }
335
+ if (action.type === 'unbind') {
336
+ // Fetch the old sprint number for display (we only know the id here)
337
+ let oldNumber = null;
338
+ try {
339
+ const sprintRes = await fetch(`${base}/api/sprints/${action.oldSprintId}`, { headers: { Authorization: `Bearer ${creds.token}` } });
340
+ if (sprintRes.ok) {
341
+ const body = (await sprintRes.json());
342
+ oldNumber = body.sprint.number;
343
+ }
344
+ }
345
+ catch {
346
+ // non-critical — we'll fall back to '-'
347
+ }
348
+ let deleteRes;
349
+ try {
350
+ deleteRes = await fetch(`${base}/api/sprints/${action.oldSprintId}/tasks/${task.id}`, {
351
+ method: 'DELETE',
352
+ headers: { Authorization: `Bearer ${creds.token}` },
353
+ });
354
+ }
355
+ catch (err) {
356
+ const msg = err instanceof Error ? err.message : String(err);
357
+ console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
358
+ return 1;
359
+ }
360
+ if (!deleteRes.ok) {
361
+ let errMsg = `sprint unbind failed (HTTP ${deleteRes.status})`;
362
+ try {
363
+ const b = (await deleteRes.json());
364
+ if (b.error)
365
+ errMsg = b.error;
366
+ }
367
+ catch { /* ignore */ }
368
+ console.error(`Error: ${errMsg}`);
369
+ return 1;
370
+ }
371
+ const oldLabel = oldNumber !== null ? `#${oldNumber}` : action.oldSprintId;
372
+ process.stdout.write(`Unbound ${task.identifier} from sprint ${oldLabel}\n`);
373
+ return;
374
+ }
375
+ if (action.type === 'bind') {
376
+ let postRes;
377
+ try {
378
+ postRes = await fetch(`${base}/api/sprints/${action.newSprintId}/tasks`, {
379
+ method: 'POST',
380
+ headers: {
381
+ Authorization: `Bearer ${creds.token}`,
382
+ 'Content-Type': 'application/json',
383
+ },
384
+ body: JSON.stringify({ taskId: task.id }),
385
+ });
386
+ }
387
+ catch (err) {
388
+ const msg = err instanceof Error ? err.message : String(err);
389
+ console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
390
+ return 1;
391
+ }
392
+ if (!postRes.ok) {
393
+ let errMsg = `sprint bind failed (HTTP ${postRes.status})`;
394
+ try {
395
+ const b = (await postRes.json());
396
+ if (b.error)
397
+ errMsg = b.error;
398
+ }
399
+ catch { /* ignore */ }
400
+ console.error(`Error: ${errMsg}`);
401
+ return 1;
402
+ }
403
+ // resolvedSprintNumber is set from the resolveSprintId call above
404
+ const newLabel = resolvedSprintNumber !== null ? `#${resolvedSprintNumber}` : action.newSprintId;
405
+ process.stdout.write(`Sprint: - → ${newLabel}\n`);
406
+ return;
407
+ }
408
+ if (action.type === 'rebind') {
409
+ // Fetch old sprint number for display (only know id from currentSprintId)
410
+ let oldNumber = null;
411
+ try {
412
+ const oldSprintRes = await fetch(`${base}/api/sprints/${action.oldSprintId}`, { headers: { Authorization: `Bearer ${creds.token}` } });
413
+ if (oldSprintRes.ok) {
414
+ const b = (await oldSprintRes.json());
415
+ oldNumber = b.sprint.number;
416
+ }
417
+ }
418
+ catch {
419
+ // non-critical
420
+ }
421
+ // Step 1: DELETE old sprint binding
422
+ let deleteRes;
423
+ try {
424
+ deleteRes = await fetch(`${base}/api/sprints/${action.oldSprintId}/tasks/${task.id}`, {
425
+ method: 'DELETE',
426
+ headers: { Authorization: `Bearer ${creds.token}` },
427
+ });
428
+ }
429
+ catch {
430
+ console.error(`Task may be in inconsistent sprint state, re-run or check Web UI`);
431
+ return 1;
432
+ }
433
+ if (!deleteRes.ok) {
434
+ console.error(`Task may be in inconsistent sprint state, re-run or check Web UI`);
435
+ return 1;
436
+ }
437
+ // Step 2: POST new sprint binding
438
+ let postRes;
439
+ try {
440
+ postRes = await fetch(`${base}/api/sprints/${action.newSprintId}/tasks`, {
441
+ method: 'POST',
442
+ headers: {
443
+ Authorization: `Bearer ${creds.token}`,
444
+ 'Content-Type': 'application/json',
445
+ },
446
+ body: JSON.stringify({ taskId: task.id }),
447
+ });
448
+ }
449
+ catch {
450
+ console.error(`Task may be in inconsistent sprint state, re-run or check Web UI`);
451
+ return 1;
452
+ }
453
+ if (!postRes.ok) {
454
+ console.error(`Task may be in inconsistent sprint state, re-run or check Web UI`);
455
+ return 1;
456
+ }
457
+ const oldLabel = oldNumber !== null ? `#${oldNumber}` : '-';
458
+ // resolvedSprintNumber is set from the resolveSprintId call above
459
+ const newLabel = resolvedSprintNumber !== null ? `#${resolvedSprintNumber}` : action.newSprintId;
460
+ process.stdout.write(`Sprint: ${oldLabel} → ${newLabel}\n`);
461
+ return;
462
+ }
463
+ }
464
+ // If only --sprint was given (no PATCH fields), we've already returned above.
465
+ // But if --sprint wasn't given and PATCH succeeded, we returned inside the
466
+ // PATCH success block. This path is unreachable in normal operation.
467
+ return;
468
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.whoami = whoami;
4
+ const config_1 = require("../lib/config");
5
+ async function whoami() {
6
+ const creds = (0, config_1.readCredentials)();
7
+ if (!creds) {
8
+ console.log('Not logged in');
9
+ console.log('Run `lumo auth login` to log in');
10
+ return 1;
11
+ }
12
+ console.log(`Logged in as ${creds.email}`);
13
+ console.log(` Workspace: ${creds.workspaceName} (${creds.workspaceSlug})`);
14
+ console.log(` Key: ${creds.apiKeyName} (${creds.apiKeyPrefix})`);
15
+ console.log(` API: ${creds.apiUrl}`);
16
+ }