@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,8 +5,9 @@ exports.sprintDelete = sprintDelete;
5
5
  const config_1 = require("../lib/config");
6
6
  const api_1 = require("../lib/api");
7
7
  const resolve_1 = require("../lib/resolve");
8
+ const sanitize_1 = require("../lib/sanitize");
8
9
  function formatDeleteRefusal(number, name, taskCount) {
9
- const head = `Refusing to delete sprint #${number} "${name}" without --yes.`;
10
+ const head = `Refusing to delete sprint #${number} "${(0, sanitize_1.sanitizeField)(name)}" without --yes.`;
10
11
  const tail = `Re-run with --yes to confirm.`;
11
12
  if (taskCount === 0)
12
13
  return `${head}\n${tail}`;
@@ -18,8 +19,7 @@ async function sprintDelete(identifier, opts) {
18
19
  console.error('Error: not logged in. Run `lumo auth login` first.');
19
20
  return 1;
20
21
  }
21
- const envUrl = process.env.LUMO_API_URL?.trim();
22
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
22
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
23
23
  const base = (0, api_1.trimTrailingSlash)(apiUrl);
24
24
  const workspaceSlug = creds.workspaceSlug ?? '';
25
25
  let resolved;
@@ -81,8 +81,8 @@ async function sprintDelete(identifier, opts) {
81
81
  catch {
82
82
  // body wasn't JSON
83
83
  }
84
- console.error(`Error: ${errMsg}`);
84
+ console.error(`Error: ${(0, sanitize_1.sanitizeField)(errMsg)}`);
85
85
  return 1;
86
86
  }
87
- process.stdout.write(`Deleted sprint #${sprint.number} "${sprint.name}"\n`);
87
+ process.stdout.write(`Deleted sprint #${sprint.number} "${(0, sanitize_1.sanitizeField)(sprint.name)}"\n`);
88
88
  }
@@ -5,6 +5,7 @@ exports.sprintList = sprintList;
5
5
  const config_1 = require("../lib/config");
6
6
  const api_1 = require("../lib/api");
7
7
  const resolve_1 = require("../lib/resolve");
8
+ const sanitize_1 = require("../lib/sanitize");
8
9
  const VALID_STATUSES = ['DRAFT', 'ACTIVE', 'CLOSED'];
9
10
  function formatDate(iso) {
10
11
  if (!iso)
@@ -25,7 +26,7 @@ function formatSprintList(rows) {
25
26
  const statusCol = r.status.padEnd(statusW);
26
27
  const numCol = `#${r.number}`.padEnd(numW + 1); // +1 for the '#'
27
28
  const dateRange = `${formatDate(r.startDate)}..${formatDate(r.endDate)}`;
28
- return `${statusCol} ${numCol} ${dateRange} ${r.name}`;
29
+ return `${statusCol} ${numCol} ${dateRange} ${(0, sanitize_1.sanitizeField)(r.name)}`;
29
30
  })
30
31
  .join('\n');
31
32
  }
@@ -49,8 +50,7 @@ async function sprintList(options) {
49
50
  console.error('Error: not logged in. Run `lumo auth login` first.');
50
51
  return 1;
51
52
  }
52
- const envUrl = process.env.LUMO_API_URL?.trim();
53
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
53
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
54
54
  const base = (0, api_1.trimTrailingSlash)(apiUrl);
55
55
  let teamId;
56
56
  try {
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.sprintRemove = sprintRemove;
4
4
  const config_1 = require("../lib/config");
5
5
  const api_1 = require("../lib/api");
6
+ const sanitize_1 = require("../lib/sanitize");
6
7
  const resolve_1 = require("../lib/resolve");
7
8
  async function sprintRemove(identifier, taskIdentifier, opts) {
8
9
  const creds = (0, config_1.readCredentials)();
@@ -10,8 +11,7 @@ async function sprintRemove(identifier, taskIdentifier, opts) {
10
11
  console.error('Error: not logged in. Run `lumo auth login` first.');
11
12
  return 1;
12
13
  }
13
- const envUrl = process.env.LUMO_API_URL?.trim();
14
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
14
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
15
15
  const base = (0, api_1.trimTrailingSlash)(apiUrl);
16
16
  const workspaceSlug = creds.workspaceSlug ?? '';
17
17
  let resolved;
@@ -50,7 +50,7 @@ async function sprintRemove(identifier, taskIdentifier, opts) {
50
50
  catch {
51
51
  // ignore
52
52
  }
53
- console.error(`Error: ${errMsg}`);
53
+ console.error(`Error: ${(0, sanitize_1.sanitizeField)(errMsg)}`);
54
54
  return 1;
55
55
  }
56
56
  process.stdout.write(`Removed ${taskIdentifier} from sprint #${resolved.number}\n`);
@@ -6,14 +6,15 @@ const config_1 = require("../lib/config");
6
6
  const api_1 = require("../lib/api");
7
7
  const resolve_1 = require("../lib/resolve");
8
8
  const format_1 = require("../lib/format");
9
+ const sanitize_1 = require("../lib/sanitize");
9
10
  function fmtDate(iso) {
10
11
  return iso ? iso.slice(0, 10) : '-';
11
12
  }
12
13
  function formatSprintShow(s, progress, tasks) {
13
14
  const lines = [
14
- `Sprint: #${s.number} ${s.name}`,
15
+ `Sprint: #${s.number} ${(0, sanitize_1.sanitizeField)(s.name)}`,
15
16
  `Status: ${s.status}`,
16
- `Team: ${s.team.name}`,
17
+ `Team: ${(0, sanitize_1.sanitizeField)(s.team.name)}`,
17
18
  `Start: ${fmtDate(s.startDate)}`,
18
19
  `End: ${fmtDate(s.endDate)}`,
19
20
  `Started: ${fmtDate(s.startedAt)}`,
@@ -32,8 +33,7 @@ async function sprintShow(identifier, opts) {
32
33
  console.error('Error: not logged in. Run `lumo auth login` first.');
33
34
  return 1;
34
35
  }
35
- const envUrl = process.env.LUMO_API_URL?.trim();
36
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
36
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
37
37
  const base = (0, api_1.trimTrailingSlash)(apiUrl);
38
38
  let sprintId;
39
39
  try {
@@ -4,14 +4,14 @@ exports.sprintStart = sprintStart;
4
4
  const config_1 = require("../lib/config");
5
5
  const api_1 = require("../lib/api");
6
6
  const resolve_1 = require("../lib/resolve");
7
+ const sanitize_1 = require("../lib/sanitize");
7
8
  async function sprintStart(identifier, opts) {
8
9
  const creds = (0, config_1.readCredentials)();
9
10
  if (!creds) {
10
11
  console.error('Error: not logged in. Run `lumo auth login` first.');
11
12
  return 1;
12
13
  }
13
- const envUrl = process.env.LUMO_API_URL?.trim();
14
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
14
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
15
15
  const base = (0, api_1.trimTrailingSlash)(apiUrl);
16
16
  const workspaceSlug = creds.workspaceSlug ?? '';
17
17
  let resolved;
@@ -61,8 +61,8 @@ async function sprintStart(identifier, opts) {
61
61
  catch {
62
62
  // ignore
63
63
  }
64
- console.error(`Error: ${errMsg}`);
64
+ console.error(`Error: ${(0, sanitize_1.sanitizeField)(errMsg)}`);
65
65
  return 1;
66
66
  }
67
- process.stdout.write(`Started sprint #${displayNumber} "${displayName}"\n`);
67
+ process.stdout.write(`Started sprint #${displayNumber} "${(0, sanitize_1.sanitizeField)(displayName)}"\n`);
68
68
  }
@@ -5,9 +5,10 @@ exports.sprintSummary = sprintSummary;
5
5
  const config_1 = require("../lib/config");
6
6
  const api_1 = require("../lib/api");
7
7
  const resolve_1 = require("../lib/resolve");
8
+ const sanitize_1 = require("../lib/sanitize");
8
9
  function formatSprintSummary(input) {
9
10
  const { number, name, stats, tldr, report } = input;
10
- const lines = [`Sprint: #${number} ${name}`];
11
+ const lines = [`Sprint: #${number} ${(0, sanitize_1.sanitizeField)(name)}`];
11
12
  if (stats === null && tldr === null && report === null) {
12
13
  lines.push('', '(no summary generated yet)');
13
14
  return lines.join('\n');
@@ -16,10 +17,10 @@ function formatSprintSummary(input) {
16
17
  lines.push(`Done: ${stats.done} / ${stats.total}`);
17
18
  }
18
19
  if (tldr) {
19
- lines.push('', tldr);
20
+ lines.push('', (0, sanitize_1.sanitizeField)(tldr));
20
21
  }
21
22
  if (report) {
22
- lines.push('', report);
23
+ lines.push('', (0, sanitize_1.sanitizeField)(report));
23
24
  }
24
25
  return lines.join('\n');
25
26
  }
@@ -29,8 +30,7 @@ async function sprintSummary(identifier, opts) {
29
30
  console.error('Error: not logged in. Run `lumo auth login` first.');
30
31
  return 1;
31
32
  }
32
- const envUrl = process.env.LUMO_API_URL?.trim();
33
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
33
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
34
34
  const base = (0, api_1.trimTrailingSlash)(apiUrl);
35
35
  const workspaceSlug = creds.workspaceSlug ?? '';
36
36
  let resolved;
@@ -82,7 +82,7 @@ async function sprintSummary(identifier, opts) {
82
82
  catch {
83
83
  // ignore
84
84
  }
85
- console.error(`Error: ${errMsg}`);
85
+ console.error(`Error: ${(0, sanitize_1.sanitizeField)(errMsg)}`);
86
86
  return 1;
87
87
  }
88
88
  // 202 means queued; regeneration is async. The GET below may still return
@@ -124,7 +124,7 @@ async function sprintSummary(identifier, opts) {
124
124
  catch {
125
125
  // ignore
126
126
  }
127
- console.error(`Error: ${errMsg}`);
127
+ console.error(`Error: ${(0, sanitize_1.sanitizeField)(errMsg)}`);
128
128
  return 1;
129
129
  }
130
130
  // 404 → fall through with all-null; formatSprintSummary renders the friendly marker.
@@ -6,6 +6,7 @@ exports.sprintUpdate = sprintUpdate;
6
6
  const config_1 = require("../lib/config");
7
7
  const api_1 = require("../lib/api");
8
8
  const resolve_1 = require("../lib/resolve");
9
+ const sanitize_1 = require("../lib/sanitize");
9
10
  function buildSprintUpdatePayload(opts) {
10
11
  const payload = {};
11
12
  const flagsGiven = [];
@@ -29,9 +30,10 @@ function fmtDate(v) {
29
30
  return v.slice(0, 10);
30
31
  }
31
32
  function formatSprintUpdateSummary(before, after) {
33
+ const beforeName = (0, sanitize_1.sanitizeField)(before.name);
32
34
  const changes = [];
33
35
  if (after.name !== undefined && after.name !== before.name) {
34
- changes.push(`name "${before.name}" → "${after.name}"`);
36
+ changes.push(`name "${beforeName}" → "${(0, sanitize_1.sanitizeField)(after.name)}"`);
35
37
  }
36
38
  if (after.startDate !== undefined) {
37
39
  changes.push(`start → ${fmtDate(after.startDate)}`);
@@ -39,7 +41,7 @@ function formatSprintUpdateSummary(before, after) {
39
41
  if (after.endDate !== undefined) {
40
42
  changes.push(`end → ${fmtDate(after.endDate)}`);
41
43
  }
42
- return `Updated sprint #${before.number} "${before.name}": ${changes.join(', ')}`;
44
+ return `Updated sprint #${before.number} "${beforeName}": ${changes.join(', ')}`;
43
45
  }
44
46
  async function sprintUpdate(identifier, opts) {
45
47
  // Reject empty strings — these fields can't be cleared.
@@ -65,8 +67,7 @@ async function sprintUpdate(identifier, opts) {
65
67
  console.error('Error: not logged in. Run `lumo auth login` first.');
66
68
  return 1;
67
69
  }
68
- const envUrl = process.env.LUMO_API_URL?.trim();
69
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
70
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
70
71
  const base = (0, api_1.trimTrailingSlash)(apiUrl);
71
72
  let sprintId;
72
73
  let resolvedNumber;
@@ -141,7 +142,7 @@ async function sprintUpdate(identifier, opts) {
141
142
  catch {
142
143
  // body wasn't JSON; keep the status-only message
143
144
  }
144
- console.error(`Error: ${errMsg}`);
145
+ console.error(`Error: ${(0, sanitize_1.sanitizeField)(errMsg)}`);
145
146
  return 1;
146
147
  }
147
148
  process.stdout.write(formatSprintUpdateSummary(beforeSnapshot, payload) + '\n');
@@ -4,6 +4,9 @@ exports.taskArtifactAdd = taskArtifactAdd;
4
4
  const config_1 = require("../lib/config");
5
5
  const api_1 = require("../lib/api");
6
6
  const doc_input_1 = require("../lib/doc-input");
7
+ const agent_1 = require("../lib/agent");
8
+ const sanitize_1 = require("../lib/sanitize");
9
+ const path_guard_1 = require("../lib/path-guard");
7
10
  async function taskArtifactAdd(identifier, options) {
8
11
  if (!identifier) {
9
12
  console.error('Error: missing <task>. Usage: lumo task artifact add <LUM-42> --kind spec --title "Spec" --file spec.md --source Superpowers');
@@ -24,13 +27,34 @@ async function taskArtifactAdd(identifier, options) {
24
27
  console.error('Error: --source is required (the spec-gen framework, formal name e.g. Superpowers, "Spec Kit", BMad, OpenSpec, GSD).');
25
28
  return 1;
26
29
  }
30
+ const agentRaw = options.agent?.trim();
31
+ if (!agentRaw) {
32
+ console.error(`Error: --agent is required. Valid values: ${agent_1.VALID_AGENT_TOKENS.join(', ')}.`);
33
+ return 1;
34
+ }
35
+ const agent = (0, agent_1.normalizeAgent)(agentRaw);
36
+ if (!agent) {
37
+ console.error(`Error: invalid --agent "${agentRaw}". Valid values: ${agent_1.VALID_AGENT_TOKENS.join(', ')}.`);
38
+ return 1;
39
+ }
27
40
  if (!options.file) {
28
41
  console.error('Error: --file <path> is required.');
29
42
  return 1;
30
43
  }
44
+ const verdict = (0, path_guard_1.checkArtifactFilePath)(options.file);
45
+ if (!verdict.ok) {
46
+ if (verdict.reason === 'unreadable') {
47
+ console.error(`Error: could not read file ${options.file}`);
48
+ }
49
+ else {
50
+ console.error(`Error: refusing to read ${options.file} — ${verdict.detail}. ` +
51
+ `artifact --file must be a non-sensitive path inside the project directory.`);
52
+ }
53
+ return 1;
54
+ }
31
55
  let content;
32
56
  try {
33
- content = await (0, doc_input_1.readFileUtf8)(options.file);
57
+ content = await (0, doc_input_1.readFileUtf8)(verdict.resolved);
34
58
  }
35
59
  catch {
36
60
  console.error(`Error: could not read file ${options.file}`);
@@ -45,10 +69,15 @@ async function taskArtifactAdd(identifier, options) {
45
69
  console.error('Error: not logged in. Run `lumo auth login` first.');
46
70
  return 1;
47
71
  }
48
- const envUrl = process.env.LUMO_API_URL?.trim();
49
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
72
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
50
73
  const base = (0, api_1.trimTrailingSlash)(apiUrl);
51
- const payload = { kind, title, content, source };
74
+ const payload = {
75
+ kind,
76
+ title,
77
+ content,
78
+ source,
79
+ agent,
80
+ };
52
81
  let res;
53
82
  try {
54
83
  res = await fetch(`${base}/api/tasks/${encodeURIComponent(identifier)}/artifacts`, {
@@ -84,10 +113,10 @@ async function taskArtifactAdd(identifier, options) {
84
113
  /* not JSON */
85
114
  }
86
115
  console.error(serverMsg
87
- ? `Error: ${serverMsg}`
116
+ ? `Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`
88
117
  : `Error: artifact add failed (HTTP ${res.status})`);
89
118
  return 1;
90
119
  }
91
120
  const data = (await res.json());
92
- process.stdout.write(`Added [${data.artifact.kind}] "${data.artifact.title}" to ${identifier}\n`);
121
+ process.stdout.write(`Added [${(0, sanitize_1.sanitizeField)(data.artifact.kind)}] "${(0, sanitize_1.sanitizeField)(data.artifact.title)}" to ${identifier}\n`);
93
122
  }
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.taskArtifactList = taskArtifactList;
4
4
  const config_1 = require("../lib/config");
5
5
  const api_1 = require("../lib/api");
6
+ const agent_1 = require("../lib/agent");
7
+ const sanitize_1 = require("../lib/sanitize");
6
8
  async function taskArtifactList(identifier) {
7
9
  if (!identifier) {
8
10
  console.error('Error: missing <task>. Usage: lumo task artifact list <LUM-42>');
@@ -13,8 +15,7 @@ async function taskArtifactList(identifier) {
13
15
  console.error('Error: not logged in. Run `lumo auth login` first.');
14
16
  return 1;
15
17
  }
16
- const envUrl = process.env.LUMO_API_URL?.trim();
17
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
18
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
18
19
  const base = (0, api_1.trimTrailingSlash)(apiUrl);
19
20
  let res;
20
21
  try {
@@ -43,6 +44,7 @@ async function taskArtifactList(identifier) {
43
44
  return;
44
45
  }
45
46
  for (const a of artifacts) {
46
- process.stdout.write(`${a.id} ${a.kind.padEnd(10)} ${a.source.padEnd(12)} ${a.title}\n`);
47
+ const agentLabel = agent_1.AGENT_LABELS[a.agent] ?? (0, sanitize_1.sanitizeField)(a.agent);
48
+ process.stdout.write(`${a.id} ${(0, sanitize_1.sanitizeField)(a.kind).padEnd(10)} ${agentLabel.padEnd(15)} ${(0, sanitize_1.sanitizeField)(a.source).padEnd(12)} ${(0, sanitize_1.sanitizeField)(a.title)}\n`);
47
49
  }
48
50
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.taskArtifactRm = taskArtifactRm;
4
4
  const config_1 = require("../lib/config");
5
5
  const api_1 = require("../lib/api");
6
+ const sanitize_1 = require("../lib/sanitize");
6
7
  async function taskArtifactRm(identifier, artifactId, options) {
7
8
  if (!identifier) {
8
9
  console.error('Error: missing <task>. Usage: lumo task artifact rm <LUM-42> <artifact-id> --yes');
@@ -21,8 +22,7 @@ async function taskArtifactRm(identifier, artifactId, options) {
21
22
  console.error('Error: not logged in. Run `lumo auth login` first.');
22
23
  return 1;
23
24
  }
24
- const envUrl = process.env.LUMO_API_URL?.trim();
25
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
25
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
26
26
  const base = (0, api_1.trimTrailingSlash)(apiUrl);
27
27
  let res;
28
28
  try {
@@ -50,7 +50,7 @@ async function taskArtifactRm(identifier, artifactId, options) {
50
50
  catch {
51
51
  /* not JSON */
52
52
  }
53
- console.error(`Error: ${serverMsg}`);
53
+ console.error(`Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`);
54
54
  return 1;
55
55
  }
56
56
  if (res.status !== 204) {
@@ -64,7 +64,7 @@ async function taskArtifactRm(identifier, artifactId, options) {
64
64
  /* not JSON */
65
65
  }
66
66
  console.error(serverMsg
67
- ? `Error: ${serverMsg}`
67
+ ? `Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`
68
68
  : `Error: artifact rm failed (HTTP ${res.status})`);
69
69
  return 1;
70
70
  }
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.taskArtifactShow = taskArtifactShow;
4
4
  const config_1 = require("../lib/config");
5
5
  const api_1 = require("../lib/api");
6
+ const agent_1 = require("../lib/agent");
7
+ const sanitize_1 = require("../lib/sanitize");
6
8
  async function taskArtifactShow(identifier, artifactId) {
7
9
  if (!identifier) {
8
10
  console.error('Error: missing <task>. Usage: lumo task artifact show <LUM-42> <artifact-id>');
@@ -17,8 +19,7 @@ async function taskArtifactShow(identifier, artifactId) {
17
19
  console.error('Error: not logged in. Run `lumo auth login` first.');
18
20
  return 1;
19
21
  }
20
- const envUrl = process.env.LUMO_API_URL?.trim();
21
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
22
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
22
23
  const base = (0, api_1.trimTrailingSlash)(apiUrl);
23
24
  let res;
24
25
  try {
@@ -43,7 +44,7 @@ async function taskArtifactShow(identifier, artifactId) {
43
44
  catch {
44
45
  /* not JSON */
45
46
  }
46
- console.error(`Error: ${serverMsg}`);
47
+ console.error(`Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`);
47
48
  return 1;
48
49
  }
49
50
  if (!res.ok) {
@@ -52,10 +53,11 @@ async function taskArtifactShow(identifier, artifactId) {
52
53
  }
53
54
  const { artifact } = (await res.json());
54
55
  process.stdout.write(`id: ${artifact.id}\n`);
55
- process.stdout.write(`kind: ${artifact.kind}\n`);
56
- process.stdout.write(`title: ${artifact.title}\n`);
57
- process.stdout.write(`source: ${artifact.source}\n`);
56
+ process.stdout.write(`kind: ${(0, sanitize_1.sanitizeField)(artifact.kind)}\n`);
57
+ process.stdout.write(`title: ${(0, sanitize_1.sanitizeField)(artifact.title)}\n`);
58
+ process.stdout.write(`source: ${(0, sanitize_1.sanitizeField)(artifact.source)}\n`);
59
+ process.stdout.write(`agent: ${agent_1.AGENT_LABELS[artifact.agent] ?? (0, sanitize_1.sanitizeField)(artifact.agent ?? '')}\n`);
58
60
  process.stdout.write(`order: ${artifact.order}\n`);
59
61
  process.stdout.write(`task: ${identifier}\n`);
60
- process.stdout.write(`\n${artifact.content}\n`);
62
+ process.stdout.write(`\n${(0, sanitize_1.sanitizeField)(artifact.content)}\n`);
61
63
  }
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.taskArtifactUpdate = taskArtifactUpdate;
4
4
  const config_1 = require("../lib/config");
5
5
  const api_1 = require("../lib/api");
6
+ const sanitize_1 = require("../lib/sanitize");
7
+ const agent_1 = require("../lib/agent");
6
8
  async function taskArtifactUpdate(identifier, artifactId, options) {
7
9
  if (!identifier) {
8
10
  console.error('Error: missing <task>. Usage: lumo task artifact update <LUM-42> <artifact-id> --kind plan');
@@ -37,8 +39,16 @@ async function taskArtifactUpdate(identifier, artifactId, options) {
37
39
  }
38
40
  payload.source = source;
39
41
  }
42
+ if (options.agent !== undefined) {
43
+ const agent = (0, agent_1.normalizeAgent)(options.agent);
44
+ if (!agent) {
45
+ console.error(`Error: invalid --agent "${options.agent}". Valid values: ${agent_1.VALID_AGENT_TOKENS.join(', ')}.`);
46
+ return 1;
47
+ }
48
+ payload.agent = agent;
49
+ }
40
50
  if (Object.keys(payload).length === 0) {
41
- console.error('Error: provide at least one of --kind, --title, or --source to update.');
51
+ console.error('Error: provide at least one of --kind, --title, --source, or --agent to update.');
42
52
  return 1;
43
53
  }
44
54
  const creds = (0, config_1.readCredentials)();
@@ -46,8 +56,7 @@ async function taskArtifactUpdate(identifier, artifactId, options) {
46
56
  console.error('Error: not logged in. Run `lumo auth login` first.');
47
57
  return 1;
48
58
  }
49
- const envUrl = process.env.LUMO_API_URL?.trim();
50
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
59
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
51
60
  const base = (0, api_1.trimTrailingSlash)(apiUrl);
52
61
  let res;
53
62
  try {
@@ -79,7 +88,7 @@ async function taskArtifactUpdate(identifier, artifactId, options) {
79
88
  catch {
80
89
  /* not JSON */
81
90
  }
82
- console.error(`Error: ${serverMsg}`);
91
+ console.error(`Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`);
83
92
  return 1;
84
93
  }
85
94
  if (res.status !== 200) {
@@ -93,10 +102,10 @@ async function taskArtifactUpdate(identifier, artifactId, options) {
93
102
  /* not JSON */
94
103
  }
95
104
  console.error(serverMsg
96
- ? `Error: ${serverMsg}`
105
+ ? `Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`
97
106
  : `Error: artifact update failed (HTTP ${res.status})`);
98
107
  return 1;
99
108
  }
100
109
  const data = (await res.json());
101
- process.stdout.write(`Updated [${data.artifact.kind}] "${data.artifact.title}" (${data.artifact.source}) on ${identifier}\n`);
110
+ process.stdout.write(`Updated [${(0, sanitize_1.sanitizeField)(data.artifact.kind)}] "${(0, sanitize_1.sanitizeField)(data.artifact.title)}" (${(0, sanitize_1.sanitizeField)(data.artifact.source)}) on ${identifier}\n`);
102
111
  }
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.taskCommentList = taskCommentList;
4
+ const config_1 = require("../lib/config");
5
+ const api_1 = require("../lib/api");
6
+ const sanitize_1 = require("../lib/sanitize");
7
+ /**
8
+ * Strip TipTap/HTML markup to readable plain text. Comment bodies are stored as
9
+ * HTML; the CLI only needs the visible text. We collapse block tags to newlines
10
+ * and drop the rest, then decode the handful of entities that survive.
11
+ */
12
+ function htmlToPlainText(html) {
13
+ return html
14
+ .replace(/<\/(p|div|li|h[1-6]|blockquote)>/gi, '\n')
15
+ .replace(/<br\s*\/?>/gi, '\n')
16
+ .replace(/<[^>]+>/g, '')
17
+ .replace(/&nbsp;/g, ' ')
18
+ .replace(/&amp;/g, '&')
19
+ .replace(/&lt;/g, '<')
20
+ .replace(/&gt;/g, '>')
21
+ .replace(/&quot;/g, '"')
22
+ .replace(/&#39;/g, "'")
23
+ .replace(/\n{3,}/g, '\n\n')
24
+ .trim();
25
+ }
26
+ function printComment(c, indent) {
27
+ const author = (0, sanitize_1.sanitizeField)(c.authorActorId ?? 'unknown');
28
+ console.log(`${indent}${author} · ${c.createdAt}`);
29
+ const text = (0, sanitize_1.sanitizeField)(htmlToPlainText(c.body));
30
+ for (const line of (text || '(empty)').split('\n')) {
31
+ console.log(`${indent}${line}`);
32
+ }
33
+ console.log('');
34
+ for (const reply of c.replies ?? []) {
35
+ printComment(reply, indent + ' ');
36
+ }
37
+ }
38
+ /**
39
+ * `lumo task comments list <LUM-N>`
40
+ *
41
+ * Tier-2 retrieval for the task comment thread. Resolves the LUM-N identifier to
42
+ * its DB id (the comments endpoint is keyed by DB id), then fetches the full
43
+ * thread from `/api/tasks/:id/comments` and prints each top-level comment
44
+ * (replies indented) as `author · time` followed by the plain-text body.
45
+ */
46
+ async function taskCommentList(identifier) {
47
+ if (!identifier) {
48
+ console.error('Error: usage: lumo task comments list <LUM-42>');
49
+ return 1;
50
+ }
51
+ const creds = (0, config_1.readCredentials)();
52
+ if (!creds) {
53
+ console.error('Error: not logged in. Run `lumo auth login` first.');
54
+ return 1;
55
+ }
56
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
57
+ const base = (0, api_1.trimTrailingSlash)(apiUrl);
58
+ // 1. Resolve LUM-N → DB id (the comments endpoint takes the DB id).
59
+ let resolveRes;
60
+ try {
61
+ resolveRes = await fetch(`${base}/api/tasks/resolve/${encodeURIComponent(identifier)}`, { headers: { Authorization: `Bearer ${creds.token}` } });
62
+ }
63
+ catch (err) {
64
+ const msg = err instanceof Error ? err.message : String(err);
65
+ console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
66
+ return 1;
67
+ }
68
+ if (resolveRes.status === 401) {
69
+ console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
70
+ return 1;
71
+ }
72
+ if (resolveRes.status === 404) {
73
+ console.error(`Error: task ${identifier} not found in workspace ${creds.workspaceSlug}`);
74
+ return 1;
75
+ }
76
+ if (!resolveRes.ok) {
77
+ console.error(`Error: resolve failed (HTTP ${resolveRes.status})`);
78
+ return 1;
79
+ }
80
+ const resolved = (await resolveRes.json());
81
+ // 2. Fetch the comment thread.
82
+ let res;
83
+ try {
84
+ res = await fetch(`${base}/api/tasks/${encodeURIComponent(resolved.id)}/comments`, { headers: { Authorization: `Bearer ${creds.token}` } });
85
+ }
86
+ catch (err) {
87
+ const msg = err instanceof Error ? err.message : String(err);
88
+ console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
89
+ return 1;
90
+ }
91
+ if (res.status === 401) {
92
+ console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
93
+ return 1;
94
+ }
95
+ if (res.status === 404) {
96
+ console.error(`Error: task ${identifier} not found`);
97
+ return 1;
98
+ }
99
+ if (!res.ok) {
100
+ console.error(`Error: comments list failed (HTTP ${res.status})`);
101
+ return 1;
102
+ }
103
+ const { comments } = (await res.json());
104
+ if (!comments || comments.length === 0) {
105
+ console.log('(no comments)');
106
+ return;
107
+ }
108
+ for (const c of comments) {
109
+ printComment(c, '');
110
+ }
111
+ }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.taskComment = taskComment;
4
4
  const config_1 = require("../lib/config");
5
5
  const api_1 = require("../lib/api");
6
+ const sanitize_1 = require("../lib/sanitize");
6
7
  /**
7
8
  * `lumo task comment <LUM-N> <body>`
8
9
  *
@@ -28,8 +29,7 @@ async function taskComment(identifier, body) {
28
29
  console.error('Error: not logged in. Run `lumo auth login` first.');
29
30
  return 1;
30
31
  }
31
- const envUrl = process.env.LUMO_API_URL?.trim();
32
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
32
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
33
33
  const base = (0, api_1.trimTrailingSlash)(apiUrl);
34
34
  // 1. Resolve LUM-N → DB id (the comments endpoint takes DB id).
35
35
  let resolveRes;
@@ -86,7 +86,7 @@ async function taskComment(identifier, body) {
86
86
  // Body wasn't JSON
87
87
  }
88
88
  console.error(serverMsg
89
- ? `Error: ${serverMsg}`
89
+ ? `Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`
90
90
  : `Error: comment create failed (HTTP ${postRes.status})`);
91
91
  return 1;
92
92
  }