@lumoai/cli 1.4.0 → 1.5.1

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 (83) hide show
  1. package/assets/skill.md +228 -17
  2. package/dist/cli/src/commands/auth-login.js +4 -3
  3. package/dist/cli/src/commands/auth-logout.js +2 -1
  4. package/dist/cli/src/commands/doc-bind.js +3 -3
  5. package/dist/cli/src/commands/doc-create.js +5 -5
  6. package/dist/cli/src/commands/doc-delete.js +4 -4
  7. package/dist/cli/src/commands/doc-import-gdoc.js +82 -0
  8. package/dist/cli/src/commands/doc-list.js +7 -7
  9. package/dist/cli/src/commands/doc-move.js +8 -8
  10. package/dist/cli/src/commands/doc-share-list.js +11 -8
  11. package/dist/cli/src/commands/doc-share.js +7 -5
  12. package/dist/cli/src/commands/doc-show.js +6 -6
  13. package/dist/cli/src/commands/doc-sync.js +44 -0
  14. package/dist/cli/src/commands/doc-unbind.js +4 -4
  15. package/dist/cli/src/commands/doc-unshare.js +9 -7
  16. package/dist/cli/src/commands/doc-update.js +5 -5
  17. package/dist/cli/src/commands/hook.js +2 -2
  18. package/dist/cli/src/commands/memory-project-add.js +86 -0
  19. package/dist/cli/src/commands/memory-project-list.js +58 -0
  20. package/dist/cli/src/commands/memory-promote.js +52 -0
  21. package/dist/cli/src/commands/memory-rm.js +42 -0
  22. package/dist/cli/src/commands/memory-task-add.js +99 -0
  23. package/dist/cli/src/commands/memory-task-list.js +61 -0
  24. package/dist/cli/src/commands/milestone-create.js +4 -4
  25. package/dist/cli/src/commands/milestone-delete.js +5 -5
  26. package/dist/cli/src/commands/milestone-list.js +3 -3
  27. package/dist/cli/src/commands/milestone-show.js +5 -5
  28. package/dist/cli/src/commands/milestone-update.js +6 -5
  29. package/dist/cli/src/commands/project-list.js +3 -3
  30. package/dist/cli/src/commands/session-attach.js +5 -5
  31. package/dist/cli/src/commands/session-detach.js +3 -3
  32. package/dist/cli/src/commands/session-status.js +3 -3
  33. package/dist/cli/src/commands/setup.js +33 -7
  34. package/dist/cli/src/commands/sprint-add.js +3 -3
  35. package/dist/cli/src/commands/sprint-close.js +5 -5
  36. package/dist/cli/src/commands/sprint-create.js +4 -4
  37. package/dist/cli/src/commands/sprint-delete.js +5 -5
  38. package/dist/cli/src/commands/sprint-list.js +3 -3
  39. package/dist/cli/src/commands/sprint-remove.js +3 -3
  40. package/dist/cli/src/commands/sprint-show.js +4 -4
  41. package/dist/cli/src/commands/sprint-start.js +4 -4
  42. package/dist/cli/src/commands/sprint-summary.js +7 -7
  43. package/dist/cli/src/commands/sprint-update.js +6 -5
  44. package/dist/cli/src/commands/task-artifact-add.js +35 -6
  45. package/dist/cli/src/commands/task-artifact-list.js +5 -3
  46. package/dist/cli/src/commands/task-artifact-rm.js +4 -4
  47. package/dist/cli/src/commands/task-artifact-show.js +9 -7
  48. package/dist/cli/src/commands/task-artifact-update.js +15 -6
  49. package/dist/cli/src/commands/task-comment-list.js +111 -0
  50. package/dist/cli/src/commands/task-comment.js +3 -3
  51. package/dist/cli/src/commands/task-context.js +24 -12
  52. package/dist/cli/src/commands/task-create.js +7 -7
  53. package/dist/cli/src/commands/task-figma-add.js +3 -2
  54. package/dist/cli/src/commands/task-figma-context.js +61 -0
  55. package/dist/cli/src/commands/task-figma-list.js +3 -2
  56. package/dist/cli/src/commands/task-figma-refresh.js +4 -3
  57. package/dist/cli/src/commands/task-figma-rm.js +3 -2
  58. package/dist/cli/src/commands/task-list.js +1 -2
  59. package/dist/cli/src/commands/task-pr-show.js +66 -0
  60. package/dist/cli/src/commands/task-show.js +8 -7
  61. package/dist/cli/src/commands/task-slack-show.js +59 -0
  62. package/dist/cli/src/commands/task-update.js +7 -7
  63. package/dist/cli/src/commands/task-web-show.js +64 -0
  64. package/dist/cli/src/commands/whoami.js +4 -3
  65. package/dist/cli/src/index.js +239 -102
  66. package/dist/cli/src/lib/agent.js +58 -0
  67. package/dist/cli/src/lib/api.js +81 -1
  68. package/dist/cli/src/lib/config.js +2 -1
  69. package/dist/cli/src/lib/doc-input.js +12 -1
  70. package/dist/cli/src/lib/figma-api.js +1 -1
  71. package/dist/cli/src/lib/format.js +3 -2
  72. package/dist/cli/src/lib/hook-runner.js +26 -10
  73. package/dist/cli/src/lib/hooks-template.js +52 -7
  74. package/dist/cli/src/lib/memory-content.js +88 -0
  75. package/dist/cli/src/lib/path-guard.js +125 -0
  76. package/dist/cli/src/lib/resolve-bound-task.js +31 -0
  77. package/dist/cli/src/lib/resolve-doc-id.js +2 -1
  78. package/dist/cli/src/lib/resolve-member.js +2 -1
  79. package/dist/cli/src/lib/resolve-project.js +24 -0
  80. package/dist/cli/src/lib/sanitize.js +17 -0
  81. package/dist/cli/src/lib/tag-resolver.js +2 -1
  82. package/dist/cli/src/lib/update-check.js +2 -2
  83. package/package.json +1 -1
@@ -5,6 +5,7 @@ const config_1 = require("../lib/config");
5
5
  const api_1 = require("../lib/api");
6
6
  const resolve_doc_1 = require("../lib/resolve-doc");
7
7
  const doc_sort_order_1 = require("../lib/doc-sort-order");
8
+ const sanitize_1 = require("../lib/sanitize");
8
9
  async function docMove(reference, opts) {
9
10
  if (!reference) {
10
11
  console.error('Error: usage: lumo doc move <doc> --parent <doc> | --root');
@@ -25,8 +26,7 @@ async function docMove(reference, opts) {
25
26
  console.error('Error: not logged in. Run `lumo auth login` first.');
26
27
  return 1;
27
28
  }
28
- const envUrl = process.env.LUMO_API_URL?.trim();
29
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
29
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
30
30
  const listUrl = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/documents`;
31
31
  let listRes;
32
32
  try {
@@ -40,7 +40,7 @@ async function docMove(reference, opts) {
40
40
  }
41
41
  if (!listRes.ok) {
42
42
  const text = await listRes.text();
43
- console.error(`Error: ${listRes.status} ${listRes.statusText}: ${text}`);
43
+ console.error(`Error: ${listRes.status} ${listRes.statusText}: ${(0, sanitize_1.sanitizeField)(text)}`);
44
44
  return 1;
45
45
  }
46
46
  const { documents } = (await listRes.json());
@@ -48,7 +48,7 @@ async function docMove(reference, opts) {
48
48
  if (docMatch.kind === 'ambiguous') {
49
49
  console.error(`Error: title "${reference}" matches ${docMatch.candidates.length} docs:`);
50
50
  for (const c of docMatch.candidates) {
51
- console.error(` ${c.id} ${c.title}`);
51
+ console.error(` ${c.id} ${(0, sanitize_1.sanitizeField)(c.title)}`);
52
52
  }
53
53
  console.error('Re-run with the cuid.');
54
54
  return 1;
@@ -65,7 +65,7 @@ async function docMove(reference, opts) {
65
65
  if (parentMatch.kind === 'ambiguous') {
66
66
  console.error(`Error: --parent "${opts.parent}" matches ${parentMatch.candidates.length} docs:`);
67
67
  for (const c of parentMatch.candidates) {
68
- console.error(` ${c.id} ${c.title}`);
68
+ console.error(` ${c.id} ${(0, sanitize_1.sanitizeField)(c.title)}`);
69
69
  }
70
70
  console.error('Re-run with the cuid.');
71
71
  return 1;
@@ -76,7 +76,7 @@ async function docMove(reference, opts) {
76
76
  }
77
77
  const parentRow = documents.find(d => d.id === parentMatch.doc.id);
78
78
  newParentId = parentRow.id;
79
- parentTitleForOutput = `"${parentRow.title.replace(/"/g, '\\"')}"`;
79
+ parentTitleForOutput = `"${(0, sanitize_1.sanitizeField)(parentRow.title).replace(/"/g, '\\"')}"`;
80
80
  }
81
81
  const siblings = documents.filter(d => d.parentId === newParentId && d.id !== docRow.id);
82
82
  const sortOrder = (0, doc_sort_order_1.pickNextSortOrder)(siblings);
@@ -105,9 +105,9 @@ async function docMove(reference, opts) {
105
105
  msg = json.error;
106
106
  }
107
107
  catch { }
108
- console.error(`Error: ${moveRes.status} ${moveRes.statusText}: ${msg}`);
108
+ console.error(`Error: ${moveRes.status} ${moveRes.statusText}: ${(0, sanitize_1.sanitizeField)(msg)}`);
109
109
  return 1;
110
110
  }
111
- const escapedDocTitle = docRow.title.replace(/"/g, '\\"');
111
+ const escapedDocTitle = (0, sanitize_1.sanitizeField)(docRow.title).replace(/"/g, '\\"');
112
112
  console.log(`Moved ${docRow.id} "${escapedDocTitle}" → ${parentTitleForOutput}`);
113
113
  }
@@ -6,14 +6,18 @@ const config_1 = require("../lib/config");
6
6
  const api_1 = require("../lib/api");
7
7
  const resolve_doc_id_1 = require("../lib/resolve-doc-id");
8
8
  const resolve_member_1 = require("../lib/resolve-member");
9
+ const sanitize_1 = require("../lib/sanitize");
9
10
  function formatShareListRows(rows) {
10
11
  if (rows.length === 0)
11
12
  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
- });
13
+ // Sanitize first, then measure/pad off the sanitized string so the
14
+ // column width matches the printed cell (control chars stripped).
15
+ const sanitized = rows.map(r => ({
16
+ name: (0, sanitize_1.sanitizeField)(r.displayName),
17
+ role: r.role,
18
+ }));
19
+ const nameWidth = Math.max(...sanitized.map(r => r.name.length));
20
+ return sanitized.map(r => `${r.name.padEnd(nameWidth, ' ')} ${r.role}`);
17
21
  }
18
22
  async function docShareList(docRef) {
19
23
  if (!docRef) {
@@ -25,8 +29,7 @@ async function docShareList(docRef) {
25
29
  console.error('Error: not logged in. Run `lumo auth login` first.');
26
30
  return 1;
27
31
  }
28
- const envUrl = process.env.LUMO_API_URL?.trim();
29
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
32
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
30
33
  const docId = await (0, resolve_doc_id_1.lookupDocId)(apiUrl, creds.token, docRef);
31
34
  if (!docId) {
32
35
  console.error(`Error: Document not found: ${docRef}`);
@@ -43,7 +46,7 @@ async function docShareList(docRef) {
43
46
  ]);
44
47
  if (!sharesRes.ok) {
45
48
  const text = await sharesRes.text();
46
- console.error(`Error: ${sharesRes.status} ${sharesRes.statusText}: ${text}`);
49
+ console.error(`Error: ${sharesRes.status} ${sharesRes.statusText}: ${(0, sanitize_1.sanitizeField)(text)}`);
47
50
  return 1;
48
51
  }
49
52
  const { shares } = (await sharesRes.json());
@@ -6,6 +6,7 @@ const config_1 = require("../lib/config");
6
6
  const api_1 = require("../lib/api");
7
7
  const resolve_doc_id_1 = require("../lib/resolve-doc-id");
8
8
  const resolve_member_1 = require("../lib/resolve-member");
9
+ const sanitize_1 = require("../lib/sanitize");
9
10
  const ALLOWED_ROLES = ['VIEWER', 'EDITOR', 'MANAGER'];
10
11
  function normalizeRole(value) {
11
12
  if (!value)
@@ -34,8 +35,7 @@ async function docShare(docRef, memberRef, opts) {
34
35
  console.error('Error: not logged in. Run `lumo auth login` first.');
35
36
  return 1;
36
37
  }
37
- const envUrl = process.env.LUMO_API_URL?.trim();
38
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
38
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
39
39
  const docId = await (0, resolve_doc_id_1.lookupDocId)(apiUrl, creds.token, docRef);
40
40
  if (!docId) {
41
41
  console.error(`Error: Document not found: ${docRef}`);
@@ -55,7 +55,9 @@ async function docShare(docRef, memberRef, opts) {
55
55
  return 1;
56
56
  }
57
57
  if (result.kind === 'ambiguous') {
58
- const list = result.candidates.map(c => c.email ?? c.displayName).join(', ');
58
+ const list = result.candidates
59
+ .map(c => (0, sanitize_1.sanitizeField)(c.email ?? c.displayName))
60
+ .join(', ');
59
61
  console.error(`Error: multiple members match "${result.query}": ${list}`);
60
62
  return 1;
61
63
  }
@@ -70,8 +72,8 @@ async function docShare(docRef, memberRef, opts) {
70
72
  });
71
73
  if (!res.ok) {
72
74
  const text = await res.text();
73
- console.error(`Error: ${res.status} ${res.statusText}: ${text}`);
75
+ console.error(`Error: ${res.status} ${res.statusText}: ${(0, sanitize_1.sanitizeField)(text)}`);
74
76
  return 1;
75
77
  }
76
- console.log(`Shared ${docId} ↔ ${result.member.displayName} (${role})`);
78
+ console.log(`Shared ${docId} ↔ ${(0, sanitize_1.sanitizeField)(result.member.displayName)} (${role})`);
77
79
  }
@@ -6,6 +6,7 @@ const config_1 = require("../lib/config");
6
6
  const api_1 = require("../lib/api");
7
7
  const markdown_tiptap_1 = require("../lib/markdown-tiptap");
8
8
  const resolve_doc_id_1 = require("../lib/resolve-doc-id");
9
+ const sanitize_1 = require("../lib/sanitize");
9
10
  function scopeLabel(s) {
10
11
  if (s === 'PRIVATE')
11
12
  return 'personal';
@@ -16,15 +17,15 @@ function scopeLabel(s) {
16
17
  function formatShowOutput(vm) {
17
18
  const lines = [
18
19
  `ID: ${vm.id}`,
19
- `Title: ${vm.title}`,
20
+ `Title: ${(0, sanitize_1.sanitizeField)(vm.title)}`,
20
21
  `Scope: ${scopeLabel(vm.scope)}`,
21
- `Project: ${vm.projectName ?? '-'}`,
22
+ `Project: ${vm.projectName ? (0, sanitize_1.sanitizeField)(vm.projectName) : '-'}`,
22
23
  `Created: ${vm.createdAt}`,
23
24
  `Updated: ${vm.updatedAt}`,
24
25
  `Mentioned tasks: ${vm.mentionedTasks.length ? vm.mentionedTasks.join(', ') : '-'}`,
25
26
  ];
26
27
  const header = lines.join('\n');
27
- return vm.bodyMarkdown ? `${header}\n\n${vm.bodyMarkdown}` : header;
28
+ return vm.bodyMarkdown ? `${header}\n\n${(0, sanitize_1.sanitizeField)(vm.bodyMarkdown)}` : header;
28
29
  }
29
30
  async function docShow(reference) {
30
31
  if (!reference) {
@@ -36,8 +37,7 @@ async function docShow(reference) {
36
37
  console.error('Error: not logged in. Run `lumo auth login` first.');
37
38
  return 1;
38
39
  }
39
- const envUrl = process.env.LUMO_API_URL?.trim();
40
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
40
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
41
41
  const id = await (0, resolve_doc_id_1.lookupDocId)(apiUrl, creds.token, reference);
42
42
  if (!id) {
43
43
  console.error(`Error: Document not found: ${reference}`);
@@ -48,7 +48,7 @@ async function docShow(reference) {
48
48
  });
49
49
  if (!res.ok) {
50
50
  const text = await res.text();
51
- console.error(`Error: ${res.status} ${res.statusText}: ${text}`);
51
+ console.error(`Error: ${res.status} ${res.statusText}: ${(0, sanitize_1.sanitizeField)(text)}`);
52
52
  return 1;
53
53
  }
54
54
  // The exact shape of the GET response is not guaranteed; defensively unwrap.
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatSyncedLine = formatSyncedLine;
4
+ exports.docSync = docSync;
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 sanitize_1 = require("../lib/sanitize");
9
+ function formatSyncedLine(doc) {
10
+ const escaped = (0, sanitize_1.sanitizeField)(doc.title).replace(/"/g, '\\"');
11
+ return `Synced ${doc.id} "${escaped}" from Google`;
12
+ }
13
+ async function docSync(reference) {
14
+ const creds = (0, config_1.readCredentials)();
15
+ if (!creds) {
16
+ console.error('Error: not logged in. Run `lumo auth login` first.');
17
+ return 1;
18
+ }
19
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
20
+ const base = (0, api_1.trimTrailingSlash)(apiUrl);
21
+ const docId = await (0, resolve_doc_id_1.lookupDocId)(apiUrl, creds.token, reference);
22
+ if (!docId) {
23
+ console.error(`Error: doc not found: ${reference}`);
24
+ return 1;
25
+ }
26
+ let res;
27
+ try {
28
+ res = await fetch(`${base}/api/documents/${docId}/sync-google`, {
29
+ method: 'POST',
30
+ headers: { Authorization: `Bearer ${creds.token}` },
31
+ });
32
+ }
33
+ catch (err) {
34
+ console.error(`Error: network failure: ${err.message}`);
35
+ return 1;
36
+ }
37
+ if (!res.ok) {
38
+ const text = await res.text();
39
+ console.error(`Error: ${res.status} ${res.statusText}: ${(0, sanitize_1.sanitizeField)(text)}`);
40
+ return 1;
41
+ }
42
+ const { document } = (await res.json());
43
+ console.log(formatSyncedLine(document));
44
+ }
@@ -4,6 +4,7 @@ exports.docUnbind = docUnbind;
4
4
  const config_1 = require("../lib/config");
5
5
  const api_1 = require("../lib/api");
6
6
  const resolve_doc_id_1 = require("../lib/resolve-doc-id");
7
+ const sanitize_1 = require("../lib/sanitize");
7
8
  async function docUnbind(docRef, task) {
8
9
  if (!docRef || !task) {
9
10
  console.error('Error: usage: lumo doc unbind <doc> <task>');
@@ -14,8 +15,7 @@ async function docUnbind(docRef, task) {
14
15
  console.error('Error: not logged in. Run `lumo auth login` first.');
15
16
  return 1;
16
17
  }
17
- const envUrl = process.env.LUMO_API_URL?.trim();
18
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
18
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
19
19
  const docId = await (0, resolve_doc_id_1.lookupDocId)(apiUrl, creds.token, docRef);
20
20
  if (!docId) {
21
21
  console.error(`Error: Document not found: ${docRef}`);
@@ -29,12 +29,12 @@ async function docUnbind(docRef, task) {
29
29
  });
30
30
  if (res.status === 409) {
31
31
  const { error } = (await res.json());
32
- console.error(`Error: ${error}`);
32
+ console.error(`Error: ${(0, sanitize_1.sanitizeField)(error)}`);
33
33
  return 1;
34
34
  }
35
35
  if (!res.ok) {
36
36
  const text = await res.text();
37
- console.error(`Error: ${res.status} ${res.statusText}: ${text}`);
37
+ console.error(`Error: ${res.status} ${res.statusText}: ${(0, sanitize_1.sanitizeField)(text)}`);
38
38
  return 1;
39
39
  }
40
40
  const data = (await res.json());
@@ -5,6 +5,7 @@ const config_1 = require("../lib/config");
5
5
  const api_1 = require("../lib/api");
6
6
  const resolve_doc_id_1 = require("../lib/resolve-doc-id");
7
7
  const resolve_member_1 = require("../lib/resolve-member");
8
+ const sanitize_1 = require("../lib/sanitize");
8
9
  async function docUnshare(docRef, memberRef) {
9
10
  if (!docRef || !memberRef) {
10
11
  console.error('Error: usage: lumo doc unshare <doc> <member>');
@@ -15,8 +16,7 @@ async function docUnshare(docRef, memberRef) {
15
16
  console.error('Error: not logged in. Run `lumo auth login` first.');
16
17
  return 1;
17
18
  }
18
- const envUrl = process.env.LUMO_API_URL?.trim();
19
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
19
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
20
20
  const docId = await (0, resolve_doc_id_1.lookupDocId)(apiUrl, creds.token, docRef);
21
21
  if (!docId) {
22
22
  console.error(`Error: Document not found: ${docRef}`);
@@ -36,7 +36,9 @@ async function docUnshare(docRef, memberRef) {
36
36
  return 1;
37
37
  }
38
38
  if (result.kind === 'ambiguous') {
39
- const list = result.candidates.map(c => c.email ?? c.displayName).join(', ');
39
+ const list = result.candidates
40
+ .map(c => (0, sanitize_1.sanitizeField)(c.email ?? c.displayName))
41
+ .join(', ');
40
42
  console.error(`Error: multiple members match "${result.query}": ${list}`);
41
43
  return 1;
42
44
  }
@@ -48,13 +50,13 @@ async function docUnshare(docRef, memberRef) {
48
50
  });
49
51
  if (!listRes.ok) {
50
52
  const text = await listRes.text();
51
- console.error(`Error: ${listRes.status} ${listRes.statusText}: ${text}`);
53
+ console.error(`Error: ${listRes.status} ${listRes.statusText}: ${(0, sanitize_1.sanitizeField)(text)}`);
52
54
  return 1;
53
55
  }
54
56
  const { shares } = (await listRes.json());
55
57
  const row = shares.find(s => s.member.id === result.member.memberId);
56
58
  if (!row) {
57
- console.log(`Not shared with ${result.member.displayName}`);
59
+ console.log(`Not shared with ${(0, sanitize_1.sanitizeField)(result.member.displayName)}`);
58
60
  return;
59
61
  }
60
62
  const delUrl = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/documents/${docId}/shares/${row.id}`;
@@ -64,8 +66,8 @@ async function docUnshare(docRef, memberRef) {
64
66
  });
65
67
  if (!delRes.ok) {
66
68
  const text = await delRes.text();
67
- console.error(`Error: ${delRes.status} ${delRes.statusText}: ${text}`);
69
+ console.error(`Error: ${delRes.status} ${delRes.statusText}: ${(0, sanitize_1.sanitizeField)(text)}`);
68
70
  return 1;
69
71
  }
70
- console.log(`Unshared ${docId} ↔ ${result.member.displayName}`);
72
+ console.log(`Unshared ${docId} ↔ ${(0, sanitize_1.sanitizeField)(result.member.displayName)}`);
71
73
  }
@@ -8,11 +8,12 @@ const doc_input_1 = require("../lib/doc-input");
8
8
  const doc_create_1 = require("./doc-create");
9
9
  const resolve_doc_id_1 = require("../lib/resolve-doc-id");
10
10
  const tag_resolver_1 = require("../lib/tag-resolver");
11
+ const sanitize_1 = require("../lib/sanitize");
11
12
  function formatUpdatedDocLine(doc) {
12
- const escaped = doc.title.replace(/"/g, '\\"');
13
+ const escaped = (0, sanitize_1.sanitizeField)(doc.title).replace(/"/g, '\\"');
13
14
  const head = `Updated ${doc.id} "${escaped}" ${doc.url}`;
14
15
  if (doc.tags && doc.tags.length > 0) {
15
- return `${head}\nTags: ${doc.tags.join(', ')}`;
16
+ return `${head}\nTags: ${doc.tags.map(sanitize_1.sanitizeField).join(', ')}`;
16
17
  }
17
18
  return head;
18
19
  }
@@ -26,8 +27,7 @@ async function docUpdate(reference, opts) {
26
27
  console.error('Error: not logged in. Run `lumo auth login` first.');
27
28
  return 1;
28
29
  }
29
- const envUrl = process.env.LUMO_API_URL?.trim();
30
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
30
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
31
31
  // CLI-side mutex check: --tag/--tag-id (bulk replace) vs --add-tag/--add-tag-id/--remove-tag/--remove-tag-id (incremental)
32
32
  const hasBulk = (opts.tag && opts.tag.length > 0) || (opts.tagId && opts.tagId.length > 0);
33
33
  const hasIncremental = (opts.addTag && opts.addTag.length > 0) ||
@@ -122,7 +122,7 @@ async function docUpdate(reference, opts) {
122
122
  });
123
123
  if (!res.ok) {
124
124
  const text = await res.text();
125
- console.error(`Error: ${res.status} ${res.statusText}: ${text}`);
125
+ console.error(`Error: ${res.status} ${res.statusText}: ${(0, sanitize_1.sanitizeField)(text)}`);
126
126
  return 1;
127
127
  }
128
128
  const { document } = (await res.json());
@@ -11,9 +11,9 @@ const hook_log_1 = require("../lib/hook-log");
11
11
  * if `runHook` someday throws (it currently never does), Claude Code is
12
12
  * not disrupted.
13
13
  */
14
- async function hookCommand(path) {
14
+ async function hookCommand(path, agentToken) {
15
15
  try {
16
- await (0, hook_runner_1.runHook)(path);
16
+ await (0, hook_runner_1.runHook)(path, agentToken);
17
17
  }
18
18
  catch (err) {
19
19
  (0, hook_log_1.logHookError)(`[${path}] dispatcher`, err);
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.memoryProjectAdd = memoryProjectAdd;
4
+ const config_1 = require("../lib/config");
5
+ const api_1 = require("../lib/api");
6
+ const sanitize_1 = require("../lib/sanitize");
7
+ const memory_content_1 = require("../lib/memory-content");
8
+ const resolve_1 = require("../lib/resolve");
9
+ const resolve_bound_task_1 = require("../lib/resolve-bound-task");
10
+ const resolve_project_1 = require("../lib/resolve-project");
11
+ const agent_1 = require("../lib/agent");
12
+ async function memoryProjectAdd(refArg, options) {
13
+ if (!options.category) {
14
+ console.error('Error: --category <trap|decision|convention|procedural> is required.');
15
+ return 1;
16
+ }
17
+ const built = (0, memory_content_1.buildMemoryContent)(options.category, options);
18
+ if (!built.ok) {
19
+ console.error(`Error: ${built.error}`);
20
+ return 1;
21
+ }
22
+ let agent;
23
+ const agentRaw = options.agent?.trim();
24
+ if (agentRaw) {
25
+ const normalized = (0, agent_1.normalizeAgent)(agentRaw);
26
+ if (!normalized) {
27
+ console.error(`Error: invalid --agent "${agentRaw}". Valid values: ${agent_1.VALID_AGENT_TOKENS.join(', ')}.`);
28
+ return 1;
29
+ }
30
+ agent = 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 apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
38
+ const base = (0, api_1.trimTrailingSlash)(apiUrl);
39
+ let projectId;
40
+ let echoSuffix = '';
41
+ if (refArg) {
42
+ projectId = await (0, resolve_1.resolveProjectId)(base, creds.token, refArg);
43
+ }
44
+ else {
45
+ const bound = await (0, resolve_bound_task_1.resolveBoundTaskIdentifier)(apiUrl, creds.token);
46
+ if (!bound) {
47
+ console.error('Error: no <project-ref> given and no task bound to this session.\nPass a project, or run `lumo session attach <LUM-N>`.');
48
+ return 1;
49
+ }
50
+ const r = await (0, resolve_project_1.resolveBoundProjectId)(apiUrl, creds.token, bound);
51
+ if (!r.ok) {
52
+ console.error(`Error: ${r.error}`);
53
+ return 1;
54
+ }
55
+ projectId = r.id;
56
+ echoSuffix = ` (project of bound task ${bound})`;
57
+ }
58
+ let res;
59
+ try {
60
+ res = await fetch(`${base}/api/projects/${encodeURIComponent(projectId)}/memories`, {
61
+ method: 'POST',
62
+ headers: { Authorization: `Bearer ${creds.token}`, 'Content-Type': 'application/json' },
63
+ body: JSON.stringify({
64
+ category: built.category,
65
+ content: built.content,
66
+ ...(agent ? { agent } : {}),
67
+ }),
68
+ });
69
+ }
70
+ catch (err) {
71
+ console.error(`Error: could not reach Lumo API at ${apiUrl} (${err instanceof Error ? err.message : String(err)})`);
72
+ return 1;
73
+ }
74
+ if (res.status !== 201) {
75
+ let m = null;
76
+ try {
77
+ const b = (await res.json());
78
+ if (typeof b.error === 'string')
79
+ m = b.error;
80
+ }
81
+ catch { /* */ }
82
+ console.error(m ? `Error: ${(0, sanitize_1.sanitizeField)(m)}` : `Error: memory add failed (HTTP ${res.status})`);
83
+ return 1;
84
+ }
85
+ process.stdout.write(`Added ${built.category} PROJECT memory${echoSuffix}\n`);
86
+ }
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.memoryProjectList = memoryProjectList;
4
+ const config_1 = require("../lib/config");
5
+ const api_1 = require("../lib/api");
6
+ const memory_content_1 = require("../lib/memory-content");
7
+ const resolve_1 = require("../lib/resolve");
8
+ const resolve_bound_task_1 = require("../lib/resolve-bound-task");
9
+ const resolve_project_1 = require("../lib/resolve-project");
10
+ async function memoryProjectList(refArg, options) {
11
+ const limit = options.limit !== undefined ? parseInt(options.limit, 10) : undefined;
12
+ if (limit !== undefined && (Number.isNaN(limit) || limit < 1)) {
13
+ console.error(`Error: invalid --limit "${options.limit}" (expected a positive integer)`);
14
+ return 1;
15
+ }
16
+ const creds = (0, config_1.readCredentials)();
17
+ if (!creds) {
18
+ console.error('Error: not logged in. Run `lumo auth login` first.');
19
+ return 1;
20
+ }
21
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
22
+ const base = (0, api_1.trimTrailingSlash)(apiUrl);
23
+ let projectId;
24
+ if (refArg) {
25
+ projectId = await (0, resolve_1.resolveProjectId)(base, creds.token, refArg);
26
+ }
27
+ else {
28
+ const bound = await (0, resolve_bound_task_1.resolveBoundTaskIdentifier)(apiUrl, creds.token);
29
+ if (!bound) {
30
+ console.error('Error: no <project-ref> given and no task bound to this session.\nPass a project, or run `lumo session attach <LUM-N>`.');
31
+ return 1;
32
+ }
33
+ const r = await (0, resolve_project_1.resolveBoundProjectId)(apiUrl, creds.token, bound);
34
+ if (!r.ok) {
35
+ console.error(`Error: ${r.error}`);
36
+ return 1;
37
+ }
38
+ projectId = r.id;
39
+ }
40
+ let res;
41
+ try {
42
+ res = await fetch(`${base}/api/projects/${encodeURIComponent(projectId)}/memories`, { headers: { Authorization: `Bearer ${creds.token}` } });
43
+ }
44
+ catch (err) {
45
+ console.error(`Error: could not reach Lumo API at ${apiUrl} (${err instanceof Error ? err.message : String(err)})`);
46
+ return 1;
47
+ }
48
+ if (!res.ok) {
49
+ console.error(`Error: memory list failed (HTTP ${res.status})`);
50
+ return 1;
51
+ }
52
+ let rows = (await res.json()).memories;
53
+ if (options.category)
54
+ rows = rows.filter(m => m.category.toLowerCase() === options.category.toLowerCase());
55
+ if (limit !== undefined)
56
+ rows = rows.slice(0, limit);
57
+ process.stdout.write((0, memory_content_1.formatMemoryList)(rows) + '\n');
58
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.memoryPromote = memoryPromote;
4
+ const config_1 = require("../lib/config");
5
+ const api_1 = require("../lib/api");
6
+ const sanitize_1 = require("../lib/sanitize");
7
+ async function memoryPromote(memoryId) {
8
+ if (!memoryId) {
9
+ console.error('Error: missing <memoryId>. Usage: lumo memory promote <memoryId>');
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 apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
18
+ const base = (0, api_1.trimTrailingSlash)(apiUrl);
19
+ let res;
20
+ try {
21
+ res = await fetch(`${base}/api/memories/${encodeURIComponent(memoryId)}`, {
22
+ method: 'PATCH',
23
+ headers: { Authorization: `Bearer ${creds.token}`, 'Content-Type': 'application/json' },
24
+ body: JSON.stringify({ scope: 'PROJECT' }),
25
+ });
26
+ }
27
+ catch (err) {
28
+ console.error(`Error: could not reach Lumo API at ${apiUrl} (${err instanceof Error ? err.message : String(err)})`);
29
+ return 1;
30
+ }
31
+ if (res.status === 404) {
32
+ console.error(`Error: memory ${memoryId} not found`);
33
+ return 1;
34
+ }
35
+ if (res.status === 409) {
36
+ console.error('Error: this memory is already project-scoped.');
37
+ return 1;
38
+ }
39
+ if (res.status !== 200) {
40
+ let m = null;
41
+ try {
42
+ const b = (await res.json());
43
+ if (typeof b.error === 'string')
44
+ m = b.error;
45
+ }
46
+ catch { /* */ }
47
+ console.error(m ? `Error: ${(0, sanitize_1.sanitizeField)(m)}` : `Error: promote failed (HTTP ${res.status})`);
48
+ return 1;
49
+ }
50
+ process.stdout.write(`Promoted ${memoryId} to PROJECT — every agent on this project now sees it.\n` +
51
+ 'Promote only lessons that recur across 2+ tasks.\n');
52
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.memoryRm = memoryRm;
4
+ const config_1 = require("../lib/config");
5
+ const api_1 = require("../lib/api");
6
+ async function memoryRm(memoryId, options) {
7
+ if (!memoryId) {
8
+ console.error('Error: missing <memoryId>. Usage: lumo memory rm <memoryId> --yes');
9
+ return 1;
10
+ }
11
+ if (!options.yes) {
12
+ console.error('Error: refusing to delete without --yes. Re-run with --yes to confirm.');
13
+ return 1;
14
+ }
15
+ const creds = (0, config_1.readCredentials)();
16
+ if (!creds) {
17
+ console.error('Error: not logged in. Run `lumo auth login` first.');
18
+ return 1;
19
+ }
20
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
21
+ const base = (0, api_1.trimTrailingSlash)(apiUrl);
22
+ let res;
23
+ try {
24
+ res = await fetch(`${base}/api/memories/${encodeURIComponent(memoryId)}`, {
25
+ method: 'DELETE',
26
+ headers: { Authorization: `Bearer ${creds.token}` },
27
+ });
28
+ }
29
+ catch (err) {
30
+ console.error(`Error: could not reach Lumo API at ${apiUrl} (${err instanceof Error ? err.message : String(err)})`);
31
+ return 1;
32
+ }
33
+ if (res.status === 404) {
34
+ console.error(`Error: memory ${memoryId} not found`);
35
+ return 1;
36
+ }
37
+ if (res.status !== 204) {
38
+ console.error(`Error: delete failed (HTTP ${res.status})`);
39
+ return 1;
40
+ }
41
+ process.stdout.write(`Deleted memory ${memoryId}\n`);
42
+ }