@lumoai/cli 1.5.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 (81) hide show
  1. package/assets/skill.md +159 -15
  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 +19 -4
  19. package/dist/cli/src/commands/memory-project-list.js +1 -2
  20. package/dist/cli/src/commands/memory-promote.js +3 -3
  21. package/dist/cli/src/commands/memory-rm.js +1 -2
  22. package/dist/cli/src/commands/memory-task-add.js +19 -4
  23. package/dist/cli/src/commands/memory-task-list.js +1 -2
  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 +17 -5
  45. package/dist/cli/src/commands/task-artifact-list.js +4 -4
  46. package/dist/cli/src/commands/task-artifact-rm.js +4 -4
  47. package/dist/cli/src/commands/task-artifact-show.js +8 -8
  48. package/dist/cli/src/commands/task-artifact-update.js +5 -5
  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 +167 -102
  66. package/dist/cli/src/lib/agent.js +10 -1
  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 +4 -3
  75. package/dist/cli/src/lib/path-guard.js +125 -0
  76. package/dist/cli/src/lib/resolve-doc-id.js +2 -1
  77. package/dist/cli/src/lib/resolve-member.js +2 -1
  78. package/dist/cli/src/lib/sanitize.js +17 -0
  79. package/dist/cli/src/lib/tag-resolver.js +2 -1
  80. package/dist/cli/src/lib/update-check.js +2 -2
  81. package/package.json +1 -1
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.sessionAttach = sessionAttach;
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 session attach <identifier>` — bind the currently-running
8
9
  * Claude Code session to a task.
@@ -30,8 +31,7 @@ async function sessionAttach(identifier) {
30
31
  console.error('Error: not logged in. Run `lumo auth login` first.');
31
32
  return 1;
32
33
  }
33
- const envUrl = process.env.LUMO_API_URL?.trim();
34
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
34
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
35
35
  const url = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/sessions/${encodeURIComponent(sessionId)}/bind-task`;
36
36
  let res;
37
37
  try {
@@ -63,7 +63,7 @@ async function sessionAttach(identifier) {
63
63
  catch {
64
64
  // fall through
65
65
  }
66
- console.error(`Error: ${message}`);
66
+ console.error(`Error: ${(0, sanitize_1.sanitizeField)(message)}`);
67
67
  return 1;
68
68
  }
69
69
  if (!res.ok) {
@@ -71,10 +71,10 @@ async function sessionAttach(identifier) {
71
71
  return 1;
72
72
  }
73
73
  const result = (await res.json());
74
- console.log(`Attached session ${sessionId} to ${result.taskIdentifier} "${result.taskTitle}"`);
74
+ console.log(`Attached session ${sessionId} to ${result.taskIdentifier} "${(0, sanitize_1.sanitizeField)(result.taskTitle)}"`);
75
75
  console.log(`Re-tagged ${result.retaggedEventCount} previously-untagged event${result.retaggedEventCount === 1 ? '' : 's'} in this session.`);
76
76
  if (result.memorySection !== '') {
77
77
  console.log('');
78
- console.log(result.memorySection);
78
+ console.log((0, sanitize_1.sanitizeField)(result.memorySection));
79
79
  }
80
80
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.sessionDetach = sessionDetach;
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 session detach` — clear the task binding on the current Claude Code
8
9
  * session. Idempotent: re-detaching an already-unbound session reports
@@ -24,8 +25,7 @@ async function sessionDetach() {
24
25
  console.error('Error: not logged in. Run `lumo auth login` first.');
25
26
  return 1;
26
27
  }
27
- const envUrl = process.env.LUMO_API_URL?.trim();
28
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
28
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
29
29
  const url = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/sessions/${encodeURIComponent(sessionId)}/bind-task`;
30
30
  let res;
31
31
  try {
@@ -56,5 +56,5 @@ async function sessionDetach() {
56
56
  process.stdout.write(`Session ${sessionId} was already unbound.\n`);
57
57
  return;
58
58
  }
59
- process.stdout.write(`Detached session ${sessionId} from ${data.previousTaskIdentifier} "${data.previousTaskTitle}".\n`);
59
+ process.stdout.write(`Detached session ${sessionId} from ${data.previousTaskIdentifier} "${(0, sanitize_1.sanitizeField)(data.previousTaskTitle ?? '')}".\n`);
60
60
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.sessionStatus = sessionStatus;
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 sessionStatus() {
7
8
  const sessionId = process.env.CLAUDE_CODE_SESSION_ID;
8
9
  if (!sessionId) {
@@ -15,8 +16,7 @@ async function sessionStatus() {
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 url = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/sessions/${encodeURIComponent(sessionId)}`;
21
21
  let res;
22
22
  try {
@@ -47,7 +47,7 @@ async function sessionStatus() {
47
47
  const data = (await res.json());
48
48
  if (data.taskIdentifier && data.taskTitle) {
49
49
  process.stdout.write(`Session ${sessionId}\n` +
50
- ` Bound to: ${data.taskIdentifier} "${data.taskTitle}"\n` +
50
+ ` Bound to: ${data.taskIdentifier} "${(0, sanitize_1.sanitizeField)(data.taskTitle)}"\n` +
51
51
  ` Events: ${data.eventCount}\n`);
52
52
  }
53
53
  else {
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.resolveAgentToken = resolveAgentToken;
36
37
  exports.setup = setup;
37
38
  const fs = __importStar(require("fs"));
38
39
  const os = __importStar(require("os"));
@@ -40,11 +41,35 @@ const path = __importStar(require("path"));
40
41
  const child_process_1 = require("child_process");
41
42
  const hooks_template_1 = require("../lib/hooks-template");
42
43
  const line_prompt_1 = require("../lib/line-prompt");
44
+ const agent_1 = require("../lib/agent");
45
+ const config_1 = require("../lib/config");
46
+ /**
47
+ * Validate the `--agent` flag and canonicalize it to the CLI token baked into
48
+ * the hook commands. Defaults to `claude-code` — accurate today, since only
49
+ * Claude Code emits hook events. Returns `{ error }` for an unrecognized
50
+ * token so the caller can abort before writing settings.json.
51
+ */
52
+ function resolveAgentToken(raw) {
53
+ if (raw === undefined)
54
+ return { token: 'claude-code' };
55
+ const enumValue = (0, agent_1.normalizeAgent)(raw);
56
+ if (!enumValue) {
57
+ return {
58
+ error: `Unknown agent "${raw}". Valid agents: ${agent_1.VALID_AGENT_TOKENS.join(', ')}.`,
59
+ };
60
+ }
61
+ return { token: agent_1.ENUM_TO_TOKEN[enumValue] };
62
+ }
43
63
  async function setup(options) {
44
64
  if (options.user && options.project) {
45
65
  process.stderr.write('Error: --user and --project are mutually exclusive.\n');
46
66
  return 1;
47
67
  }
68
+ const agentResult = resolveAgentToken(options.agent);
69
+ if ('error' in agentResult) {
70
+ process.stderr.write(`Error: ${agentResult.error}\n`);
71
+ return 1;
72
+ }
48
73
  const scope = await resolveScope(options);
49
74
  if (!scope)
50
75
  return 130;
@@ -52,7 +77,7 @@ async function setup(options) {
52
77
  const claudeDir = path.join(root, '.claude');
53
78
  process.stdout.write(`\nInstalling Lumo for ${scope} scope: ${claudeDir}\n\n`);
54
79
  installSkill(claudeDir, options.force === true);
55
- mergeSettings(claudeDir);
80
+ mergeSettings(claudeDir, agentResult.token);
56
81
  printPostInstall();
57
82
  return 0;
58
83
  }
@@ -101,7 +126,7 @@ function installSkill(claudeDir, force) {
101
126
  fs.copyFileSync(skillSrc, skillDst);
102
127
  process.stdout.write(`✓ wrote skill: ${skillDst}\n`);
103
128
  }
104
- function mergeSettings(claudeDir) {
129
+ function mergeSettings(claudeDir, agentToken) {
105
130
  const settingsPath = path.join(claudeDir, 'settings.json');
106
131
  let existing = {};
107
132
  if (fs.existsSync(settingsPath)) {
@@ -116,18 +141,19 @@ function mergeSettings(claudeDir) {
116
141
  else {
117
142
  fs.mkdirSync(claudeDir, { recursive: true });
118
143
  }
119
- const { merged, stats } = (0, hooks_template_1.mergeLumoHooks)(existing);
144
+ const { merged, stats } = (0, hooks_template_1.mergeLumoHooks)(existing, agentToken);
120
145
  fs.writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + '\n');
121
- if (stats.addedEvents.length === 0) {
122
- process.stdout.write(`✓ hooks already wired in: ${settingsPath} (${stats.alreadyPresent.length} events)\n`);
146
+ const changed = stats.addedEvents.length + stats.updatedEvents.length;
147
+ if (changed === 0) {
148
+ process.stdout.write(`✓ hooks already wired in (agent=${agentToken}): ${settingsPath} (${stats.alreadyPresent.length} events)\n`);
123
149
  }
124
150
  else {
125
- process.stdout.write(`✓ merged hooks into ${settingsPath} (added ${stats.addedEvents.length}, kept ${stats.alreadyPresent.length})\n`);
151
+ process.stdout.write(`✓ merged hooks into ${settingsPath} (agent=${agentToken}; added ${stats.addedEvents.length}, updated ${stats.updatedEvents.length}, kept ${stats.alreadyPresent.length})\n`);
126
152
  }
127
153
  }
128
154
  function printPostInstall() {
129
155
  const onPath = isLumoOnPath();
130
- const credsPath = path.join(process.env.LUMO_CONFIG_DIR || path.join(os.homedir(), '.lumo'), 'credentials.json');
156
+ const credsPath = path.join((0, config_1.configDir)(), 'credentials.json');
131
157
  const authed = fs.existsSync(credsPath);
132
158
  process.stdout.write('\nNext steps:\n');
133
159
  if (!onPath || isRunningUnderNpx()) {
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.sprintAdd = sprintAdd;
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 sprintAdd(identifier, taskIdentifier, opts) {
8
9
  const creds = (0, config_1.readCredentials)();
@@ -10,8 +11,7 @@ async function sprintAdd(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;
@@ -55,7 +55,7 @@ async function sprintAdd(identifier, taskIdentifier, opts) {
55
55
  catch {
56
56
  // ignore
57
57
  }
58
- console.error(`Error: ${errMsg}`);
58
+ console.error(`Error: ${(0, sanitize_1.sanitizeField)(errMsg)}`);
59
59
  return 1;
60
60
  }
61
61
  process.stdout.write(`Added ${taskIdentifier} to sprint #${resolved.number}\n`);
@@ -7,6 +7,7 @@ const config_1 = require("../lib/config");
7
7
  const api_1 = require("../lib/api");
8
8
  const resolve_1 = require("../lib/resolve");
9
9
  const format_1 = require("../lib/format");
10
+ const sanitize_1 = require("../lib/sanitize");
10
11
  /**
11
12
  * Build the decisions map for POST /api/sprints/<id>/close.
12
13
  * Only non-DONE tasks get a decision entry; DONE tasks are excluded entirely.
@@ -28,7 +29,7 @@ function buildCloseDecisions(tasks, mode) {
28
29
  function formatUnfinishedRefusal(number, name, tasks) {
29
30
  const unfinished = tasks.filter(t => t.status !== 'DONE');
30
31
  const lines = [
31
- `Refusing to close sprint #${number} "${name}": ${unfinished.length} task(s) not DONE.`,
32
+ `Refusing to close sprint #${number} "${(0, sanitize_1.sanitizeField)(name)}": ${unfinished.length} task(s) not DONE.`,
32
33
  '',
33
34
  (0, format_1.formatTaskListTable)(unfinished),
34
35
  '',
@@ -56,8 +57,7 @@ async function sprintClose(identifier, opts) {
56
57
  console.error('Error: not logged in. Run `lumo auth login` first.');
57
58
  return 1;
58
59
  }
59
- const envUrl = process.env.LUMO_API_URL?.trim();
60
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
60
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
61
61
  const base = (0, api_1.trimTrailingSlash)(apiUrl);
62
62
  const workspaceSlug = creds.workspaceSlug ?? '';
63
63
  let resolved;
@@ -144,8 +144,8 @@ async function sprintClose(identifier, opts) {
144
144
  catch {
145
145
  // body wasn't JSON
146
146
  }
147
- console.error(`Error: ${errMsg}`);
147
+ console.error(`Error: ${(0, sanitize_1.sanitizeField)(errMsg)}`);
148
148
  return 1;
149
149
  }
150
- process.stdout.write(`Closed sprint #${sprint.number} "${sprint.name}"\n`);
150
+ process.stdout.write(`Closed sprint #${sprint.number} "${(0, sanitize_1.sanitizeField)(sprint.name)}"\n`);
151
151
  }
@@ -6,6 +6,7 @@ exports.sprintCreate = sprintCreate;
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 buildCreateSprintPayload(opts) {
10
11
  // start/end are required by the server schema; caller validates presence.
11
12
  const payload = {
@@ -17,7 +18,7 @@ function buildCreateSprintPayload(opts) {
17
18
  return payload;
18
19
  }
19
20
  function formatCreatedSprintLine(sprint) {
20
- return `Created sprint #${sprint.number} "${sprint.name}" ${sprint.id}`;
21
+ return `Created sprint #${sprint.number} "${(0, sanitize_1.sanitizeField)(sprint.name)}" ${sprint.id}`;
21
22
  }
22
23
  async function sprintCreate(opts) {
23
24
  if (!opts.start || !opts.end) {
@@ -29,8 +30,7 @@ async function sprintCreate(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
  let teamId;
36
36
  try {
@@ -72,7 +72,7 @@ async function sprintCreate(opts) {
72
72
  catch {
73
73
  // ignore
74
74
  }
75
- console.error(`Error: ${serverMsg ?? `sprint create failed (HTTP ${res.status})`}`);
75
+ console.error(`Error: ${serverMsg != null ? (0, sanitize_1.sanitizeField)(serverMsg) : `sprint create failed (HTTP ${res.status})`}`);
76
76
  return 1;
77
77
  }
78
78
  const sprint = (await res.json());
@@ -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');
@@ -5,6 +5,8 @@ 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
7
  const agent_1 = require("../lib/agent");
8
+ const sanitize_1 = require("../lib/sanitize");
9
+ const path_guard_1 = require("../lib/path-guard");
8
10
  async function taskArtifactAdd(identifier, options) {
9
11
  if (!identifier) {
10
12
  console.error('Error: missing <task>. Usage: lumo task artifact add <LUM-42> --kind spec --title "Spec" --file spec.md --source Superpowers');
@@ -39,9 +41,20 @@ async function taskArtifactAdd(identifier, options) {
39
41
  console.error('Error: --file <path> is required.');
40
42
  return 1;
41
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
+ }
42
55
  let content;
43
56
  try {
44
- content = await (0, doc_input_1.readFileUtf8)(options.file);
57
+ content = await (0, doc_input_1.readFileUtf8)(verdict.resolved);
45
58
  }
46
59
  catch {
47
60
  console.error(`Error: could not read file ${options.file}`);
@@ -56,8 +69,7 @@ async function taskArtifactAdd(identifier, options) {
56
69
  console.error('Error: not logged in. Run `lumo auth login` first.');
57
70
  return 1;
58
71
  }
59
- const envUrl = process.env.LUMO_API_URL?.trim();
60
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
72
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
61
73
  const base = (0, api_1.trimTrailingSlash)(apiUrl);
62
74
  const payload = {
63
75
  kind,
@@ -101,10 +113,10 @@ async function taskArtifactAdd(identifier, options) {
101
113
  /* not JSON */
102
114
  }
103
115
  console.error(serverMsg
104
- ? `Error: ${serverMsg}`
116
+ ? `Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`
105
117
  : `Error: artifact add failed (HTTP ${res.status})`);
106
118
  return 1;
107
119
  }
108
120
  const data = (await res.json());
109
- 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`);
110
122
  }
@@ -4,6 +4,7 @@ exports.taskArtifactList = taskArtifactList;
4
4
  const config_1 = require("../lib/config");
5
5
  const api_1 = require("../lib/api");
6
6
  const agent_1 = require("../lib/agent");
7
+ const sanitize_1 = require("../lib/sanitize");
7
8
  async function taskArtifactList(identifier) {
8
9
  if (!identifier) {
9
10
  console.error('Error: missing <task>. Usage: lumo task artifact list <LUM-42>');
@@ -14,8 +15,7 @@ async function taskArtifactList(identifier) {
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 base = (0, api_1.trimTrailingSlash)(apiUrl);
20
20
  let res;
21
21
  try {
@@ -44,7 +44,7 @@ async function taskArtifactList(identifier) {
44
44
  return;
45
45
  }
46
46
  for (const a of artifacts) {
47
- const agentLabel = agent_1.AGENT_LABELS[a.agent] ?? a.agent;
48
- process.stdout.write(`${a.id} ${a.kind.padEnd(10)} ${agentLabel.padEnd(15)} ${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`);
49
49
  }
50
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
  }