@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
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # Lumo CLI
2
+
3
+ Manage [Lumo](https://www.uselumo.ai) tasks, sessions, sprints, and documents from the terminal. Designed to pair with Claude Code so AI agents can bind to a task, load context, and update state without leaving the shell.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @lumoai/cli
9
+ ```
10
+
11
+ Verify:
12
+
13
+ ```bash
14
+ lumo --version
15
+ ```
16
+
17
+ ## Authentication
18
+
19
+ ```bash
20
+ lumo auth login
21
+ ```
22
+
23
+ The CLI opens your browser to create an API key, then reads it from stdin. Keys are stored in `~/.lumo/credentials.json`.
24
+
25
+ Check who you're logged in as:
26
+
27
+ ```bash
28
+ lumo whoami
29
+ # Logged in as you@example.com
30
+ # Workspace: Acme (acme)
31
+ # Key: My laptop (lum_abc1...)
32
+ # API: https://www.uselumo.ai
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ```bash
38
+ # What am I working on?
39
+ lumo task list
40
+
41
+ # Create a task
42
+ lumo task create "Fix Slack OAuth redirect" --priority high
43
+
44
+ # Mark it in progress
45
+ lumo task update LUM-42 --status in_progress
46
+
47
+ # Bind the current Claude Code session to the task
48
+ lumo session attach LUM-42
49
+
50
+ # Load full background (description, prior session summaries, memory)
51
+ lumo task context LUM-42
52
+
53
+ # When you're done
54
+ lumo task update LUM-42 --status done
55
+ ```
56
+
57
+ ## Commands
58
+
59
+ | Group | Highlights |
60
+ | --------------- | ------------------------------------------------------------- |
61
+ | `auth` | `login`, `logout` |
62
+ | `whoami` | Show current identity + workspace |
63
+ | `task` | `create`, `update`, `list`, `show`, `comment`, `context` |
64
+ | `session` | `attach`, `status`, `detach` (binds Claude Code sessions) |
65
+ | `project` | `list` |
66
+ | `milestone` | `create`, `update`, `list`, `show`, `delete` |
67
+ | `sprint` | `create`, `start`, `close`, `list`, `show`, `add`, `remove`, `summary` |
68
+ | `doc` | `create`, `update`, `list`, `show`, `move`, `bind`, `share` |
69
+
70
+ Every command accepts `--help` for full flags and examples:
71
+
72
+ ```bash
73
+ lumo task create --help
74
+ lumo sprint close --help
75
+ ```
76
+
77
+ ## Environment Variables
78
+
79
+ | Variable | Default | Purpose |
80
+ | ------------------------- | ------------------------ | ------------------------------------------------------------- |
81
+ | `LUMO_API_URL` | `https://www.uselumo.ai` | Override the API endpoint (e.g. self-hosted Lumo instance). |
82
+ | `CLAUDE_CODE_SESSION_ID` | _(set by Claude Code)_ | Required for `session attach` / `status` / `detach`. |
83
+
84
+ ## Claude Code Integration
85
+
86
+ Inside a Claude Code session, the agent skill auto-discovers the CLI and uses it to load task context, bind sessions, and update task state. See the [Lumo Claude Code skill](https://github.com/Lumo-Workspace/lumo/tree/main/.claude/skills/lumo) for the full agent contract.
87
+
88
+ ## License
89
+
90
+ MIT
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.authLogin = authLogin;
4
+ const prompt_1 = require("../lib/prompt");
5
+ const browser_1 = require("../lib/browser");
6
+ const api_1 = require("../lib/api");
7
+ const config_1 = require("../lib/config");
8
+ const KEY_PREFIX = 'lum_';
9
+ async function authLogin() {
10
+ const apiUrl = (0, api_1.resolveApiUrl)();
11
+ const webUrl = (0, api_1.trimTrailingSlash)(apiUrl);
12
+ console.log('Log in to Lumo');
13
+ console.log('');
14
+ console.log(` 1. Open ${webUrl} and go to Settings → API Keys`);
15
+ console.log(` 2. Create a new key and copy it`);
16
+ console.log(` 3. Paste it below`);
17
+ console.log('');
18
+ console.log('Opening your browser…');
19
+ (0, browser_1.openBrowser)(webUrl);
20
+ console.log('');
21
+ const token = await (0, prompt_1.promptSecret)('API key (input hidden): ');
22
+ if (!token) {
23
+ console.log('Login cancelled.');
24
+ return 1;
25
+ }
26
+ if (!token.startsWith(KEY_PREFIX)) {
27
+ console.error(`Error: API key must start with "${KEY_PREFIX}"`);
28
+ return 1;
29
+ }
30
+ let resp;
31
+ try {
32
+ resp = await (0, api_1.verifyToken)(apiUrl, token);
33
+ }
34
+ catch (err) {
35
+ const msg = err instanceof Error ? err.message : String(err);
36
+ console.error(`Error: ${msg}`);
37
+ return 1;
38
+ }
39
+ (0, config_1.writeCredentials)({
40
+ token,
41
+ apiUrl,
42
+ userId: resp.user.id,
43
+ email: resp.user.email,
44
+ workspaceId: resp.workspace.id,
45
+ workspaceSlug: resp.workspace.slug,
46
+ workspaceName: resp.workspace.name,
47
+ apiKeyId: resp.apiKey.id,
48
+ apiKeyName: resp.apiKey.name,
49
+ apiKeyPrefix: resp.apiKey.prefix,
50
+ });
51
+ console.log('');
52
+ console.log(`✓ Logged in as ${resp.user.email}`);
53
+ console.log(` Workspace: ${resp.workspace.name}`);
54
+ console.log(` Key: ${resp.apiKey.name} (${resp.apiKey.prefix})`);
55
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.authLogout = authLogout;
4
+ const config_1 = require("../lib/config");
5
+ async function authLogout() {
6
+ const creds = (0, config_1.readCredentials)();
7
+ if (!creds) {
8
+ console.log('Not logged in');
9
+ return 0;
10
+ }
11
+ const email = creds.email;
12
+ (0, config_1.deleteCredentials)();
13
+ console.log(`✓ Logged out (${email})`);
14
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatBindOutput = formatBindOutput;
4
+ exports.docBind = docBind;
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
+ function formatBindOutput(args) {
9
+ if (args.alreadyBound)
10
+ return `Already bound ${args.docId} ↔ ${args.identifier}`;
11
+ const upgrade = args.upgradedFromContent ? ' (upgraded from content)' : '';
12
+ return `Bound ${args.docId} ↔ ${args.identifier}${upgrade}`;
13
+ }
14
+ async function docBind(docRef, task) {
15
+ if (!docRef || !task) {
16
+ console.error('Error: usage: lumo doc bind <doc> <task>');
17
+ return 1;
18
+ }
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 docId = await (0, resolve_doc_id_1.lookupDocId)(apiUrl, creds.token, docRef);
27
+ if (!docId) {
28
+ console.error(`Error: Document not found: ${docRef}`);
29
+ return 1;
30
+ }
31
+ const url = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/documents/${docId}/mentions`;
32
+ const res = await fetch(url, {
33
+ method: 'POST',
34
+ headers: {
35
+ Authorization: `Bearer ${creds.token}`,
36
+ 'Content-Type': 'application/json',
37
+ },
38
+ body: JSON.stringify({ taskIdentifier: task }),
39
+ });
40
+ if (!res.ok) {
41
+ const text = await res.text();
42
+ console.error(`Error: ${res.status} ${res.statusText}: ${text}`);
43
+ return 1;
44
+ }
45
+ const { mention } = (await res.json());
46
+ console.log(formatBindOutput({
47
+ docId,
48
+ identifier: mention.taskIdentifier,
49
+ alreadyBound: mention.alreadyBound,
50
+ upgradedFromContent: mention.upgradedFromContent,
51
+ }));
52
+ }
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeScope = normalizeScope;
4
+ exports.formatCreatedDocLine = formatCreatedDocLine;
5
+ exports.docCreate = docCreate;
6
+ const config_1 = require("../lib/config");
7
+ const api_1 = require("../lib/api");
8
+ const doc_input_1 = require("../lib/doc-input");
9
+ const tag_resolver_1 = require("../lib/tag-resolver");
10
+ const resolve_doc_id_1 = require("../lib/resolve-doc-id");
11
+ /** personal → PRIVATE, workspace → WORKSPACE. Null on unknown. */
12
+ function normalizeScope(value) {
13
+ const lower = (value ?? '').toLowerCase();
14
+ if (lower === 'personal')
15
+ return 'PRIVATE';
16
+ if (lower === 'workspace')
17
+ return 'WORKSPACE';
18
+ return null;
19
+ }
20
+ function formatCreatedDocLine(doc) {
21
+ const escaped = doc.title.replace(/"/g, '\\"');
22
+ const head = `Created ${doc.id} "${escaped}" ${doc.url}`;
23
+ if (doc.tags && doc.tags.length > 0) {
24
+ return `${head}\nTags: ${doc.tags.join(', ')}`;
25
+ }
26
+ return head;
27
+ }
28
+ function docUrl(apiUrl, workspaceSlug, id) {
29
+ return `${(0, api_1.trimTrailingSlash)(apiUrl)}/workspace/${workspaceSlug}/documents/${id}`;
30
+ }
31
+ async function docCreate(title, opts) {
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
+ let visibility = 'PRIVATE';
38
+ if (opts.scope !== undefined) {
39
+ const normalized = normalizeScope(opts.scope);
40
+ if (!normalized) {
41
+ console.error(`Error: invalid scope "${opts.scope}". Allowed: personal, workspace`);
42
+ return 1;
43
+ }
44
+ visibility = normalized;
45
+ }
46
+ const content = await (0, doc_input_1.resolveDocContent)({
47
+ content: opts.content,
48
+ file: opts.file,
49
+ stdinIsTTY: Boolean(process.stdin.isTTY),
50
+ readStdin: doc_input_1.readStdinToString,
51
+ readFile: doc_input_1.readFileUtf8,
52
+ });
53
+ if (content.kind === 'error') {
54
+ console.error(`Error: ${content.message}`);
55
+ return 1;
56
+ }
57
+ const envUrl = process.env.LUMO_API_URL?.trim();
58
+ const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
59
+ const url = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/documents`;
60
+ let tagIds;
61
+ if ((opts.tag && opts.tag.length > 0) ||
62
+ (opts.tagId && opts.tagId.length > 0)) {
63
+ try {
64
+ tagIds = await (0, tag_resolver_1.resolveTagRefs)({ names: opts.tag ?? [], ids: opts.tagId ?? [] }, { apiUrl, token: creds.token });
65
+ }
66
+ catch (err) {
67
+ console.error(`Error: ${err.message}`);
68
+ return 1;
69
+ }
70
+ }
71
+ let resolvedParentId;
72
+ if (opts.parent !== undefined && opts.parent.length > 0) {
73
+ const id = await (0, resolve_doc_id_1.lookupDocId)(apiUrl, creds.token, opts.parent);
74
+ if (!id) {
75
+ console.error(`Error: --parent doc not found: ${opts.parent}`);
76
+ return 1;
77
+ }
78
+ resolvedParentId = id;
79
+ }
80
+ const body = {
81
+ title: title?.trim() ?? '',
82
+ visibility,
83
+ };
84
+ if (content.kind === 'ok')
85
+ body.contentMarkdown = content.markdown;
86
+ if (opts.project !== undefined)
87
+ body.projectRef = opts.project;
88
+ if (tagIds && tagIds.length > 0)
89
+ body.tagIds = tagIds;
90
+ if (resolvedParentId !== undefined)
91
+ body.parentId = resolvedParentId;
92
+ let res;
93
+ try {
94
+ res = await fetch(url, {
95
+ method: 'POST',
96
+ headers: {
97
+ Authorization: `Bearer ${creds.token}`,
98
+ 'Content-Type': 'application/json',
99
+ },
100
+ body: JSON.stringify(body),
101
+ });
102
+ }
103
+ catch (err) {
104
+ console.error(`Error: network failure: ${err.message}`);
105
+ return 1;
106
+ }
107
+ if (!res.ok) {
108
+ const text = await res.text();
109
+ console.error(`Error: ${res.status} ${res.statusText}: ${text}`);
110
+ return 1;
111
+ }
112
+ const { document } = (await res.json());
113
+ const fullUrl = docUrl(apiUrl, creds.workspaceSlug ?? 'lumo', document.slug);
114
+ const responseTagNames = (document.docTags ?? []).map(t => t.name);
115
+ console.log(formatCreatedDocLine({
116
+ id: document.id,
117
+ title: document.title,
118
+ url: fullUrl,
119
+ tags: responseTagNames,
120
+ }));
121
+ if (opts.task) {
122
+ const bindUrl = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/documents/${document.id}/mentions`;
123
+ const bindRes = await fetch(bindUrl, {
124
+ method: 'POST',
125
+ headers: {
126
+ Authorization: `Bearer ${creds.token}`,
127
+ 'Content-Type': 'application/json',
128
+ },
129
+ body: JSON.stringify({ taskIdentifier: opts.task }),
130
+ });
131
+ if (!bindRes.ok) {
132
+ const text = await bindRes.text();
133
+ console.error(`Warning: doc created but bind to ${opts.task} failed: ${bindRes.status} ${text}`);
134
+ return 1;
135
+ }
136
+ console.log(`Bound ${document.id} ↔ ${opts.task}`);
137
+ }
138
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.docDelete = docDelete;
4
+ const config_1 = require("../lib/config");
5
+ const api_1 = require("../lib/api");
6
+ const resolve_doc_1 = require("../lib/resolve-doc");
7
+ const resolve_doc_id_1 = require("../lib/resolve-doc-id");
8
+ async function docDelete(reference, opts) {
9
+ if (!reference) {
10
+ console.error('Error: missing <doc>. Usage: lumo doc delete <doc> --yes');
11
+ return 1;
12
+ }
13
+ if (!opts.yes) {
14
+ console.error('Error: Refusing to delete without --yes');
15
+ return 1;
16
+ }
17
+ const creds = (0, config_1.readCredentials)();
18
+ if (!creds) {
19
+ console.error('Error: not logged in. Run `lumo auth login` first.');
20
+ return 1;
21
+ }
22
+ const envUrl = process.env.LUMO_API_URL?.trim();
23
+ const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
24
+ // For a cuid reference we don't have the title; for a title we look up id+title at once.
25
+ let id;
26
+ let title = '';
27
+ if ((0, resolve_doc_1.isLikelyCuid)(reference)) {
28
+ id = reference;
29
+ }
30
+ else {
31
+ const fetched = await (0, resolve_doc_id_1.lookupDocId)(apiUrl, creds.token, reference);
32
+ if (!fetched) {
33
+ console.error(`Error: Document not found: ${reference}`);
34
+ return 1;
35
+ }
36
+ id = fetched;
37
+ // Pick up the title from the lookup payload by repeating the GET — small extra cost.
38
+ // (Alternative: change lookupDocId to return the full DocLike. For now keep it simple.)
39
+ title = reference;
40
+ }
41
+ const res = await fetch(`${(0, api_1.trimTrailingSlash)(apiUrl)}/api/documents/${id}`, {
42
+ method: 'DELETE',
43
+ headers: { Authorization: `Bearer ${creds.token}` },
44
+ });
45
+ if (!res.ok) {
46
+ const text = await res.text();
47
+ console.error(`Error: ${res.status} ${res.statusText}: ${text}`);
48
+ return 1;
49
+ }
50
+ const escaped = title.replace(/"/g, '\\"');
51
+ console.log(`Deleted ${id}${title ? ` "${escaped}"` : ''}`);
52
+ }
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatDocListRows = formatDocListRows;
4
+ exports.formatDocListRowsAsTree = formatDocListRowsAsTree;
5
+ exports.docList = docList;
6
+ const config_1 = require("../lib/config");
7
+ const api_1 = require("../lib/api");
8
+ const doc_create_1 = require("./doc-create");
9
+ const doc_tree_1 = require("../lib/doc-tree");
10
+ function visibilityLabel(v) {
11
+ if (v === 'PRIVATE')
12
+ return 'PERSONAL';
13
+ return v;
14
+ }
15
+ function formatDocListRows(rows) {
16
+ if (rows.length === 0)
17
+ return [];
18
+ return rows.map(r => {
19
+ const label = visibilityLabel(r.visibility).padEnd(10, ' ');
20
+ const project = (r.project?.name ?? '-').padEnd(14, ' ');
21
+ return `${r.id} ${label} ${project} ${r.title}`;
22
+ });
23
+ }
24
+ function formatDocListRowsAsTree(rows) {
25
+ if (rows.length === 0)
26
+ return [];
27
+ const tree = (0, doc_tree_1.buildTree)(rows);
28
+ const flat = (0, doc_tree_1.flattenWithDepth)(tree);
29
+ return flat.map(({ row, depth }) => {
30
+ const label = visibilityLabel(row.visibility).padEnd(10, ' ');
31
+ const project = (row.project?.name ?? '-').padEnd(14, ' ');
32
+ const indent = ' '.repeat(depth);
33
+ return `${row.id} ${label} ${project} ${indent}${row.title}`;
34
+ });
35
+ }
36
+ async function docList(opts) {
37
+ const creds = (0, config_1.readCredentials)();
38
+ if (!creds) {
39
+ console.error('Error: not logged in. Run `lumo auth login` first.');
40
+ return 1;
41
+ }
42
+ const envUrl = process.env.LUMO_API_URL?.trim();
43
+ const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
44
+ let url;
45
+ if (opts.task) {
46
+ url = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/tasks/${opts.task}/documents`;
47
+ }
48
+ else {
49
+ const params = new URLSearchParams();
50
+ if (opts.scope && opts.scope !== 'all') {
51
+ const v = (0, doc_create_1.normalizeScope)(opts.scope);
52
+ if (!v) {
53
+ console.error(`Error: invalid scope "${opts.scope}". Allowed: personal, workspace, all`);
54
+ return 1;
55
+ }
56
+ params.set('visibility', v);
57
+ }
58
+ if (opts.project)
59
+ params.set('projectRef', opts.project);
60
+ const qs = params.toString();
61
+ url = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/documents${qs ? `?${qs}` : ''}`;
62
+ }
63
+ const res = await fetch(url, {
64
+ headers: { Authorization: `Bearer ${creds.token}` },
65
+ });
66
+ if (!res.ok) {
67
+ const text = await res.text();
68
+ console.error(`Error: ${res.status} ${res.statusText}: ${text}`);
69
+ return 1;
70
+ }
71
+ const { documents } = (await res.json());
72
+ let rows = documents;
73
+ if (opts.task && opts.scope && opts.scope !== 'all') {
74
+ const v = (0, doc_create_1.normalizeScope)(opts.scope);
75
+ if (v)
76
+ rows = rows.filter(r => r.visibility === v);
77
+ }
78
+ if (opts.task && opts.project) {
79
+ rows = rows.filter(r => (r.project?.name ?? '').toLowerCase() === opts.project.toLowerCase());
80
+ }
81
+ if (opts.limit) {
82
+ const n = parseInt(opts.limit, 10);
83
+ if (!Number.isNaN(n))
84
+ rows = rows.slice(0, n);
85
+ }
86
+ const lines = opts.tree
87
+ ? formatDocListRowsAsTree(rows)
88
+ : formatDocListRows(rows);
89
+ for (const line of lines)
90
+ console.log(line);
91
+ }
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.docMove = docMove;
4
+ const config_1 = require("../lib/config");
5
+ const api_1 = require("../lib/api");
6
+ const resolve_doc_1 = require("../lib/resolve-doc");
7
+ const doc_sort_order_1 = require("../lib/doc-sort-order");
8
+ async function docMove(reference, opts) {
9
+ if (!reference) {
10
+ console.error('Error: usage: lumo doc move <doc> --parent <doc> | --root');
11
+ return 1;
12
+ }
13
+ const hasParent = typeof opts.parent === 'string' && opts.parent.length > 0;
14
+ const hasRoot = Boolean(opts.root);
15
+ if (hasParent && hasRoot) {
16
+ console.error('Error: --parent and --root are mutually exclusive');
17
+ return 1;
18
+ }
19
+ if (!hasParent && !hasRoot) {
20
+ console.error('Error: specify --parent <doc> or --root');
21
+ return 1;
22
+ }
23
+ const creds = (0, config_1.readCredentials)();
24
+ if (!creds) {
25
+ console.error('Error: not logged in. Run `lumo auth login` first.');
26
+ return 1;
27
+ }
28
+ const envUrl = process.env.LUMO_API_URL?.trim();
29
+ const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
30
+ const listUrl = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/documents`;
31
+ let listRes;
32
+ try {
33
+ listRes = await fetch(listUrl, {
34
+ headers: { Authorization: `Bearer ${creds.token}` },
35
+ });
36
+ }
37
+ catch (err) {
38
+ console.error(`Error: network failure: ${err.message}`);
39
+ return 1;
40
+ }
41
+ if (!listRes.ok) {
42
+ const text = await listRes.text();
43
+ console.error(`Error: ${listRes.status} ${listRes.statusText}: ${text}`);
44
+ return 1;
45
+ }
46
+ const { documents } = (await listRes.json());
47
+ const docMatch = (0, resolve_doc_1.resolveDoc)(reference, documents);
48
+ if (docMatch.kind === 'ambiguous') {
49
+ console.error(`Error: title "${reference}" matches ${docMatch.candidates.length} docs:`);
50
+ for (const c of docMatch.candidates) {
51
+ console.error(` ${c.id} ${c.title}`);
52
+ }
53
+ console.error('Re-run with the cuid.');
54
+ return 1;
55
+ }
56
+ if (docMatch.kind === 'not-found') {
57
+ console.error(`Error: Document not found: ${reference}`);
58
+ return 1;
59
+ }
60
+ const docRow = documents.find(d => d.id === docMatch.doc.id);
61
+ let newParentId = null;
62
+ let parentTitleForOutput = 'root';
63
+ if (hasParent) {
64
+ const parentMatch = (0, resolve_doc_1.resolveDoc)(opts.parent, documents);
65
+ if (parentMatch.kind === 'ambiguous') {
66
+ console.error(`Error: --parent "${opts.parent}" matches ${parentMatch.candidates.length} docs:`);
67
+ for (const c of parentMatch.candidates) {
68
+ console.error(` ${c.id} ${c.title}`);
69
+ }
70
+ console.error('Re-run with the cuid.');
71
+ return 1;
72
+ }
73
+ if (parentMatch.kind === 'not-found') {
74
+ console.error(`Error: Document not found: ${opts.parent}`);
75
+ return 1;
76
+ }
77
+ const parentRow = documents.find(d => d.id === parentMatch.doc.id);
78
+ newParentId = parentRow.id;
79
+ parentTitleForOutput = `"${parentRow.title.replace(/"/g, '\\"')}"`;
80
+ }
81
+ const siblings = documents.filter(d => d.parentId === newParentId && d.id !== docRow.id);
82
+ const sortOrder = (0, doc_sort_order_1.pickNextSortOrder)(siblings);
83
+ const moveUrl = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/documents/${docRow.id}/move`;
84
+ let moveRes;
85
+ try {
86
+ moveRes = await fetch(moveUrl, {
87
+ method: 'PATCH',
88
+ headers: {
89
+ Authorization: `Bearer ${creds.token}`,
90
+ 'Content-Type': 'application/json',
91
+ },
92
+ body: JSON.stringify({ parentId: newParentId, sortOrder }),
93
+ });
94
+ }
95
+ catch (err) {
96
+ console.error(`Error: network failure: ${err.message}`);
97
+ return 1;
98
+ }
99
+ if (!moveRes.ok) {
100
+ const text = await moveRes.text();
101
+ let msg = text;
102
+ try {
103
+ const json = JSON.parse(text);
104
+ if (json.error)
105
+ msg = json.error;
106
+ }
107
+ catch { }
108
+ console.error(`Error: ${moveRes.status} ${moveRes.statusText}: ${msg}`);
109
+ return 1;
110
+ }
111
+ const escapedDocTitle = docRow.title.replace(/"/g, '\\"');
112
+ console.log(`Moved ${docRow.id} "${escapedDocTitle}" → ${parentTitleForOutput}`);
113
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatShareListRows = formatShareListRows;
4
+ exports.docShareList = docShareList;
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
+ function formatShareListRows(rows) {
10
+ if (rows.length === 0)
11
+ return [];
12
+ const nameWidth = Math.max(...rows.map(r => r.displayName.length));
13
+ return rows.map(r => {
14
+ const name = r.displayName.padEnd(nameWidth, ' ');
15
+ return `${name} ${r.role}`;
16
+ });
17
+ }
18
+ async function docShareList(docRef) {
19
+ if (!docRef) {
20
+ console.error('Error: usage: lumo doc share-list <doc>');
21
+ return 1;
22
+ }
23
+ const creds = (0, config_1.readCredentials)();
24
+ if (!creds) {
25
+ console.error('Error: not logged in. Run `lumo auth login` first.');
26
+ return 1;
27
+ }
28
+ const envUrl = process.env.LUMO_API_URL?.trim();
29
+ const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
30
+ const docId = await (0, resolve_doc_id_1.lookupDocId)(apiUrl, creds.token, docRef);
31
+ if (!docId) {
32
+ console.error(`Error: Document not found: ${docRef}`);
33
+ return 1;
34
+ }
35
+ // Shares endpoint returns memberId only; we cross-reference the workspace
36
+ // member directory to print a friendly display name. If the directory call
37
+ // fails (rare), we fall back to memberId so the user still sees something.
38
+ const [sharesRes, members] = await Promise.all([
39
+ fetch(`${(0, api_1.trimTrailingSlash)(apiUrl)}/api/documents/${docId}/shares`, {
40
+ headers: { Authorization: `Bearer ${creds.token}` },
41
+ }),
42
+ (0, resolve_member_1.fetchMembers)(apiUrl, creds.token).catch(() => null),
43
+ ]);
44
+ if (!sharesRes.ok) {
45
+ const text = await sharesRes.text();
46
+ console.error(`Error: ${sharesRes.status} ${sharesRes.statusText}: ${text}`);
47
+ return 1;
48
+ }
49
+ const { shares } = (await sharesRes.json());
50
+ const nameByMemberId = new Map();
51
+ if (members) {
52
+ for (const m of members)
53
+ nameByMemberId.set(m.memberId, m.displayName);
54
+ }
55
+ const rows = shares.map(s => ({
56
+ displayName: nameByMemberId.get(s.member.id) ?? s.member.id,
57
+ role: s.role,
58
+ }));
59
+ const lines = formatShareListRows(rows);
60
+ for (const line of lines)
61
+ console.log(line);
62
+ }