@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,492 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const fs = __importStar(require("fs"));
38
+ const os = __importStar(require("os"));
39
+ const path = __importStar(require("path"));
40
+ const commander_1 = require("commander");
41
+ const auth_login_1 = require("./commands/auth-login");
42
+ const auth_logout_1 = require("./commands/auth-logout");
43
+ const whoami_1 = require("./commands/whoami");
44
+ const hook_1 = require("./commands/hook");
45
+ const session_attach_1 = require("./commands/session-attach");
46
+ const session_detach_1 = require("./commands/session-detach");
47
+ const session_status_1 = require("./commands/session-status");
48
+ const task_context_1 = require("./commands/task-context");
49
+ const task_create_1 = require("./commands/task-create");
50
+ const task_update_1 = require("./commands/task-update");
51
+ const task_list_1 = require("./commands/task-list");
52
+ const task_show_1 = require("./commands/task-show");
53
+ const task_comment_1 = require("./commands/task-comment");
54
+ const project_list_1 = require("./commands/project-list");
55
+ const milestone_list_1 = require("./commands/milestone-list");
56
+ const milestone_create_1 = require("./commands/milestone-create");
57
+ const milestone_show_1 = require("./commands/milestone-show");
58
+ const milestone_update_1 = require("./commands/milestone-update");
59
+ const milestone_delete_1 = require("./commands/milestone-delete");
60
+ const sprint_create_1 = require("./commands/sprint-create");
61
+ const sprint_list_1 = require("./commands/sprint-list");
62
+ const sprint_show_1 = require("./commands/sprint-show");
63
+ const sprint_update_1 = require("./commands/sprint-update");
64
+ const sprint_delete_1 = require("./commands/sprint-delete");
65
+ const sprint_start_1 = require("./commands/sprint-start");
66
+ const sprint_close_1 = require("./commands/sprint-close");
67
+ const sprint_summary_1 = require("./commands/sprint-summary");
68
+ const sprint_add_1 = require("./commands/sprint-add");
69
+ const sprint_remove_1 = require("./commands/sprint-remove");
70
+ const doc_create_1 = require("./commands/doc-create");
71
+ const doc_update_1 = require("./commands/doc-update");
72
+ const doc_show_1 = require("./commands/doc-show");
73
+ const doc_list_1 = require("./commands/doc-list");
74
+ const doc_delete_1 = require("./commands/doc-delete");
75
+ const doc_bind_1 = require("./commands/doc-bind");
76
+ const doc_unbind_1 = require("./commands/doc-unbind");
77
+ const doc_share_1 = require("./commands/doc-share");
78
+ const doc_unshare_1 = require("./commands/doc-unshare");
79
+ const doc_share_list_1 = require("./commands/doc-share-list");
80
+ const doc_move_1 = require("./commands/doc-move");
81
+ // Resolve package.json relative to __dirname so this works regardless of how
82
+ // deep the compiled output ends up (flat dist/ or nested dist/cli/src/).
83
+ const pkg = require(path.resolve(__dirname, '../../..', 'package.json'));
84
+ (() => {
85
+ const dir = process.env.LUMO_CONFIG_DIR || path.join(os.homedir(), '.lumo');
86
+ try {
87
+ fs.rmSync(path.join(dir, 'current-task.json'), { force: true });
88
+ }
89
+ catch { }
90
+ try {
91
+ fs.rmSync(path.join(dir, 'sessions'), { recursive: true, force: true });
92
+ }
93
+ catch { }
94
+ })();
95
+ const collect = (val, acc) => [...acc, val];
96
+ function wrap(fn) {
97
+ return async (...args) => {
98
+ try {
99
+ const code = await fn(...args);
100
+ if (typeof code === 'number' && code !== 0)
101
+ process.exit(code);
102
+ }
103
+ catch (err) {
104
+ const msg = err instanceof Error ? err.message : String(err);
105
+ console.error(`Error: ${msg}`);
106
+ process.exit(1);
107
+ }
108
+ };
109
+ }
110
+ const program = new commander_1.Command()
111
+ .name('lumo')
112
+ .description('Lumo CLI — manage tasks and sessions from the terminal')
113
+ .version(pkg.version);
114
+ const auth = program.command('auth').description('Manage Lumo authentication');
115
+ auth
116
+ .command('login')
117
+ .description('Log in to Lumo by pasting an API key')
118
+ .action(wrap(auth_login_1.authLogin));
119
+ auth
120
+ .command('logout')
121
+ .description('Log out and remove local credentials')
122
+ .action(wrap(auth_logout_1.authLogout));
123
+ program
124
+ .command('whoami')
125
+ .description('Show the currently logged-in identity')
126
+ .action(wrap(whoami_1.whoami));
127
+ const session = program
128
+ .command('session')
129
+ .description('Manage per-terminal coding-session context');
130
+ session
131
+ .command('attach <identifier>')
132
+ .description('Attach the currently-running Claude Code session (CLAUDE_CODE_SESSION_ID) to a task. Sets Session.taskId server-side and re-tags untagged hook events.')
133
+ .action(wrap(identifier => (0, session_attach_1.sessionAttach)(identifier)));
134
+ session
135
+ .command('status')
136
+ .description('Show the task currently bound to this Claude Code session (or "no task" if none).')
137
+ .action(wrap(() => (0, session_status_1.sessionStatus)()));
138
+ session
139
+ .command('detach')
140
+ .description('Clear the task binding on the current Claude Code session. Past hook events keep their taskId; only future events become untagged.')
141
+ .action(wrap(() => (0, session_detach_1.sessionDetach)()));
142
+ const task = program
143
+ .command('task')
144
+ .description('Inspect tasks from the terminal');
145
+ task
146
+ .command('context <identifier>')
147
+ .description('Print a task + prior session summaries as markdown, for pasting into a coding agent on startup.')
148
+ .action(wrap(identifier => (0, task_context_1.taskContext)(identifier)));
149
+ task
150
+ .command('list')
151
+ .description('List tasks assigned to you. Filter with --status, --project, --milestone, --limit.')
152
+ .option('-s, --status <value>', 'Filter by status: todo | in_progress | in_review | done')
153
+ .option('-p, --project <ref>', 'Filter by project name (case-insensitive)')
154
+ .option('-n, --limit <count>', 'Show only the first N tasks')
155
+ .option('-m, --milestone <ref>', 'Filter by milestone name or UUID (scopes to my tasks under that milestone)')
156
+ .action(wrap(options => (0, task_list_1.taskList)(options)));
157
+ task
158
+ .command('show <identifier>')
159
+ .description('Show a single task: title, status, priority, assignee, project, URL, description.')
160
+ .action(wrap(identifier => (0, task_show_1.taskShow)(identifier)));
161
+ task
162
+ .command('comment <identifier> <body>')
163
+ .description('Add a comment to a task. Body is plain text — quote it to pass spaces or newlines.')
164
+ .action(wrap((identifier, body) => (0, task_comment_1.taskComment)(identifier, body)));
165
+ task
166
+ .command('create <title>')
167
+ .description('Create a task. --project required when workspace has >1 project; defaults: priority=low, assignee=me.')
168
+ .option('-d, --description <text>', 'Task description')
169
+ .option('-p, --priority <level>', 'Priority: low | medium | high | urgent (default: low)')
170
+ .option('-a, --assignee <ref>', 'Assignee email, name, or "me" (default: me)')
171
+ .option('--project <ref>', 'Project name or slug')
172
+ .option('--milestone <ref>', 'Milestone name (case-insensitive)')
173
+ .option('--tag <name>', 'Attach tag by name (repeatable)', collect, [])
174
+ .option('--tag-id <cuid>', 'Attach tag by id (repeatable)', collect, [])
175
+ .option('--sprint <ref>', 'Sprint number or UUID to add the task to after creation')
176
+ .action(wrap((title, options) => (0, task_create_1.taskCreate)(title, options)));
177
+ const projectCmd = program
178
+ .command('project')
179
+ .description('Inspect projects from the terminal');
180
+ projectCmd
181
+ .command('list')
182
+ .description('List projects in the current workspace. Slug column matches `task create --project <ref>`.')
183
+ .action(wrap(() => (0, project_list_1.projectList)()));
184
+ const milestoneCmd = program
185
+ .command('milestone')
186
+ .description('Inspect milestones from the terminal');
187
+ milestoneCmd
188
+ .command('list')
189
+ .description('List milestones for a project. --project required when workspace has >1 project.')
190
+ .option('--project <ref>', 'Project name or slug')
191
+ .action(wrap(options => (0, milestone_list_1.milestoneList)(options)));
192
+ milestoneCmd
193
+ .command('create <name>')
194
+ .description('Create a milestone. --project required when workspace has >1 project.')
195
+ .option('--project <ref>', 'Project name or slug')
196
+ .option('-d, --description <text>', 'Milestone description')
197
+ .option('--start <date>', 'Start date (YYYY-MM-DD)')
198
+ .option('--target <date>', 'Target date (YYYY-MM-DD)')
199
+ .action(wrap((name, options) => (0, milestone_create_1.milestoneCreate)(name, options)));
200
+ milestoneCmd
201
+ .command('show <identifier>')
202
+ .description('Show a milestone: status, dates, description, task counts, and tasks under it. Identifier accepts UUID or name.')
203
+ .option('--project <ref>', 'Project name or slug (when identifier is a name)')
204
+ .action(wrap((identifier, options) => (0, milestone_show_1.milestoneShow)(identifier, options)));
205
+ milestoneCmd
206
+ .command('update <identifier>')
207
+ .description('Update a milestone. Provide at least one of --name, --description, --status, --start, --target. Use "" to clear nullable fields.')
208
+ .option('--project <ref>', 'Project name or slug (when identifier is a name)')
209
+ .option('-n, --name <text>', 'New name')
210
+ .option('-d, --description <text>', 'New description (empty string to clear)')
211
+ .option('-s, --status <value>', 'New status: planned | active | completed | cancelled')
212
+ .option('--start <date>', 'Start date YYYY-MM-DD (empty string to clear)')
213
+ .option('--target <date>', 'Target date YYYY-MM-DD (empty string to clear)')
214
+ .action(wrap((identifier, options) => (0, milestone_update_1.milestoneUpdate)(identifier, options)));
215
+ milestoneCmd
216
+ .command('delete <identifier>')
217
+ .description('Delete a milestone. Tasks under it keep their data (milestoneId is cleared). Requires --yes.')
218
+ .option('--project <ref>', 'Project name or slug (when identifier is a name)')
219
+ .option('--yes', 'Required: confirm deletion without TTY prompt')
220
+ .action(wrap((identifier, options) => (0, milestone_delete_1.milestoneDelete)(identifier, options)));
221
+ const sprintCmd = program
222
+ .command('sprint')
223
+ .description('Inspect sprints from the terminal');
224
+ sprintCmd
225
+ .command('list')
226
+ .description('List sprints for a team. --team required when workspace has >1 team. Sorted newest first.')
227
+ .option('--team <ref>', 'Team name or UUID')
228
+ .option('-s, --status <value>', 'Filter by status: draft | active | closed (case-insensitive)')
229
+ .option('-n, --limit <count>', 'Show only the first N sprints')
230
+ .action(wrap(options => (0, sprint_list_1.sprintList)(options)));
231
+ sprintCmd
232
+ .command('show <identifier>')
233
+ .description('Show a sprint: status, dates, progress, and tasks. Identifier accepts a sprint number or UUID.')
234
+ .option('--team <ref>', 'Team name or UUID (when workspace has >1 team)')
235
+ .action(wrap((identifier, options) => (0, sprint_show_1.sprintShow)(identifier, options)));
236
+ sprintCmd
237
+ .command('create')
238
+ .description('Create a draft sprint. --team required when workspace has >1 team. --start and --end (YYYY-MM-DD) required.')
239
+ .option('--team <ref>', 'Team name or UUID')
240
+ .option('-n, --name <text>', 'Sprint name (server fills default when omitted)')
241
+ .option('--start <date>', 'Start date (YYYY-MM-DD)')
242
+ .option('--end <date>', 'End date (YYYY-MM-DD)')
243
+ .action(wrap(options => (0, sprint_create_1.sprintCreate)(options)));
244
+ sprintCmd
245
+ .command('update <identifier>')
246
+ .description('Update a sprint. Provide at least one of --name, --start, --end. Identifier accepts a sprint number or UUID. Empty strings are rejected — these fields cannot be cleared.')
247
+ .option('--team <ref>', 'Team name or UUID (when workspace has >1 team)')
248
+ .option('-n, --name <text>', 'New sprint name')
249
+ .option('--start <date>', 'New start date (YYYY-MM-DD)')
250
+ .option('--end <date>', 'New end date (YYYY-MM-DD)')
251
+ .action(wrap((identifier, options) => (0, sprint_update_1.sprintUpdate)(identifier, options)));
252
+ sprintCmd
253
+ .command('delete <identifier>')
254
+ .description('Delete a DRAFT sprint. Requires --yes. Server rejects ACTIVE/CLOSED sprints.')
255
+ .option('--team <ref>', 'Team name or UUID (informational)')
256
+ .option('--yes', 'Confirm deletion')
257
+ .action(wrap((identifier, options) => (0, sprint_delete_1.sprintDelete)(identifier, options)));
258
+ sprintCmd
259
+ .command('start <identifier>')
260
+ .description('Transition a DRAFT sprint to ACTIVE.')
261
+ .option('--team <ref>', 'Team name or UUID (informational)')
262
+ .action(wrap((identifier, options) => (0, sprint_start_1.sprintStart)(identifier, options)));
263
+ sprintCmd
264
+ .command('close <identifier>')
265
+ .description('Close an ACTIVE sprint. With no unfinished tasks, closes immediately. Pass --move-all --yes to move unfinished tasks to the next sprint, or --backlog-all --yes to return them to the backlog.')
266
+ .option('--team <ref>', 'Team name or UUID (informational)')
267
+ .option('--move-all', 'Move all unfinished tasks to the next sprint')
268
+ .option('--backlog-all', 'Return all unfinished tasks to the backlog')
269
+ .option('--yes', 'Required when --move-all or --backlog-all is set')
270
+ .action(wrap((identifier, options) => (0, sprint_close_1.sprintClose)(identifier, options)));
271
+ sprintCmd
272
+ .command('summary <identifier>')
273
+ .description('Show the AI-generated summary for a sprint. Identifier accepts a sprint number or UUID. Prints "(no summary generated yet)" when none exists. Use --retry to queue regeneration before fetching.')
274
+ .option('--team <ref>', 'Team name or UUID (when workspace has >1 team)')
275
+ .option('--retry', 'Trigger summary regeneration before fetching')
276
+ .action(wrap((identifier, options) => (0, sprint_summary_1.sprintSummary)(identifier, options)));
277
+ sprintCmd
278
+ .command('add <identifier> <task>')
279
+ .description('Add a task to a sprint. <task> accepts LUM-N or UUID.')
280
+ .option('--team <ref>', 'Team name or UUID (informational)')
281
+ .action(wrap((identifier, task, options) => (0, sprint_add_1.sprintAdd)(identifier, task, options)));
282
+ sprintCmd
283
+ .command('remove <identifier> <task>')
284
+ .description('Remove a task from a sprint. <task> accepts LUM-N or UUID.')
285
+ .option('--team <ref>', 'Team name or UUID (informational)')
286
+ .action(wrap((identifier, task, options) => (0, sprint_remove_1.sprintRemove)(identifier, task, options)));
287
+ const doc = program.command('doc').description('Manage Lumo documents');
288
+ doc
289
+ .command('create [title]')
290
+ .description('Create a new document. Body comes from --content, --file, or piped stdin (pick one). --scope defaults to personal. Use --task LUM-N to bind the new doc to a task, --parent to file it under another doc.')
291
+ .option('--content <text>', 'Inline markdown content')
292
+ .option('--file <path>', 'Read markdown content from file')
293
+ .option('--scope <scope>', 'personal | workspace (default: personal)')
294
+ .option('--project <ref>', 'Project name or slug to file under')
295
+ .option('--task <LUM-N>', 'Bind to this task after creation')
296
+ .option('--parent <doc>', 'File under this parent doc (cuid or title)')
297
+ .option('--tag <name>', 'Attach tag by name (repeatable)', collect, [])
298
+ .option('--tag-id <cuid>', 'Attach tag by id (repeatable)', collect, [])
299
+ .action(wrap((title, opts) => (0, doc_create_1.docCreate)(title, opts)));
300
+ doc
301
+ .command('update <doc>')
302
+ .description('Update an existing document. <doc> accepts a cuid or a case-insensitive title (ambiguous titles fail with candidates). Replacement body comes from --content, --file, or piped stdin (pick one). --tag/--tag-id (bulk replace) cannot be combined with --add-tag/--add-tag-id/--remove-tag/--remove-tag-id.')
303
+ .option('--title <text>', 'New title')
304
+ .option('--content <text>', 'Replace content (inline)')
305
+ .option('--file <path>', 'Replace content from file')
306
+ .option('--scope <scope>', 'personal | workspace')
307
+ .option('--project <ref>', 'Project name/slug; pass "" to clear')
308
+ .option('--tag <name>', 'Set tags by name (bulk replace, repeatable)', collect, [])
309
+ .option('--tag-id <cuid>', 'Set tags by id (bulk replace, repeatable)', collect, [])
310
+ .option('--add-tag <name>', 'Add tag by name (repeatable)', collect, [])
311
+ .option('--add-tag-id <cuid>', 'Add tag by id (repeatable)', collect, [])
312
+ .option('--remove-tag <name>', 'Remove tag by name (repeatable). Unknown names are find-or-create, so prefer --remove-tag-id to avoid orphan rows.', collect, [])
313
+ .option('--remove-tag-id <cuid>', 'Remove tag by id (repeatable). Unknown ids are a no-op.', collect, [])
314
+ .action(wrap((reference, opts) => (0, doc_update_1.docUpdate)(reference, opts)));
315
+ doc
316
+ .command('show <doc>')
317
+ .description('Show document header and body (doc = title or cuid)')
318
+ .action(wrap(reference => (0, doc_show_1.docShow)(reference)));
319
+ doc
320
+ .command('list')
321
+ .description('List documents visible to the current user')
322
+ .option('--scope <scope>', 'personal | workspace | all (default: all)')
323
+ .option('--project <ref>', 'Filter by project name/slug')
324
+ .option('--task <LUM-N>', 'Only docs bound to this task')
325
+ .option('--limit <n>', 'Cap output to first N rows')
326
+ .option('--tree', 'Render as an indented tree')
327
+ .action(wrap(opts => (0, doc_list_1.docList)(opts)));
328
+ doc
329
+ .command('move <doc>')
330
+ .description('Move a doc under a different parent or to top level. Exactly one of --parent or --root is required. The moved doc lands at the end of its new sibling list; reordering among siblings is web-UI only.')
331
+ .option('--parent <doc>', 'New parent doc (cuid or title)')
332
+ .option('--root', 'Move to top level (no parent)')
333
+ .action(wrap((reference, opts) => (0, doc_move_1.docMove)(reference, opts)));
334
+ doc
335
+ .command('delete <doc>')
336
+ .description('Delete a document (requires --yes)')
337
+ .option('--yes', 'Confirm deletion')
338
+ .action(wrap((reference, opts) => (0, doc_delete_1.docDelete)(reference, opts)));
339
+ doc
340
+ .command('bind <doc> <task>')
341
+ .description('Bind a document to a task by adding an EXPLICIT mention row. Idempotent. If a CONTENT-derived mention already exists for the same pair, it is upgraded to EXPLICIT so `doc unbind` can later remove it.')
342
+ .action(wrap((docRef, task) => (0, doc_bind_1.docBind)(docRef, task)));
343
+ doc
344
+ .command('unbind <doc> <task>')
345
+ .description('Unbind a document from a task. Removes EXPLICIT mentions only — purely CONTENT-derived mentions (from @LUM-N in the body) cannot be removed via CLI and fail with 409; edit the doc body in the web UI instead.')
346
+ .action(wrap((docRef, task) => (0, doc_unbind_1.docUnbind)(docRef, task)));
347
+ doc
348
+ .command('share <doc> <member>')
349
+ .description('Share a document with a workspace member. PRIVATE docs are auto-promoted to SHARED. <member> accepts "me", an email, or a display name.')
350
+ .option('--role <role>', 'viewer | editor | manager (default: viewer)')
351
+ .action(wrap((docRef, member, opts) => (0, doc_share_1.docShare)(docRef, member, opts)));
352
+ doc
353
+ .command('unshare <doc> <member>')
354
+ .description('Remove a member from a document\'s share list. Idempotent — prints "Not shared with …" if no row exists.')
355
+ .action(wrap((docRef, member) => (0, doc_unshare_1.docUnshare)(docRef, member)));
356
+ doc
357
+ .command('share-list <doc>')
358
+ .description('List the members a document is shared with, as fixed-width rows: <displayName> <ROLE>.')
359
+ .action(wrap(docRef => (0, doc_share_list_1.docShareList)(docRef)));
360
+ task
361
+ .command('update <identifier>')
362
+ .description('Update a task. Provide at least one of --title, --description, --status, --priority, --assignee, --milestone, --sprint, or tag flags. Use "" to clear description, assignee, milestone, or sprint binding. --tag/--tag-id (bulk replace) cannot be combined with --add-tag/--add-tag-id/--remove-tag/--remove-tag-id.')
363
+ .option('-t, --title <text>', 'New title')
364
+ .option('-d, --description <text>', 'New description (empty string to clear)')
365
+ .option('-s, --status <value>', 'New status: todo | in_progress | in_review | done')
366
+ .option('-p, --priority <level>', 'New priority: low | medium | high | urgent')
367
+ .option('-a, --assignee <ref>', 'New assignee: email, name, or "me" (empty string to clear)')
368
+ .option('--milestone <ref>', 'Milestone name (case-insensitive); empty string to unbind')
369
+ .option('--sprint <ref>', 'Sprint number or UUID to bind the task to; empty string to unbind from current sprint')
370
+ .option('--tag <name>', 'Set tags by name (bulk replace, repeatable)', collect, [])
371
+ .option('--tag-id <cuid>', 'Set tags by id (bulk replace, repeatable)', collect, [])
372
+ .option('--add-tag <name>', 'Add tag by name (repeatable)', collect, [])
373
+ .option('--add-tag-id <cuid>', 'Add tag by id (repeatable)', collect, [])
374
+ .option('--remove-tag <name>', 'Remove tag by name (repeatable). Unknown names are find-or-create, so prefer --remove-tag-id to avoid orphan rows.', collect, [])
375
+ .option('--remove-tag-id <cuid>', 'Remove tag by id (repeatable). Unknown ids are a no-op.', collect, [])
376
+ .action(wrap((identifier, options) => (0, task_update_1.taskUpdate)(identifier, options)));
377
+ // Claude Code invokes `lumo hook <type>` per tool call, so these handlers
378
+ // must never crash the caller. Two invariants keep us safe:
379
+ // 1. `hookCommand` is stderr-silent and never throws (it catches internally
380
+ // and routes failures to ~/.lumo/hook.log). That makes `wrap()` a
381
+ // defensive no-op here — its process.exit(1) branch is unreachable.
382
+ // 2. `./commands/hook` and its transitive imports (`hook-runner`,
383
+ // `hook-log`, `config`, `api`) must remain side-effect-free at module
384
+ // scope, so a broken hook never fails at import time either.
385
+ const hook = program
386
+ .command('hook')
387
+ .description('Claude Code hook ingestion (invoked by settings.json, not humans)');
388
+ hook
389
+ .command('pre-tool-use')
390
+ .description('Forward a PreToolUse hook event to Lumo (reads JSON from stdin)')
391
+ .action(wrap(() => (0, hook_1.hookCommand)('pre-tool-use')));
392
+ hook
393
+ .command('post-tool-use')
394
+ .description('Forward a PostToolUse hook event to Lumo (reads JSON from stdin)')
395
+ .action(wrap(() => (0, hook_1.hookCommand)('post-tool-use')));
396
+ hook
397
+ .command('post-tool-use-failure')
398
+ .description('Forward a PostToolUseFailure hook event to Lumo (reads JSON from stdin)')
399
+ .action(wrap(() => (0, hook_1.hookCommand)('post-tool-use-failure')));
400
+ hook
401
+ .command('user-prompt-submit')
402
+ .description('Forward a UserPromptSubmit hook event to Lumo (reads JSON from stdin)')
403
+ .action(wrap(() => (0, hook_1.hookCommand)('user-prompt-submit')));
404
+ hook
405
+ .command('stop')
406
+ .description('Forward a Stop hook event to Lumo (reads JSON from stdin)')
407
+ .action(wrap(() => (0, hook_1.hookCommand)('stop')));
408
+ hook
409
+ .command('stop-failure')
410
+ .description('Forward a StopFailure hook event to Lumo (reads JSON from stdin)')
411
+ .action(wrap(() => (0, hook_1.hookCommand)('stop-failure')));
412
+ hook
413
+ .command('permission-request')
414
+ .description('Forward a PermissionRequest hook event to Lumo (reads JSON from stdin)')
415
+ .action(wrap(() => (0, hook_1.hookCommand)('permission-request')));
416
+ hook
417
+ .command('permission-denied')
418
+ .description('Forward a PermissionDenied hook event to Lumo (reads JSON from stdin)')
419
+ .action(wrap(() => (0, hook_1.hookCommand)('permission-denied')));
420
+ hook
421
+ .command('session-start')
422
+ .description('Forward a SessionStart hook event to Lumo (reads JSON from stdin)')
423
+ .action(wrap(() => (0, hook_1.hookCommand)('session-start')));
424
+ hook
425
+ .command('session-end')
426
+ .description('Forward a SessionEnd hook event to Lumo (reads JSON from stdin)')
427
+ .action(wrap(() => (0, hook_1.hookCommand)('session-end')));
428
+ hook
429
+ .command('subagent-start')
430
+ .description('Forward a SubagentStart hook event to Lumo (reads JSON from stdin)')
431
+ .action(wrap(() => (0, hook_1.hookCommand)('subagent-start')));
432
+ hook
433
+ .command('subagent-stop')
434
+ .description('Forward a SubagentStop hook event to Lumo (reads JSON from stdin)')
435
+ .action(wrap(() => (0, hook_1.hookCommand)('subagent-stop')));
436
+ hook
437
+ .command('worktree-create')
438
+ .description('Forward a WorktreeCreate hook event to Lumo (reads JSON from stdin)')
439
+ .action(wrap(() => (0, hook_1.hookCommand)('worktree-create')));
440
+ hook
441
+ .command('worktree-remove')
442
+ .description('Forward a WorktreeRemove hook event to Lumo (reads JSON from stdin)')
443
+ .action(wrap(() => (0, hook_1.hookCommand)('worktree-remove')));
444
+ hook
445
+ .command('file-changed')
446
+ .description('Forward a FileChanged hook event to Lumo (reads JSON from stdin)')
447
+ .action(wrap(() => (0, hook_1.hookCommand)('file-changed')));
448
+ hook
449
+ .command('config-change')
450
+ .description('Forward a ConfigChange hook event to Lumo (reads JSON from stdin)')
451
+ .action(wrap(() => (0, hook_1.hookCommand)('config-change')));
452
+ hook
453
+ .command('task-created')
454
+ .description('Forward a TaskCreated hook event to Lumo (reads JSON from stdin)')
455
+ .action(wrap(() => (0, hook_1.hookCommand)('task-created')));
456
+ hook
457
+ .command('task-completed')
458
+ .description('Forward a TaskCompleted hook event to Lumo (reads JSON from stdin)')
459
+ .action(wrap(() => (0, hook_1.hookCommand)('task-completed')));
460
+ hook
461
+ .command('post-tool-batch')
462
+ .description('Forward a PostToolBatch hook event to Lumo (reads JSON from stdin)')
463
+ .action(wrap(() => (0, hook_1.hookCommand)('post-tool-batch')));
464
+ hook
465
+ .command('user-prompt-expansion')
466
+ .description('Forward a UserPromptExpansion hook event to Lumo (reads JSON from stdin)')
467
+ .action(wrap(() => (0, hook_1.hookCommand)('user-prompt-expansion')));
468
+ hook
469
+ .command('notification')
470
+ .description('Forward a Notification hook event to Lumo (reads JSON from stdin)')
471
+ .action(wrap(() => (0, hook_1.hookCommand)('notification')));
472
+ hook
473
+ .command('elicitation')
474
+ .description('Forward an Elicitation hook event to Lumo (reads JSON from stdin)')
475
+ .action(wrap(() => (0, hook_1.hookCommand)('elicitation')));
476
+ hook
477
+ .command('elicitation-result')
478
+ .description('Forward an ElicitationResult hook event to Lumo (reads JSON from stdin)')
479
+ .action(wrap(() => (0, hook_1.hookCommand)('elicitation-result')));
480
+ hook
481
+ .command('cwd-changed')
482
+ .description('Forward a CwdChanged hook event to Lumo (reads JSON from stdin)')
483
+ .action(wrap(() => (0, hook_1.hookCommand)('cwd-changed')));
484
+ hook
485
+ .command('instructions-loaded')
486
+ .description('Forward an InstructionsLoaded hook event to Lumo (reads JSON from stdin)')
487
+ .action(wrap(() => (0, hook_1.hookCommand)('instructions-loaded')));
488
+ program.parseAsync(process.argv).catch(err => {
489
+ const msg = err instanceof Error ? err.message : String(err);
490
+ console.error(`Error: ${msg}`);
491
+ process.exit(1);
492
+ });
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveApiUrl = resolveApiUrl;
4
+ exports.trimTrailingSlash = trimTrailingSlash;
5
+ exports.verifyToken = verifyToken;
6
+ const DEFAULT_API_URL = 'https://www.uselumo.ai';
7
+ function resolveApiUrl() {
8
+ const url = process.env.LUMO_API_URL?.trim();
9
+ return url && url.length > 0 ? url : DEFAULT_API_URL;
10
+ }
11
+ function trimTrailingSlash(url) {
12
+ return url.replace(/\/+$/, '');
13
+ }
14
+ async function verifyToken(apiUrl, token) {
15
+ let res;
16
+ try {
17
+ res = await fetch(`${trimTrailingSlash(apiUrl)}/api/auth/verify`, {
18
+ method: 'GET',
19
+ headers: { Authorization: `Bearer ${token}` },
20
+ });
21
+ }
22
+ catch (err) {
23
+ const msg = err instanceof Error ? err.message : String(err);
24
+ throw new Error(`Could not reach Lumo API at ${apiUrl} (${msg})`);
25
+ }
26
+ if (res.status === 401) {
27
+ throw new Error('Invalid or revoked API key');
28
+ }
29
+ if (!res.ok) {
30
+ throw new Error(`Verify failed: HTTP ${res.status}`);
31
+ }
32
+ return (await res.json());
33
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.openBrowser = openBrowser;
4
+ const child_process_1 = require("child_process");
5
+ /**
6
+ * Open a URL in the default browser (best-effort, non-blocking).
7
+ * Silent on failure — the user can always copy the URL manually.
8
+ */
9
+ function openBrowser(url) {
10
+ const platform = process.platform;
11
+ let cmd;
12
+ let args;
13
+ if (platform === 'darwin') {
14
+ cmd = 'open';
15
+ args = [url];
16
+ }
17
+ else if (platform === 'win32') {
18
+ cmd = 'cmd';
19
+ args = ['/c', 'start', '""', url];
20
+ }
21
+ else {
22
+ cmd = 'xdg-open';
23
+ args = [url];
24
+ }
25
+ try {
26
+ const child = (0, child_process_1.spawn)(cmd, args, { stdio: 'ignore', detached: true });
27
+ child.on('error', () => { });
28
+ child.unref();
29
+ }
30
+ catch {
31
+ // Silent
32
+ }
33
+ }
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.credentialsPath = credentialsPath;
37
+ exports.readCredentials = readCredentials;
38
+ exports.writeCredentials = writeCredentials;
39
+ exports.deleteCredentials = deleteCredentials;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const os = __importStar(require("os"));
43
+ function configDir() {
44
+ return path.join(os.homedir(), '.lumo');
45
+ }
46
+ function credentialsPath() {
47
+ return path.join(configDir(), 'credentials.json');
48
+ }
49
+ function readCredentials() {
50
+ const p = credentialsPath();
51
+ if (!fs.existsSync(p))
52
+ return null;
53
+ try {
54
+ const data = JSON.parse(fs.readFileSync(p, 'utf8'));
55
+ if (typeof data?.token !== 'string' || typeof data?.apiUrl !== 'string') {
56
+ return null;
57
+ }
58
+ return data;
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ function writeCredentials(creds) {
65
+ const dir = configDir();
66
+ if (!fs.existsSync(dir)) {
67
+ fs.mkdirSync(dir, { mode: 0o700, recursive: true });
68
+ }
69
+ else {
70
+ try {
71
+ fs.chmodSync(dir, 0o700);
72
+ }
73
+ catch {
74
+ // Best-effort perms tightening; continue if filesystem doesn't support chmod
75
+ }
76
+ }
77
+ const p = credentialsPath();
78
+ fs.writeFileSync(p, JSON.stringify(creds, null, 2), { mode: 0o600 });
79
+ try {
80
+ fs.chmodSync(p, 0o600);
81
+ }
82
+ catch {
83
+ // Best-effort perms tightening
84
+ }
85
+ }
86
+ function deleteCredentials() {
87
+ const p = credentialsPath();
88
+ if (!fs.existsSync(p))
89
+ return false;
90
+ fs.unlinkSync(p);
91
+ return true;
92
+ }