@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,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeRole = normalizeRole;
4
+ exports.docShare = docShare;
5
+ const config_1 = require("../lib/config");
6
+ const api_1 = require("../lib/api");
7
+ const resolve_doc_id_1 = require("../lib/resolve-doc-id");
8
+ const resolve_member_1 = require("../lib/resolve-member");
9
+ const ALLOWED_ROLES = ['VIEWER', 'EDITOR', 'MANAGER'];
10
+ function normalizeRole(value) {
11
+ if (!value)
12
+ return null;
13
+ const upper = value.toUpperCase();
14
+ return ALLOWED_ROLES.includes(upper)
15
+ ? upper
16
+ : null;
17
+ }
18
+ async function docShare(docRef, memberRef, opts) {
19
+ if (!docRef || !memberRef) {
20
+ console.error('Error: usage: lumo doc share <doc> <member> [--role viewer|editor|manager]');
21
+ return 1;
22
+ }
23
+ let role = 'VIEWER';
24
+ if (opts.role !== undefined) {
25
+ const normalized = normalizeRole(opts.role);
26
+ if (!normalized) {
27
+ console.error(`Error: invalid role "${opts.role}". Allowed: viewer, editor, manager`);
28
+ return 1;
29
+ }
30
+ role = normalized;
31
+ }
32
+ const creds = (0, config_1.readCredentials)();
33
+ if (!creds) {
34
+ console.error('Error: not logged in. Run `lumo auth login` first.');
35
+ return 1;
36
+ }
37
+ const envUrl = process.env.LUMO_API_URL?.trim();
38
+ const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
39
+ const docId = await (0, resolve_doc_id_1.lookupDocId)(apiUrl, creds.token, docRef);
40
+ if (!docId) {
41
+ console.error(`Error: Document not found: ${docRef}`);
42
+ return 1;
43
+ }
44
+ let members;
45
+ try {
46
+ members = await (0, resolve_member_1.fetchMembers)(apiUrl, creds.token);
47
+ }
48
+ catch (err) {
49
+ console.error(`Error: ${err.message}`);
50
+ return 1;
51
+ }
52
+ const result = (0, resolve_member_1.resolveMember)(members, memberRef, creds.email);
53
+ if (result.kind === 'not_found') {
54
+ console.error(`Error: no member matches "${result.query}".`);
55
+ return 1;
56
+ }
57
+ if (result.kind === 'ambiguous') {
58
+ const list = result.candidates.map(c => c.email ?? c.displayName).join(', ');
59
+ console.error(`Error: multiple members match "${result.query}": ${list}`);
60
+ return 1;
61
+ }
62
+ const url = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/documents/${docId}/shares`;
63
+ const res = await fetch(url, {
64
+ method: 'POST',
65
+ headers: {
66
+ Authorization: `Bearer ${creds.token}`,
67
+ 'Content-Type': 'application/json',
68
+ },
69
+ body: JSON.stringify({ memberId: result.member.memberId, role }),
70
+ });
71
+ if (!res.ok) {
72
+ const text = await res.text();
73
+ console.error(`Error: ${res.status} ${res.statusText}: ${text}`);
74
+ return 1;
75
+ }
76
+ console.log(`Shared ${docId} ↔ ${result.member.displayName} (${role})`);
77
+ }
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatShowOutput = formatShowOutput;
4
+ exports.docShow = docShow;
5
+ const config_1 = require("../lib/config");
6
+ const api_1 = require("../lib/api");
7
+ const markdown_tiptap_1 = require("../lib/markdown-tiptap");
8
+ const resolve_doc_id_1 = require("../lib/resolve-doc-id");
9
+ function scopeLabel(s) {
10
+ if (s === 'PRIVATE')
11
+ return 'personal';
12
+ if (s === 'WORKSPACE')
13
+ return 'workspace';
14
+ return 'shared';
15
+ }
16
+ function formatShowOutput(vm) {
17
+ const lines = [
18
+ `ID: ${vm.id}`,
19
+ `Title: ${vm.title}`,
20
+ `Scope: ${scopeLabel(vm.scope)}`,
21
+ `Project: ${vm.projectName ?? '-'}`,
22
+ `Created: ${vm.createdAt}`,
23
+ `Updated: ${vm.updatedAt}`,
24
+ `Mentioned tasks: ${vm.mentionedTasks.length ? vm.mentionedTasks.join(', ') : '-'}`,
25
+ ];
26
+ const header = lines.join('\n');
27
+ return vm.bodyMarkdown ? `${header}\n\n${vm.bodyMarkdown}` : header;
28
+ }
29
+ async function docShow(reference) {
30
+ if (!reference) {
31
+ console.error('Error: missing <doc>. Usage: lumo doc show <doc>');
32
+ return 1;
33
+ }
34
+ const creds = (0, config_1.readCredentials)();
35
+ if (!creds) {
36
+ console.error('Error: not logged in. Run `lumo auth login` first.');
37
+ return 1;
38
+ }
39
+ const envUrl = process.env.LUMO_API_URL?.trim();
40
+ const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
41
+ const id = await (0, resolve_doc_id_1.lookupDocId)(apiUrl, creds.token, reference);
42
+ if (!id) {
43
+ console.error(`Error: Document not found: ${reference}`);
44
+ return 1;
45
+ }
46
+ const res = await fetch(`${(0, api_1.trimTrailingSlash)(apiUrl)}/api/documents/${id}`, {
47
+ headers: { Authorization: `Bearer ${creds.token}` },
48
+ });
49
+ if (!res.ok) {
50
+ const text = await res.text();
51
+ console.error(`Error: ${res.status} ${res.statusText}: ${text}`);
52
+ return 1;
53
+ }
54
+ // The exact shape of the GET response is not guaranteed; defensively unwrap.
55
+ const data = (await res.json());
56
+ const d = data.document;
57
+ if (!d) {
58
+ console.error('Error: server returned an empty document response');
59
+ return 1;
60
+ }
61
+ // Server returns `contentMarkdown` derived from the HTML body (LUM-83+).
62
+ // Fall back to parsing the raw content as legacy Tiptap JSON for docs
63
+ // written before the storage shape changed.
64
+ let body = '';
65
+ if (d.contentMarkdown) {
66
+ body = d.contentMarkdown;
67
+ }
68
+ else if (d.content) {
69
+ try {
70
+ body = (0, markdown_tiptap_1.tiptapToMarkdown)(JSON.parse(d.content));
71
+ }
72
+ catch {
73
+ body = '';
74
+ }
75
+ }
76
+ // mentions may carry a flat identifier or a (team.identifier + number) shape.
77
+ const mentionedTasks = (d.mentions ?? [])
78
+ .map(m => {
79
+ if (!m.task)
80
+ return null;
81
+ if (m.task.identifier)
82
+ return m.task.identifier;
83
+ if (m.task.team?.identifier && typeof m.task.number === 'number') {
84
+ return `${m.task.team.identifier}-${m.task.number}`;
85
+ }
86
+ return null;
87
+ })
88
+ .filter((x) => x !== null);
89
+ console.log(formatShowOutput({
90
+ id: d.id,
91
+ title: d.title,
92
+ scope: d.visibility ?? 'PRIVATE',
93
+ projectName: d.project?.name ?? null,
94
+ createdAt: d.createdAt ?? '',
95
+ updatedAt: d.updatedAt ?? '',
96
+ mentionedTasks,
97
+ bodyMarkdown: body,
98
+ }));
99
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.docUnbind = docUnbind;
4
+ const config_1 = require("../lib/config");
5
+ const api_1 = require("../lib/api");
6
+ const resolve_doc_id_1 = require("../lib/resolve-doc-id");
7
+ async function docUnbind(docRef, task) {
8
+ if (!docRef || !task) {
9
+ console.error('Error: usage: lumo doc unbind <doc> <task>');
10
+ return 1;
11
+ }
12
+ const creds = (0, config_1.readCredentials)();
13
+ if (!creds) {
14
+ console.error('Error: not logged in. Run `lumo auth login` first.');
15
+ return 1;
16
+ }
17
+ const envUrl = process.env.LUMO_API_URL?.trim();
18
+ const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
19
+ const docId = await (0, resolve_doc_id_1.lookupDocId)(apiUrl, creds.token, docRef);
20
+ if (!docId) {
21
+ console.error(`Error: Document not found: ${docRef}`);
22
+ return 1;
23
+ }
24
+ // The DELETE route accepts either cuid or LUM-N as the [taskId] segment; the server resolves it.
25
+ const url = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/documents/${docId}/mentions/${encodeURIComponent(task)}`;
26
+ const res = await fetch(url, {
27
+ method: 'DELETE',
28
+ headers: { Authorization: `Bearer ${creds.token}` },
29
+ });
30
+ if (res.status === 409) {
31
+ const { error } = (await res.json());
32
+ console.error(`Error: ${error}`);
33
+ return 1;
34
+ }
35
+ if (!res.ok) {
36
+ const text = await res.text();
37
+ console.error(`Error: ${res.status} ${res.statusText}: ${text}`);
38
+ return 1;
39
+ }
40
+ const data = (await res.json());
41
+ if (data.removed === 0) {
42
+ console.log(`Already unbound ${docId} ↔ ${data.taskIdentifier}`);
43
+ }
44
+ else {
45
+ console.log(`Unbound ${docId} ↔ ${data.taskIdentifier}`);
46
+ }
47
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.docUnshare = docUnshare;
4
+ const config_1 = require("../lib/config");
5
+ const api_1 = require("../lib/api");
6
+ const resolve_doc_id_1 = require("../lib/resolve-doc-id");
7
+ const resolve_member_1 = require("../lib/resolve-member");
8
+ async function docUnshare(docRef, memberRef) {
9
+ if (!docRef || !memberRef) {
10
+ console.error('Error: usage: lumo doc unshare <doc> <member>');
11
+ return 1;
12
+ }
13
+ const creds = (0, config_1.readCredentials)();
14
+ if (!creds) {
15
+ console.error('Error: not logged in. Run `lumo auth login` first.');
16
+ return 1;
17
+ }
18
+ const envUrl = process.env.LUMO_API_URL?.trim();
19
+ const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
20
+ const docId = await (0, resolve_doc_id_1.lookupDocId)(apiUrl, creds.token, docRef);
21
+ if (!docId) {
22
+ console.error(`Error: Document not found: ${docRef}`);
23
+ return 1;
24
+ }
25
+ let members;
26
+ try {
27
+ members = await (0, resolve_member_1.fetchMembers)(apiUrl, creds.token);
28
+ }
29
+ catch (err) {
30
+ console.error(`Error: ${err.message}`);
31
+ return 1;
32
+ }
33
+ const result = (0, resolve_member_1.resolveMember)(members, memberRef, creds.email);
34
+ if (result.kind === 'not_found') {
35
+ console.error(`Error: no member matches "${result.query}".`);
36
+ return 1;
37
+ }
38
+ if (result.kind === 'ambiguous') {
39
+ const list = result.candidates.map(c => c.email ?? c.displayName).join(', ');
40
+ console.error(`Error: multiple members match "${result.query}": ${list}`);
41
+ return 1;
42
+ }
43
+ // The DELETE endpoint takes a share-row id, not a memberId. Fetch the
44
+ // current shares to find the row for this member.
45
+ const sharesUrl = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/documents/${docId}/shares`;
46
+ const listRes = await fetch(sharesUrl, {
47
+ headers: { Authorization: `Bearer ${creds.token}` },
48
+ });
49
+ if (!listRes.ok) {
50
+ const text = await listRes.text();
51
+ console.error(`Error: ${listRes.status} ${listRes.statusText}: ${text}`);
52
+ return 1;
53
+ }
54
+ const { shares } = (await listRes.json());
55
+ const row = shares.find(s => s.member.id === result.member.memberId);
56
+ if (!row) {
57
+ console.log(`Not shared with ${result.member.displayName}`);
58
+ return;
59
+ }
60
+ const delUrl = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/documents/${docId}/shares/${row.id}`;
61
+ const delRes = await fetch(delUrl, {
62
+ method: 'DELETE',
63
+ headers: { Authorization: `Bearer ${creds.token}` },
64
+ });
65
+ if (!delRes.ok) {
66
+ const text = await delRes.text();
67
+ console.error(`Error: ${delRes.status} ${delRes.statusText}: ${text}`);
68
+ return 1;
69
+ }
70
+ console.log(`Unshared ${docId} ↔ ${result.member.displayName}`);
71
+ }
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatUpdatedDocLine = formatUpdatedDocLine;
4
+ exports.docUpdate = docUpdate;
5
+ const config_1 = require("../lib/config");
6
+ const api_1 = require("../lib/api");
7
+ const doc_input_1 = require("../lib/doc-input");
8
+ const doc_create_1 = require("./doc-create");
9
+ const resolve_doc_id_1 = require("../lib/resolve-doc-id");
10
+ const tag_resolver_1 = require("../lib/tag-resolver");
11
+ function formatUpdatedDocLine(doc) {
12
+ const escaped = doc.title.replace(/"/g, '\\"');
13
+ const head = `Updated ${doc.id} "${escaped}" ${doc.url}`;
14
+ if (doc.tags && doc.tags.length > 0) {
15
+ return `${head}\nTags: ${doc.tags.join(', ')}`;
16
+ }
17
+ return head;
18
+ }
19
+ async function docUpdate(reference, opts) {
20
+ if (!reference) {
21
+ console.error('Error: missing <doc>. Usage: lumo doc update <doc> [flags]');
22
+ return 1;
23
+ }
24
+ const creds = (0, config_1.readCredentials)();
25
+ if (!creds) {
26
+ console.error('Error: not logged in. Run `lumo auth login` first.');
27
+ return 1;
28
+ }
29
+ const envUrl = process.env.LUMO_API_URL?.trim();
30
+ const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
31
+ // CLI-side mutex check: --tag/--tag-id (bulk replace) vs --add-tag/--add-tag-id/--remove-tag/--remove-tag-id (incremental)
32
+ const hasBulk = (opts.tag && opts.tag.length > 0) || (opts.tagId && opts.tagId.length > 0);
33
+ const hasIncremental = (opts.addTag && opts.addTag.length > 0) ||
34
+ (opts.addTagId && opts.addTagId.length > 0) ||
35
+ (opts.removeTag && opts.removeTag.length > 0) ||
36
+ (opts.removeTagId && opts.removeTagId.length > 0);
37
+ if (hasBulk && hasIncremental) {
38
+ console.error('Error: --tag/--tag-id are mutually exclusive with --add-tag/--add-tag-id/--remove-tag/--remove-tag-id');
39
+ return 1;
40
+ }
41
+ const id = await (0, resolve_doc_id_1.lookupDocId)(apiUrl, creds.token, reference);
42
+ if (!id) {
43
+ console.error(`Error: Document not found: ${reference}`);
44
+ return 1;
45
+ }
46
+ const body = {};
47
+ if (opts.title !== undefined) {
48
+ if (opts.title.trim().length === 0) {
49
+ console.error('Error: --title cannot be empty');
50
+ return 1;
51
+ }
52
+ body.title = opts.title.trim();
53
+ }
54
+ if (opts.scope !== undefined) {
55
+ const v = (0, doc_create_1.normalizeScope)(opts.scope);
56
+ if (!v) {
57
+ console.error(`Error: invalid scope "${opts.scope}". Allowed: personal, workspace`);
58
+ return 1;
59
+ }
60
+ body.visibility = v;
61
+ }
62
+ if (opts.project !== undefined) {
63
+ body.projectRef = opts.project === '' ? null : opts.project;
64
+ }
65
+ const content = await (0, doc_input_1.resolveDocContent)({
66
+ content: opts.content,
67
+ file: opts.file,
68
+ stdinIsTTY: Boolean(process.stdin.isTTY),
69
+ readStdin: doc_input_1.readStdinToString,
70
+ readFile: doc_input_1.readFileUtf8,
71
+ });
72
+ if (content.kind === 'error') {
73
+ console.error(`Error: ${content.message}`);
74
+ return 1;
75
+ }
76
+ if (content.kind === 'ok')
77
+ body.contentMarkdown = content.markdown;
78
+ // Resolve tag refs into ids
79
+ const deps = { apiUrl, token: creds.token };
80
+ let tagIds;
81
+ let addTagIds;
82
+ let removeTagIds;
83
+ try {
84
+ if (hasBulk) {
85
+ tagIds = await (0, tag_resolver_1.resolveTagRefs)({ names: opts.tag ?? [], ids: opts.tagId ?? [] }, deps);
86
+ }
87
+ else if (hasIncremental) {
88
+ if ((opts.addTag && opts.addTag.length > 0) ||
89
+ (opts.addTagId && opts.addTagId.length > 0)) {
90
+ addTagIds = await (0, tag_resolver_1.resolveTagRefs)({ names: opts.addTag ?? [], ids: opts.addTagId ?? [] }, deps);
91
+ }
92
+ if ((opts.removeTag && opts.removeTag.length > 0) ||
93
+ (opts.removeTagId && opts.removeTagId.length > 0)) {
94
+ // NOTE: --remove-tag <unknown-name> creates the Tag via find-or-create, then
95
+ // the detach is a no-op. Net effect: orphan Tag row. Acceptable v1 trade-off.
96
+ removeTagIds = await (0, tag_resolver_1.resolveTagRefs)({ names: opts.removeTag ?? [], ids: opts.removeTagId ?? [] }, deps);
97
+ }
98
+ }
99
+ }
100
+ catch (err) {
101
+ console.error(`Error: ${err.message}`);
102
+ return 1;
103
+ }
104
+ if (tagIds !== undefined)
105
+ body.tagIds = tagIds;
106
+ if (addTagIds !== undefined)
107
+ body.addTagIds = addTagIds;
108
+ if (removeTagIds !== undefined)
109
+ body.removeTagIds = removeTagIds;
110
+ if (Object.keys(body).length === 0) {
111
+ console.error('Error: no fields to update — provide at least one flag');
112
+ return 1;
113
+ }
114
+ const url = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/documents/${id}`;
115
+ const res = await fetch(url, {
116
+ method: 'PATCH',
117
+ headers: {
118
+ Authorization: `Bearer ${creds.token}`,
119
+ 'Content-Type': 'application/json',
120
+ },
121
+ body: JSON.stringify(body),
122
+ });
123
+ if (!res.ok) {
124
+ const text = await res.text();
125
+ console.error(`Error: ${res.status} ${res.statusText}: ${text}`);
126
+ return 1;
127
+ }
128
+ const { document } = (await res.json());
129
+ const docUrlStr = `${(0, api_1.trimTrailingSlash)(apiUrl)}/workspace/${creds.workspaceSlug ?? 'lumo'}/documents/${document.slug}`;
130
+ // Read tag names off the response (present when tags were changed)
131
+ const responseTagNames = tagIds !== undefined ||
132
+ addTagIds !== undefined ||
133
+ removeTagIds !== undefined
134
+ ? (document.docTags ?? []).map(t => t.name)
135
+ : undefined;
136
+ const lineDoc = {
137
+ id: document.id,
138
+ title: document.title,
139
+ url: docUrlStr,
140
+ };
141
+ if (responseTagNames !== undefined)
142
+ lineDoc.tags = responseTagNames;
143
+ console.log(formatUpdatedDocLine(lineDoc));
144
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hookCommand = hookCommand;
4
+ const hook_runner_1 = require("../lib/hook-runner");
5
+ const hook_log_1 = require("../lib/hook-log");
6
+ /**
7
+ * Dispatcher for the `lumo hook <path>` subcommands.
8
+ *
9
+ * Always exits 0. Failures are logged to `~/.lumo/hook.log` — never to
10
+ * stderr. The inner try/catch is a belt-and-suspenders guard so that even
11
+ * if `runHook` someday throws (it currently never does), Claude Code is
12
+ * not disrupted.
13
+ */
14
+ async function hookCommand(path) {
15
+ try {
16
+ await (0, hook_runner_1.runHook)(path);
17
+ }
18
+ catch (err) {
19
+ (0, hook_log_1.logHookError)(`[${path}] dispatcher`, err);
20
+ }
21
+ }
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildCreatePayload = buildCreatePayload;
4
+ exports.formatCreatedMilestoneLine = formatCreatedMilestoneLine;
5
+ exports.milestoneCreate = milestoneCreate;
6
+ const config_1 = require("../lib/config");
7
+ const api_1 = require("../lib/api");
8
+ const resolve_1 = require("../lib/resolve");
9
+ function buildCreatePayload(name, opts) {
10
+ const payload = { name };
11
+ if (opts.description !== undefined)
12
+ payload.description = opts.description;
13
+ if (opts.start !== undefined)
14
+ payload.startDate = opts.start;
15
+ if (opts.target !== undefined)
16
+ payload.targetDate = opts.target;
17
+ return payload;
18
+ }
19
+ function formatCreatedMilestoneLine(milestone) {
20
+ return `Created milestone "${milestone.name}" ${milestone.id}`;
21
+ }
22
+ async function milestoneCreate(name, opts) {
23
+ if (!name || !name.trim()) {
24
+ console.error('Error: milestone name is required');
25
+ return 1;
26
+ }
27
+ const creds = (0, config_1.readCredentials)();
28
+ if (!creds) {
29
+ console.error('Error: not logged in. Run `lumo auth login` first.');
30
+ return 1;
31
+ }
32
+ const envUrl = process.env.LUMO_API_URL?.trim();
33
+ const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
34
+ const base = (0, api_1.trimTrailingSlash)(apiUrl);
35
+ let projectId;
36
+ try {
37
+ projectId = await (0, resolve_1.resolveProjectId)(base, creds.token, opts.project);
38
+ }
39
+ catch (err) {
40
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
41
+ return 1;
42
+ }
43
+ const payload = buildCreatePayload(name.trim(), opts);
44
+ let res;
45
+ try {
46
+ res = await fetch(`${base}/api/projects/${projectId}/milestones`, {
47
+ method: 'POST',
48
+ headers: {
49
+ Authorization: `Bearer ${creds.token}`,
50
+ 'Content-Type': 'application/json',
51
+ },
52
+ body: JSON.stringify(payload),
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.ok) {
65
+ let serverMsg = null;
66
+ try {
67
+ const errBody = (await res.json());
68
+ if (typeof errBody.error === 'string')
69
+ serverMsg = errBody.error;
70
+ }
71
+ catch {
72
+ // Body wasn't JSON; fall through to status-only message
73
+ }
74
+ if (serverMsg) {
75
+ console.error(`Error: ${serverMsg}`);
76
+ }
77
+ else {
78
+ console.error(`Error: milestone create failed (HTTP ${res.status})`);
79
+ }
80
+ return 1;
81
+ }
82
+ const { milestone } = (await res.json());
83
+ process.stdout.write(formatCreatedMilestoneLine(milestone) + '\n');
84
+ }
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatDeleteRefusal = formatDeleteRefusal;
4
+ exports.milestoneDelete = milestoneDelete;
5
+ const config_1 = require("../lib/config");
6
+ const api_1 = require("../lib/api");
7
+ const resolve_1 = require("../lib/resolve");
8
+ function formatDeleteRefusal(name, taskCount) {
9
+ const head = `Refusing to delete milestone "${name}" without --yes.`;
10
+ const tail = `Re-run with --yes to confirm.`;
11
+ if (taskCount === 0)
12
+ return `${head}\n${tail}`;
13
+ return `${head}\n${taskCount} tasks under it will keep their data; only milestoneId is cleared.\n${tail}`;
14
+ }
15
+ function totalTasks(counts) {
16
+ return counts.TODO + counts.IN_PROGRESS + counts.IN_REVIEW + counts.DONE;
17
+ }
18
+ async function milestoneDelete(identifier, opts) {
19
+ const creds = (0, config_1.readCredentials)();
20
+ if (!creds) {
21
+ console.error('Error: not logged in. Run `lumo auth login` first.');
22
+ return 1;
23
+ }
24
+ const envUrl = process.env.LUMO_API_URL?.trim();
25
+ const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
26
+ const base = (0, api_1.trimTrailingSlash)(apiUrl);
27
+ let milestoneId;
28
+ let resolvedName;
29
+ try {
30
+ const resolved = await (0, resolve_1.resolveMilestoneId)(base, creds.token, identifier, opts.project);
31
+ milestoneId = resolved.id;
32
+ resolvedName = resolved.name;
33
+ }
34
+ catch (err) {
35
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
36
+ return 1;
37
+ }
38
+ // Always fetch the milestone first — we need name + taskCounts for the
39
+ // refusal message, and the same data confirms the milestone exists.
40
+ let showRes;
41
+ try {
42
+ showRes = await fetch(`${base}/api/milestones/${milestoneId}`, {
43
+ headers: { Authorization: `Bearer ${creds.token}` },
44
+ });
45
+ }
46
+ catch (err) {
47
+ const msg = err instanceof Error ? err.message : String(err);
48
+ console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
49
+ return 1;
50
+ }
51
+ if (showRes.status === 401) {
52
+ console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
53
+ return 1;
54
+ }
55
+ if (!showRes.ok) {
56
+ console.error(`Error: milestone fetch failed (HTTP ${showRes.status})`);
57
+ return 1;
58
+ }
59
+ const { milestone } = (await showRes.json());
60
+ const name = resolvedName || milestone.name;
61
+ const total = totalTasks(milestone.taskCounts);
62
+ if (!opts.yes) {
63
+ console.error(formatDeleteRefusal(name, total));
64
+ return 1;
65
+ }
66
+ let res;
67
+ try {
68
+ res = await fetch(`${base}/api/milestones/${milestoneId}`, {
69
+ method: 'DELETE',
70
+ headers: { Authorization: `Bearer ${creds.token}` },
71
+ });
72
+ }
73
+ catch (err) {
74
+ const msg = err instanceof Error ? err.message : String(err);
75
+ console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
76
+ return 1;
77
+ }
78
+ if (res.status === 401) {
79
+ console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
80
+ return 1;
81
+ }
82
+ if (!res.ok) {
83
+ let errMsg = `milestone delete failed (HTTP ${res.status})`;
84
+ try {
85
+ const errBody = (await res.json());
86
+ if (errBody.error)
87
+ errMsg = errBody.error;
88
+ }
89
+ catch {
90
+ // body wasn't JSON
91
+ }
92
+ console.error(`Error: ${errMsg}`);
93
+ return 1;
94
+ }
95
+ process.stdout.write(`Deleted milestone "${name}"\n`);
96
+ }