@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
package/assets/skill.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: lumo
3
- description: 'Use the Lumo CLI to load task context, manage session bindings, inspect projects and milestones, and create/update/list/show/comment on tasks from the terminal. Activate when: user mentions a Lumo task identifier (LUM-42, LUM-12, etc.), asks to load task background or context, wants to bind, check, or detach a Claude Code session''s task binding, is about to start development work on a specific task, wants to create a new task, list their tasks, view a task, comment on a task, list projects, list milestones, attach a task to a milestone, or update a task''s status/title/description/priority/assignee/milestone. Triggers on: "LUM-", "task context", "load context", "session start", "session attach", "session status", "session detach", "bind session", "unbind session", "which task", "what task am I on", "work on LUM", "create task", "new task", "add task", "file a task", "log a task", "list tasks", "my tasks", "show task", "view task", "comment on task", "leave a comment", "list projects", "what projects", "update task", "change task status", "rename task", "reassign task", "mark task as done", "milestone", "里程碑", "list milestones", "set milestone", "挂到 milestone", "attach milestone", "unbind milestone", "create milestone", "new milestone", "update milestone", "change milestone status", "delete milestone", "show milestone", "view milestone", "tasks in milestone", "milestone tasks", "新建里程碑", "更新里程碑", "删除里程碑", "查看里程碑", "auth login", "log in", "login", "auth logout", "log out", "logout", "sign out", "switch account", "switch identity", "whoami", "who am I", "current identity", "current user", "current workspace", "登录", "登出", "切换账号", "当前身份", "create doc", "new doc", "new document", "write doc", "写文档", "新建文档", "update doc", "edit doc", "修改文档", "更新文档", "list docs", "my docs", "我的文档", "show doc", "view doc", "查看文档", "delete doc", "删除文档", "bind doc", "attach doc to task", "把文档关联到任务", "文档绑定到任务", "unbind doc", "解绑文档", "personal doc", "workspace doc", "个人文档", "workspace 文档", "doc scope", "tag", "add tag", "remove tag", "标签", "添加标签", "移除标签", "doc tag", "task tag", "share doc", "doc share", "share document", "分享文档", "文档分享", "unshare doc", "remove share", "取消分享", "share list", "list doc shares", "who has access", "viewer", "editor", "manager", "shared with", "doc tree", "doc move", "move doc", "reparent doc", "文档树", "移动文档", "sprint", "start sprint", "close sprint", "add to sprint", "active sprints", "冲刺", "迭代", "开始冲刺", "关闭冲刺", "create sprint", "new sprint", "list sprints", "show sprint", "update sprint", "delete sprint", "sprint summary", "sprint retro", "把任务挂到冲刺", "冲刺里有什么", "lumo update", "update cli", "upgrade lumo", "update lumo", "upgrade cli", "升级 lumo", "更新 cli", "new lumo version", "是否有新版本", "lumo setup", "install lumo skill", "install lumo hooks", "wire up lumo", "set up lumo", "onboard lumo", "npx @lumoai/cli", "安装 lumo", "配置 lumo", "lumo 初始化", "task artifact", "artifact add", "artifact list", "artifact show", "artifact rm", "artifact delete", "artifact update", "update artifact", "edit artifact", "change artifact kind", "change artifact source", "remove artifact", "delete artifact", "spec artifact", "record spec", "attach spec", "attach plan", "记录 spec", "挂 spec", "查看 artifact", "编辑 artifact", "修改 artifact", "删除 artifact", figma, attach figma, figma link, 关联 figma, 设计稿, figma design.'
3
+ description: 'Use the Lumo CLI to load task context, manage session bindings, inspect projects and milestones, and create/update/list/show/comment on tasks from the terminal. Activate when: user mentions a Lumo task identifier (LUM-42, LUM-12, etc.), asks to load task background or context, wants to bind, check, or detach a Claude Code session''s task binding, is about to start development work on a specific task, wants to create a new task, list their tasks, view a task, comment on a task, list projects, list milestones, attach a task to a milestone, or update a task''s status/title/description/priority/assignee/milestone. Triggers on: "LUM-", "task context", "load context", "session start", "session attach", "session status", "session detach", "bind session", "unbind session", "which task", "what task am I on", "work on LUM", "create task", "new task", "add task", "file a task", "log a task", "list tasks", "my tasks", "show task", "view task", "comment on task", "leave a comment", "list projects", "what projects", "update task", "change task status", "rename task", "reassign task", "mark task as done", "milestone", "里程碑", "list milestones", "set milestone", "挂到 milestone", "attach milestone", "unbind milestone", "create milestone", "new milestone", "update milestone", "change milestone status", "delete milestone", "show milestone", "view milestone", "tasks in milestone", "milestone tasks", "新建里程碑", "更新里程碑", "删除里程碑", "查看里程碑", "auth login", "log in", "login", "auth logout", "log out", "logout", "sign out", "switch account", "switch identity", "whoami", "who am I", "current identity", "current user", "current workspace", "登录", "登出", "切换账号", "当前身份", "create doc", "new doc", "new document", "write doc", "写文档", "新建文档", "update doc", "edit doc", "修改文档", "更新文档", "list docs", "my docs", "我的文档", "show doc", "view doc", "查看文档", "delete doc", "删除文档", "bind doc", "attach doc to task", "把文档关联到任务", "文档绑定到任务", "unbind doc", "解绑文档", "personal doc", "workspace doc", "个人文档", "workspace 文档", "doc scope", "tag", "add tag", "remove tag", "标签", "添加标签", "移除标签", "doc tag", "task tag", "share doc", "doc share", "share document", "分享文档", "文档分享", "unshare doc", "remove share", "取消分享", "share list", "list doc shares", "who has access", "viewer", "editor", "manager", "shared with", "doc tree", "doc move", "move doc", "reparent doc", "文档树", "移动文档", "sprint", "start sprint", "close sprint", "add to sprint", "active sprints", "冲刺", "迭代", "开始冲刺", "关闭冲刺", "create sprint", "new sprint", "list sprints", "show sprint", "update sprint", "delete sprint", "sprint summary", "sprint retro", "把任务挂到冲刺", "冲刺里有什么", "lumo update", "update cli", "upgrade lumo", "update lumo", "upgrade cli", "升级 lumo", "更新 cli", "new lumo version", "是否有新版本", "lumo setup", "install lumo skill", "install lumo hooks", "wire up lumo", "set up lumo", "onboard lumo", "npx @lumoai/cli", "安装 lumo", "配置 lumo", "lumo 初始化", "task artifact", "artifact add", "artifact list", "artifact show", "artifact rm", "artifact delete", "artifact update", "update artifact", "edit artifact", "change artifact kind", "change artifact source", "remove artifact", "delete artifact", "spec artifact", "record spec", "attach spec", "attach plan", "记录 spec", "挂 spec", "查看 artifact", "编辑 artifact", "修改 artifact", "删除 artifact", figma, attach figma, figma link, 关联 figma, 设计稿, figma design, "memory", "记忆", "remember", "record a memory", "记一条", "promote memory", "promote to project", "沉淀", "task memory", "project memory", "lumo memory", "retrieval", "取全文", "load full content", "拉全文", "task slack show", "看 thread", "看 slack thread", "show slack thread", "slack 全文", "task web show", "show web link body", "web 正文", "抓网页正文", "task figma context", "figma metadata", "figma 元数据", "figma 设计上下文", "task comments list", "list comments", "看评论", "评论流", "task pr show", "查看 PR", "show pr", "PR 详情", "pr metadata", "import google doc", "sync google doc", "google drive", "doc import-gdoc", "doc sync", "导入 google 文档", "同步 google 文档".'
4
4
  ---
5
5
 
6
6
  ## Prerequisites
@@ -17,15 +17,18 @@ which lumo && lumo whoami
17
17
 
18
18
  ## Onboarding
19
19
 
20
- ### `lumo setup [--user|--project] [--force]` — install skill + hooks
20
+ ### `lumo setup [--user|--project] [--force] [--agent <token>]` — install skill + hooks
21
21
 
22
- Bootstraps Lumo into a Claude Code installation. Copies the bundled `SKILL.md` into `<scope>/.claude/skills/lumo/` and idempotently merges 25 hook entries into `<scope>/.claude/settings.json`. Existing user permissions and non-Lumo hook entries are preserved.
22
+ Bootstraps Lumo into a coding-agent installation. Copies the bundled `SKILL.md` into `<scope>/.claude/skills/lumo/` and idempotently merges 25 hook entries into `<scope>/.claude/settings.json`. Existing user permissions and non-Lumo hook entries are preserved.
23
+
24
+ `--agent <token>` records which coding agent these hooks run under and is **baked into every hook command** (`lumo hook <slug> --agent <token>`). Each hook then sends the agent to the server, where it's stored on the Session and inherited by auto-sedimented memories — so a memory is attributed to the agent that produced it instead of the default. Valid tokens: `claude-code | codex | cursor | gemini-cli | github-copilot | windsurf` (case-insensitive; `gemini` and `copilot` are accepted aliases). **Defaults to `claude-code`.** Re-running setup with a different `--agent` rewrites the token in place (no duplicate hook entries), and a legacy flagless entry is upgraded on the next run.
23
25
 
24
26
  ```bash
25
- npx @lumoai/cli setup # interactive: prompts user/project
26
- npx @lumoai/cli setup --user # write to ~/.claude/
27
- npx @lumoai/cli setup --project # write to ./.claude/
28
- npx @lumoai/cli setup --force # overwrite SKILL.md if it differs from bundled
27
+ npx @lumoai/cli setup # interactive: prompts user/project; agent=claude-code
28
+ npx @lumoai/cli setup --user # write to ~/.claude/
29
+ npx @lumoai/cli setup --project # write to ./.claude/
30
+ npx @lumoai/cli setup --force # overwrite SKILL.md if it differs from bundled
31
+ npx @lumoai/cli setup --agent codex # bake agent=codex into the hook commands
29
32
  ```
30
33
 
31
34
  Use when:
@@ -33,8 +36,9 @@ Use when:
33
36
  - The user asks "how do I set up lumo", "install the lumo skill", "wire up lumo hooks"
34
37
  - A fresh checkout of a project that uses Lumo doesn't have `.claude/skills/lumo/SKILL.md` yet
35
38
  - After `npm install -g @lumoai/cli`, before the first `lumo auth login`
39
+ - Wiring Lumo into a non-Claude-Code agent (Codex / Cursor / …) — pass `--agent <token>` so its memories are attributed correctly
36
40
 
37
- If stdin is not a TTY (CI, piped invocation), the default scope is `project` and no prompt is shown.
41
+ If stdin is not a TTY (CI, piped invocation), the default scope is `project` and no prompt is shown. An unrecognized `--agent` token aborts before writing anything.
38
42
 
39
43
  ## Authentication
40
44
 
@@ -129,6 +133,83 @@ The command prints a markdown document to stdout containing:
129
133
  - Focus on the **most recent 1–2 sessions** for relevant state; older sessions are for historical reference only
130
134
  - If there are **no prior sessions**, this is a fresh start — read the task description carefully and ask clarifying questions if needed
131
135
 
136
+ ## Context Retrieval (按需取全文)
137
+
138
+ LUM-122 split task context injection into tiers. `lumo task context <LUM-N>`
139
+ now emits a **cheap inline card** for each source — a short summary or just
140
+ metadata — instead of dumping full bodies. Slack, docs, artifacts, and comments
141
+ get an **LLM summary**; web links, Figma, and PRs get **metadata only**. Each
142
+ card ends with the **retrieval command** you run to pull the heavy content on
143
+ demand.
144
+
145
+ **How to use it:** when the inline card is not enough and you need the full
146
+ Slack thread, the web page body, the Figma metadata, the entire comment thread,
147
+ or the PR detail — run the matching command below. Pass the same identifier
148
+ (`LUM-N`) plus the id the card shows for that source (a Slack `contextId`, a web
149
+ `linkId`, a Figma `linkId`, or a PR `number`).
150
+
151
+ All five are **read-only** (no live Slack/GitHub/Figma calls except the web body
152
+ fetch). Web/Figma/PR are v1 metadata-degraded: they print a `note:` explaining
153
+ that live content needs an external integration.
154
+
155
+ ### `lumo task slack show <identifier> <contextId>` — full Slack thread snapshot
156
+
157
+ Prints the **stored** thread snapshot (no live Slack call), one line per message
158
+ as `author: text`. Author falls back to `@<userId>` when the display name is
159
+ missing. Empty snapshot prints `(no messages in stored snapshot)`.
160
+
161
+ ```bash
162
+ lumo task slack show LUM-42 ctx_abc123
163
+ ```
164
+
165
+ ### `lumo task web show <identifier> <linkId>` — fetched web link body
166
+
167
+ Fetches the page body on demand behind the SSRF guard (cached after first read)
168
+ and prints it as plain text. Empty body prints `(empty body)`. Fetch failures
169
+ (blocked host, timeout) print the server's error message.
170
+
171
+ ```bash
172
+ lumo task web show LUM-42 wl_abc123
173
+ ```
174
+
175
+ ### `lumo task figma context <identifier> <linkId>` — Figma link metadata
176
+
177
+ **v1 metadata fallback.** Prints the cached design metadata as `file:` /
178
+ `frame:` / `url:` / `synced:` (and `syncError:` if the last sync failed) lines.
179
+ Live design context (layers, variables, code connect) requires the Figma MCP
180
+ server, so the command ends with a `note:` saying so.
181
+
182
+ ```bash
183
+ lumo task figma context LUM-42 cfl_abc123
184
+ ```
185
+
186
+ ### `lumo task comments list <identifier>` — full comment thread
187
+
188
+ Prints the **entire** comment thread: each comment as `author · createdAt`
189
+ followed by its plain-text body (comment bodies are stored as HTML and stripped
190
+ to text). Replies are indented two spaces under their parent. Author falls back
191
+ to `unknown`. No comments prints `(no comments)`.
192
+
193
+ ```bash
194
+ lumo task comments list LUM-42
195
+ ```
196
+
197
+ **Plural, and distinct from `task comment`.** `task comments list` _reads_ the
198
+ whole thread (this retrieval command). `task comment <identifier> <body>`
199
+ _writes_ a single new comment (see Task Management). Don't confuse the two —
200
+ the plural `comments` is read-only.
201
+
202
+ ### `lumo task pr show <identifier> <number>` — synced PR metadata
203
+
204
+ **v1 metadata fallback.** Prints the synced PR record: a `#<number> (repo)
205
+ title` header, then `state:` (with ` · draft` when draft), `ci:`, `author:`,
206
+ `branch: <head> → <base>`, and `url:` lines. The live diff + review comments
207
+ require the GitHub integration, so the command ends with a `note:` saying so.
208
+
209
+ ```bash
210
+ lumo task pr show LUM-42 128
211
+ ```
212
+
132
213
  ## Task Management
133
214
 
134
215
  ### `lumo task create <title> [flags]` — create a new task
@@ -299,32 +380,35 @@ The CLI does not support @-mention chip syntax. If the user wants to ping someon
299
380
 
300
381
  Record Claude Code spec-engineering products (spec / plan / design …) on a task. The artifacts show up in the task detail "规格" (definition) layer and are injected into `lumo task context`.
301
382
 
302
- #### `lumo task artifact add <task> --kind <kind> --title <title> --file <path> --source <source>`
383
+ #### `lumo task artifact add <task> --kind <kind> --title <title> --file <path> --source <source> --agent <agent>`
384
+
385
+ Attaches an artifact to a task. `--kind`, `--title`, `--source` are stored verbatim — **`kind` is opaque** (no enumeration; `spec` / `plan` / `requirements` / anything is accepted). `--source` is **required** and names the spec-generation framework that produced the artifact, written as its **formal name** (`Superpowers` / `Spec Kit` / `BMad` / `OpenSpec` / `GSD` / …) — it is also opaque (no enumeration), but there is **no default**, so you must pass it. Quote names that contain spaces (`--source "Spec Kit"`). `--file` supplies the body (file contents). Each call appends to the end of the task's artifact list — call once per artifact (e.g. Superpowers: one `spec`, one `plan`). The `<task>` (e.g. `LUM-42`) is resolved server-side. **`--file` is sandboxed:** the CLI rejects any path that resolves **outside the current project directory** (parent-traversal, absolute paths, escaping symlinks) or that matches a **sensitive-file denylist** (`.env*`, `id_rsa`/`id_ed25519`, `*.pem`/`*.key`, `.aws`/`.ssh` contents, `credentials`, `.npmrc`, …). There is no override flag — pass only project-local, non-secret files (e.g. `docs/spec.md`). To record an out-of-tree file, copy it into the project first.
303
386
 
304
- Attaches an artifact to a task. `--kind`, `--title`, `--source` are stored verbatim — **`kind` is opaque** (no enumeration; `spec` / `plan` / `requirements` / anything is accepted). `--source` is **required** and names the spec-generation framework that produced the artifact, written as its **formal name** (`Superpowers` / `Spec Kit` / `BMad` / `OpenSpec` / `GSD` / …) — it is also opaque (no enumeration), but there is **no default**, so you must pass it. Quote names that contain spaces (`--source "Spec Kit"`). `--file` supplies the body (file contents). Each call appends to the end of the task's artifact list — call once per artifact (e.g. Superpowers: one `spec`, one `plan`). The `<task>` (e.g. `LUM-42`) is resolved server-side.
387
+ `--agent` is **required** and names the coding tool that produced the artifact (enum). Valid values: `claude-code | codex | cursor | gemini-cli | github-copilot | windsurf` (case-insensitive; `gemini` and `copilot` are accepted aliases).
305
388
 
306
389
  ```bash
307
- lumo task artifact add LUM-42 --kind spec --title "Spec" --file docs/spec.md --source Superpowers
308
- lumo task artifact add LUM-42 --kind plan --title "Implementation plan" --file docs/plan.md --source "Spec Kit"
390
+ lumo task artifact add LUM-42 --kind spec --title "Spec" --file docs/spec.md --source Superpowers --agent claude-code
391
+ lumo task artifact add LUM-42 --kind plan --title "Implementation plan" --file docs/plan.md --source "Spec Kit" --agent claude-code
309
392
  ```
310
393
 
311
394
  Output: `Added [spec] "Spec" to LUM-42`
312
395
 
313
- #### `lumo task artifact update <task> <artifact-id> [--kind <kind>] [--title <title>] [--source <source>]`
396
+ #### `lumo task artifact update <task> <artifact-id> [--kind <kind>] [--title <title>] [--source <source>] [--agent <agent>]`
314
397
 
315
- Patches an existing artifact's metadata in place. Editable fields are **`kind`, `title`, and `source`** only — **content is immutable** (to change the body, `rm` the artifact and `add` it again). At least one flag is required; passing none errors before any network call. Empty values (`--kind ""`) are rejected. The `<artifact-id>` is the cuid in column 1 of `artifact list`.
398
+ Patches an existing artifact's metadata in place. Editable fields are **`kind`, `title`, `source`, and `agent`** — **content is immutable** (to change the body, `rm` the artifact and `add` it again). At least one flag is required; passing none errors before any network call. Empty values (`--kind ""`) are rejected. The `<artifact-id>` is the cuid in column 1 of `artifact list`. `--agent` accepts the same valid values and aliases as `artifact add`: `claude-code | codex | cursor | gemini-cli | github-copilot | windsurf` (case-insensitive; `gemini` and `copilot` are accepted aliases).
316
399
 
317
400
  ```bash
318
401
  lumo task artifact update LUM-42 cma_xxx --kind plan # re-classify spec → plan
319
402
  lumo task artifact update LUM-42 cma_xxx --source "Spec Kit" # fix the framework label
320
403
  lumo task artifact update LUM-42 cma_xxx --title "Final spec" --source OpenSpec
404
+ lumo task artifact update LUM-42 cma_xxx --agent cursor # fix the agent label
321
405
  ```
322
406
 
323
407
  Output: `Updated [plan] "Final spec" (OpenSpec) on LUM-42`. A 404 (task or artifact missing in this workspace) prints the server message and exits 1.
324
408
 
325
409
  #### `lumo task artifact list <task>`
326
410
 
327
- Lists artifacts on the task in order: `<id> <kind> <source> <title>`. Prints `No artifacts on <task>` when there are none.
411
+ Lists artifacts on the task in order: `<id> <kind> <agent> <source> <title>`. Prints `No artifacts on <task>` when there are none.
328
412
 
329
413
  ```bash
330
414
  lumo task artifact list LUM-42
@@ -332,7 +416,7 @@ lumo task artifact list LUM-42
332
416
 
333
417
  #### `lumo task artifact show <task> <artifact-id>`
334
418
 
335
- Prints one artifact's key:value header (id, kind, title, source, order, task) followed by the full content body. The `<artifact-id>` is the cuid in column 1 of `artifact list`.
419
+ Prints one artifact's key:value header (id, kind, title, agent, source, order, task) followed by the full content body. The `<artifact-id>` is the cuid in column 1 of `artifact list`.
336
420
 
337
421
  ```bash
338
422
  lumo task artifact show LUM-42 cma_xxx
@@ -515,7 +599,7 @@ Use this when the user wants to write a new document from the terminal. Title is
515
599
  | `--tag-id <cuid>` | string (repeatable) | Attach tag by id. Combines with `--tag`. Max 20 per call. |
516
600
  | `--parent <doc>` | string | cuid or case-insensitive title. Files the new doc under this parent. Omit for root. |
517
601
 
518
- The three content channels (`--content`, `--file`, stdin) are mutually exclusive — specify at most one.
602
+ The three content channels (`--content`, `--file`, stdin) are mutually exclusive — specify at most one. **`--file` is sandboxed:** the CLI rejects a path that resolves outside the current project directory (parent-traversal, absolute, escaping symlinks) or matches a sensitive-file denylist (`.env*`, private keys, `*.pem`/`*.key`, `credentials`, `.ssh`/`.aws` contents, …). Pass only project-local, non-secret files; there is no override flag.
519
603
 
520
604
  Examples:
521
605
 
@@ -563,6 +647,8 @@ The `Tags:` line is omitted when no tags were attached.
563
647
 
564
648
  `--tag` / `--tag-id` (bulk replace) are mutually exclusive with `--add-tag` / `--add-tag-id` / `--remove-tag` / `--remove-tag-id`. The CLI errors before any network call if both families are mixed.
565
649
 
650
+ Like `doc create`, `--file` is sandboxed: the CLI rejects paths that resolve outside the project directory or match the sensitive-file denylist (`.env*`, private keys, `credentials`, …). No override flag.
651
+
566
652
  Examples:
567
653
 
568
654
  ```bash
@@ -731,6 +817,63 @@ lumo doc share-list "RFC"
731
817
  - After `doc create --scope personal`, if the user mentions teammates needing access, suggest `doc share` rather than `doc update --scope workspace` when only specific members should see it
732
818
  - Before `doc unshare`, run `doc share-list` if the user hasn't named a specific member
733
819
 
820
+ ### `lumo doc import-gdoc <url> [--scope personal|workspace] [--task LUM-N]` — import a Google Doc
821
+
822
+ One-way import of a Google Doc into Lumo. The doc is exported from Google as markdown and turned into a native Lumo document (markdown → HTML), storing the source `googleDocId` and importer so it can be re-synced later (`lumo doc sync`). `<url>` accepts a Google Doc URL or a bare doc id.
823
+
824
+ | Flag | Type | Notes |
825
+ | ----------------- | ------ | ------------------------------------------------------------------------------------------ |
826
+ | `--scope <scope>` | enum | `personal` (→ PRIVATE) or `workspace` (→ WORKSPACE). Omit to use the server default scope. |
827
+ | `--task <LUM-N>` | string | Bind the imported doc to this task immediately after import. |
828
+
829
+ **Over-share note:** once imported, the content follows **Lumo's** sharing model (PRIVATE / SHARED / WORKSPACE) and is **no longer gated by Google permissions**. Importing a `workspace`-scoped doc can therefore expose it to everyone in the workspace even if the Google Doc was restricted — the command prints this reminder on success.
830
+
831
+ Requires a connected Google Drive integration; connect it in the Web UI at `/settings/integrations`. There is no CLI `google auth` command.
832
+
833
+ ```bash
834
+ lumo doc import-gdoc "https://docs.google.com/document/d/<id>/edit"
835
+ lumo doc import-gdoc "https://docs.google.com/document/d/<id>/edit" --scope workspace --task LUM-127
836
+ ```
837
+
838
+ Output:
839
+
840
+ ```
841
+ Imported cmd_xxx "Quarterly Plan" https://www.uselumo.ai/workspace/lumo/documents/quarterly-plan-42
842
+ Note: imported content follows Lumo sharing and is no longer gated by Google permissions.
843
+ Bound cmd_xxx ↔ LUM-127
844
+ ```
845
+
846
+ The `Bound ... ↔ LUM-N` line appears only when `--task` is supplied.
847
+
848
+ ### When to suggest `doc import-gdoc`
849
+
850
+ - User pastes a Google Doc URL or says "import this Google Doc", "pull this gdoc into Lumo", "把这篇 Google 文档导入".
851
+ - User wants a Google Doc tracked alongside Lumo tasks/docs — suggest import (with `--task LUM-N` if a task is in context) and remind them about the over-share semantics for `--scope workspace`.
852
+
853
+ ### `lumo doc sync <doc>` — re-sync an imported doc from Google
854
+
855
+ Re-imports a previously imported Google Doc and **overwrites the Lumo body** with the current Google content. **One-way and destructive** — any edits made to the doc inside Lumo are discarded; Google is the source of truth for synced docs.
856
+
857
+ Sync always runs as the **importer** (owner model): it re-exports using the importer's stored Google token, not the token of whoever runs the command. If the importer has lost access to the Google Doc, sync fails with a clear error.
858
+
859
+ `<doc>` accepts a doc cuid or a case-insensitive title; ambiguous titles fail with a candidate list — re-run with the cuid.
860
+
861
+ ```bash
862
+ lumo doc sync cmd_xxx
863
+ lumo doc sync "Quarterly Plan"
864
+ ```
865
+
866
+ Output:
867
+
868
+ ```
869
+ Synced cmd_xxx "Quarterly Plan" from Google
870
+ ```
871
+
872
+ ### When to suggest `doc sync`
873
+
874
+ - User says "re-sync the Google Doc", "pull the latest from Google", "refresh the imported doc", "更新一下从 Google 导入的文档".
875
+ - After the user mentions the Google Doc changed upstream. Warn first that local Lumo edits to that doc will be overwritten (one-way, destructive).
876
+
734
877
  ### Out of scope (CLI v1)
735
878
 
736
879
  The CLI does **not** currently support:
@@ -886,6 +1029,74 @@ lumo sprint remove 3 LUM-48
886
1029
 
887
1030
  When to suggest: user says "remove LUM-48 from the sprint", "take this task out of sprint 3", "move task to backlog".
888
1031
 
1032
+ ## Memory management
1033
+
1034
+ Record and curate the long-term Memory that Claude reads on future sessions.
1035
+ Memories are scoped **TASK** (useful only for one task) or **PROJECT** (useful
1036
+ across the whole project). Automated extraction (layer1) and promotion (layer2,
1037
+ on task→done) already run; these commands are the manual override.
1038
+
1039
+ ### Commands
1040
+
1041
+ ```bash
1042
+ # List
1043
+ lumo task memory list [LUM-N] [--category trap|decision|convention|procedural] [-n N]
1044
+ lumo project memory list [<project>] [--category ...] [-n N]
1045
+
1046
+ # Add (per-category fields; <task>/<project> default to the session-bound task)
1047
+ lumo task memory add [LUM-N] --category trap --trigger "..." --outcome "..." [--workaround "..."] [--agent <agent>]
1048
+ lumo task memory add [LUM-N] --category decision --what "..." --why "..." [--alternatives "..."] [--implications "..."] [--agent <agent>]
1049
+ lumo task memory add [LUM-N] --category convention --rule "..." --applies "..." [--agent <agent>]
1050
+ lumo task memory add [LUM-N] --category procedural --workflow "..." --trigger "..." [--step "..." --step "..."] [--agent <agent>]
1051
+ lumo project memory add [<project>] --category ... [--agent <agent>] # same flags; records at PROJECT scope
1052
+
1053
+ # --agent values: claude-code | codex | cursor | gemini-cli | github-copilot | windsurf (default claude-code)
1054
+ # Aliases: gemini → gemini-cli, copilot → github-copilot (case-insensitive)
1055
+ # Omitting --agent records the memory as produced by Claude Code.
1056
+
1057
+ # Single-memory ops (memoryId from `... memory list` column 1)
1058
+ lumo memory promote <memoryId> # TASK → PROJECT
1059
+ lumo memory rm <memoryId> --yes # hard delete
1060
+ ```
1061
+
1062
+ When the session is bound (`lumo session attach <LUM-N>`), omit the identifier:
1063
+ `lumo task memory add --category trap --trigger ... --outcome ...` records onto
1064
+ the bound task; `lumo project memory add ...` records onto its project.
1065
+
1066
+ ### When to record a memory (worthiness)
1067
+
1068
+ Record only knowledge that is **invisible in the codebase** — the _why_ behind a
1069
+ choice, a gotcha that only surfaces at runtime, a rule that lives in people's
1070
+ heads, a non-obvious failure cause, a non-trivial workflow. **Skip** routine work
1071
+ (reading files, plain edits, normal git, successful builds) and anything a
1072
+ developer could learn from the source, git log, or docs. When unsure, don't.
1073
+
1074
+ ### Which category
1075
+
1076
+ - `trap` — a pitfall. Describe the PROBLEM ONLY (`--trigger`, `--outcome`); put any fix in a separate `procedural`.
1077
+ - `decision` — an engineering decision (`--what` + `--why`, optional `--alternatives`/`--implications`).
1078
+ - `convention` — a team rule (`--rule` + `--applies` = where it applies).
1079
+ - `procedural` — a reusable workflow (`--workflow` + `--trigger` + `--step`…).
1080
+
1081
+ ### TASK vs PROJECT (at add time)
1082
+
1083
+ Default to **TASK** (`lumo task memory add`). Record directly to **PROJECT**
1084
+ (`lumo project memory add`) only when it helps _any_ task in the project: a
1085
+ toolchain/environment trap, a team-wide convention, a cross-task decision. When
1086
+ unsure → TASK.
1087
+
1088
+ ### When to promote (TASK → PROJECT)
1089
+
1090
+ `lumo memory promote <id>` only when the lesson **recurs across 2+ different
1091
+ tasks**, would help a _different_ task, and no equivalent PROJECT memory exists.
1092
+ A wrong promotion is costly (every agent sees it forever) — prefer leaving it at TASK.
1093
+
1094
+ ### When to reach for this
1095
+
1096
+ After a non-trivial debugging session, a pitfall you hit, or establishing a
1097
+ convention → consider `lumo task memory add`. When you notice a lesson recurring
1098
+ across multiple tasks → consider `lumo memory promote`.
1099
+
889
1100
  ## Session Management
890
1101
 
891
1102
  ### `lumo session attach <identifier>` — bind the current session to a task
@@ -5,6 +5,7 @@ const prompt_1 = require("../lib/prompt");
5
5
  const browser_1 = require("../lib/browser");
6
6
  const api_1 = require("../lib/api");
7
7
  const config_1 = require("../lib/config");
8
+ const sanitize_1 = require("../lib/sanitize");
8
9
  const KEY_PREFIX = 'lum_';
9
10
  async function authLogin() {
10
11
  const apiUrl = (0, api_1.resolveApiUrl)();
@@ -49,7 +50,7 @@ async function authLogin() {
49
50
  apiKeyPrefix: resp.apiKey.prefix,
50
51
  });
51
52
  console.log('');
52
- console.log(`✓ Logged in as ${resp.user.email}`);
53
- console.log(` Workspace: ${resp.workspace.name}`);
54
- console.log(` Key: ${resp.apiKey.name} (${resp.apiKey.prefix})`);
53
+ console.log(`✓ Logged in as ${(0, sanitize_1.sanitizeField)(resp.user.email)}`);
54
+ console.log(` Workspace: ${(0, sanitize_1.sanitizeField)(resp.workspace.name)}`);
55
+ console.log(` Key: ${(0, sanitize_1.sanitizeField)(resp.apiKey.name)} (${resp.apiKey.prefix})`);
55
56
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.authLogout = authLogout;
4
4
  const config_1 = require("../lib/config");
5
+ const sanitize_1 = require("../lib/sanitize");
5
6
  async function authLogout() {
6
7
  const creds = (0, config_1.readCredentials)();
7
8
  if (!creds) {
@@ -10,5 +11,5 @@ async function authLogout() {
10
11
  }
11
12
  const email = creds.email;
12
13
  (0, config_1.deleteCredentials)();
13
- console.log(`✓ Logged out (${email})`);
14
+ console.log(`✓ Logged out (${(0, sanitize_1.sanitizeField)(email)})`);
14
15
  }
@@ -5,6 +5,7 @@ exports.docBind = docBind;
5
5
  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
+ const sanitize_1 = require("../lib/sanitize");
8
9
  function formatBindOutput(args) {
9
10
  if (args.alreadyBound)
10
11
  return `Already bound ${args.docId} ↔ ${args.identifier}`;
@@ -21,8 +22,7 @@ async function docBind(docRef, task) {
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 docId = await (0, resolve_doc_id_1.lookupDocId)(apiUrl, creds.token, docRef);
27
27
  if (!docId) {
28
28
  console.error(`Error: Document not found: ${docRef}`);
@@ -39,7 +39,7 @@ async function docBind(docRef, task) {
39
39
  });
40
40
  if (!res.ok) {
41
41
  const text = await res.text();
42
- console.error(`Error: ${res.status} ${res.statusText}: ${text}`);
42
+ console.error(`Error: ${res.status} ${res.statusText}: ${(0, sanitize_1.sanitizeField)(text)}`);
43
43
  return 1;
44
44
  }
45
45
  const { mention } = (await res.json());
@@ -8,6 +8,7 @@ const api_1 = require("../lib/api");
8
8
  const doc_input_1 = require("../lib/doc-input");
9
9
  const tag_resolver_1 = require("../lib/tag-resolver");
10
10
  const resolve_doc_id_1 = require("../lib/resolve-doc-id");
11
+ const sanitize_1 = require("../lib/sanitize");
11
12
  /** personal → PRIVATE, workspace → WORKSPACE. Null on unknown. */
12
13
  function normalizeScope(value) {
13
14
  const lower = (value ?? '').toLowerCase();
@@ -18,10 +19,10 @@ function normalizeScope(value) {
18
19
  return null;
19
20
  }
20
21
  function formatCreatedDocLine(doc) {
21
- const escaped = doc.title.replace(/"/g, '\\"');
22
+ const escaped = (0, sanitize_1.sanitizeField)(doc.title).replace(/"/g, '\\"');
22
23
  const head = `Created ${doc.id} "${escaped}" ${doc.url}`;
23
24
  if (doc.tags && doc.tags.length > 0) {
24
- return `${head}\nTags: ${doc.tags.join(', ')}`;
25
+ return `${head}\nTags: ${doc.tags.map(sanitize_1.sanitizeField).join(', ')}`;
25
26
  }
26
27
  return head;
27
28
  }
@@ -54,8 +55,7 @@ async function docCreate(title, opts) {
54
55
  console.error(`Error: ${content.message}`);
55
56
  return 1;
56
57
  }
57
- const envUrl = process.env.LUMO_API_URL?.trim();
58
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
58
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
59
59
  const url = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/documents`;
60
60
  let tagIds;
61
61
  if ((opts.tag && opts.tag.length > 0) ||
@@ -106,7 +106,7 @@ async function docCreate(title, opts) {
106
106
  }
107
107
  if (!res.ok) {
108
108
  const text = await res.text();
109
- console.error(`Error: ${res.status} ${res.statusText}: ${text}`);
109
+ console.error(`Error: ${res.status} ${res.statusText}: ${(0, sanitize_1.sanitizeField)(text)}`);
110
110
  return 1;
111
111
  }
112
112
  const { document } = (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_1 = require("../lib/resolve-doc");
7
7
  const resolve_doc_id_1 = require("../lib/resolve-doc-id");
8
+ const sanitize_1 = require("../lib/sanitize");
8
9
  async function docDelete(reference, opts) {
9
10
  if (!reference) {
10
11
  console.error('Error: missing <doc>. Usage: lumo doc delete <doc> --yes');
@@ -19,8 +20,7 @@ async function docDelete(reference, opts) {
19
20
  console.error('Error: not logged in. Run `lumo auth login` first.');
20
21
  return 1;
21
22
  }
22
- const envUrl = process.env.LUMO_API_URL?.trim();
23
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
23
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
24
24
  // For a cuid reference we don't have the title; for a title we look up id+title at once.
25
25
  let id;
26
26
  let title = '';
@@ -44,9 +44,9 @@ async function docDelete(reference, opts) {
44
44
  });
45
45
  if (!res.ok) {
46
46
  const text = await res.text();
47
- console.error(`Error: ${res.status} ${res.statusText}: ${text}`);
47
+ console.error(`Error: ${res.status} ${res.statusText}: ${(0, sanitize_1.sanitizeField)(text)}`);
48
48
  return 1;
49
49
  }
50
- const escaped = title.replace(/"/g, '\\"');
50
+ const escaped = (0, sanitize_1.sanitizeField)(title).replace(/"/g, '\\"');
51
51
  console.log(`Deleted ${id}${title ? ` "${escaped}"` : ''}`);
52
52
  }
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatImportedLine = formatImportedLine;
4
+ exports.docImportGdoc = docImportGdoc;
5
+ const config_1 = require("../lib/config");
6
+ const api_1 = require("../lib/api");
7
+ const sanitize_1 = require("../lib/sanitize");
8
+ function formatImportedLine(doc) {
9
+ const escaped = (0, sanitize_1.sanitizeField)(doc.title).replace(/"/g, '\\"');
10
+ return (`Imported ${doc.id} "${escaped}" ${doc.url}\n` +
11
+ `Note: imported content follows Lumo sharing and is no longer gated by Google permissions.`);
12
+ }
13
+ function normalizeScope(value) {
14
+ if (value === undefined)
15
+ return undefined;
16
+ const lower = value.toLowerCase();
17
+ if (lower === 'personal')
18
+ return 'PRIVATE';
19
+ if (lower === 'workspace')
20
+ return 'WORKSPACE';
21
+ return undefined;
22
+ }
23
+ async function docImportGdoc(url, opts) {
24
+ const creds = (0, config_1.readCredentials)();
25
+ if (!creds) {
26
+ console.error('Error: not logged in. Run `lumo auth login` first.');
27
+ return 1;
28
+ }
29
+ if (!url || url.trim().length === 0) {
30
+ console.error('Error: a Google Doc URL or id is required.');
31
+ return 1;
32
+ }
33
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
34
+ const base = (0, api_1.trimTrailingSlash)(apiUrl);
35
+ const body = { url: url.trim() };
36
+ const scope = normalizeScope(opts.scope);
37
+ if (scope)
38
+ body.visibility = scope;
39
+ let res;
40
+ try {
41
+ res = await fetch(`${base}/api/documents/import-google`, {
42
+ method: 'POST',
43
+ headers: {
44
+ Authorization: `Bearer ${creds.token}`,
45
+ 'Content-Type': 'application/json',
46
+ },
47
+ body: JSON.stringify(body),
48
+ });
49
+ }
50
+ catch (err) {
51
+ console.error(`Error: network failure: ${err.message}`);
52
+ return 1;
53
+ }
54
+ if (!res.ok) {
55
+ const text = await res.text();
56
+ console.error(`Error: ${res.status} ${res.statusText}: ${(0, sanitize_1.sanitizeField)(text)}`);
57
+ return 1;
58
+ }
59
+ const { document } = (await res.json());
60
+ const fullUrl = `${base}/workspace/${creds.workspaceSlug ?? 'lumo'}/documents/${document.slug}`;
61
+ console.log(formatImportedLine({
62
+ id: document.id,
63
+ title: document.title,
64
+ url: fullUrl,
65
+ }));
66
+ if (opts.task) {
67
+ const bindRes = await fetch(`${base}/api/documents/${document.id}/mentions`, {
68
+ method: 'POST',
69
+ headers: {
70
+ Authorization: `Bearer ${creds.token}`,
71
+ 'Content-Type': 'application/json',
72
+ },
73
+ body: JSON.stringify({ taskIdentifier: opts.task }),
74
+ });
75
+ if (!bindRes.ok) {
76
+ const text = await bindRes.text();
77
+ console.error(`Warning: imported but bind to ${opts.task} failed: ${bindRes.status} ${(0, sanitize_1.sanitizeField)(text)}`);
78
+ return 1;
79
+ }
80
+ console.log(`Bound ${document.id} ↔ ${opts.task}`);
81
+ }
82
+ }
@@ -7,6 +7,7 @@ const config_1 = require("../lib/config");
7
7
  const api_1 = require("../lib/api");
8
8
  const doc_create_1 = require("./doc-create");
9
9
  const doc_tree_1 = require("../lib/doc-tree");
10
+ const sanitize_1 = require("../lib/sanitize");
10
11
  function visibilityLabel(v) {
11
12
  if (v === 'PRIVATE')
12
13
  return 'PERSONAL';
@@ -17,8 +18,8 @@ function formatDocListRows(rows) {
17
18
  return [];
18
19
  return rows.map(r => {
19
20
  const label = visibilityLabel(r.visibility).padEnd(10, ' ');
20
- const project = (r.project?.name ?? '-').padEnd(14, ' ');
21
- return `${r.id} ${label} ${project} ${r.title}`;
21
+ const project = (0, sanitize_1.sanitizeField)(r.project?.name ?? '-').padEnd(14, ' ');
22
+ return `${r.id} ${label} ${project} ${(0, sanitize_1.sanitizeField)(r.title)}`;
22
23
  });
23
24
  }
24
25
  function formatDocListRowsAsTree(rows) {
@@ -28,9 +29,9 @@ function formatDocListRowsAsTree(rows) {
28
29
  const flat = (0, doc_tree_1.flattenWithDepth)(tree);
29
30
  return flat.map(({ row, depth }) => {
30
31
  const label = visibilityLabel(row.visibility).padEnd(10, ' ');
31
- const project = (row.project?.name ?? '-').padEnd(14, ' ');
32
+ const project = (0, sanitize_1.sanitizeField)(row.project?.name ?? '-').padEnd(14, ' ');
32
33
  const indent = ' '.repeat(depth);
33
- return `${row.id} ${label} ${project} ${indent}${row.title}`;
34
+ return `${row.id} ${label} ${project} ${indent}${(0, sanitize_1.sanitizeField)(row.title)}`;
34
35
  });
35
36
  }
36
37
  async function docList(opts) {
@@ -39,8 +40,7 @@ async function docList(opts) {
39
40
  console.error('Error: not logged in. Run `lumo auth login` first.');
40
41
  return 1;
41
42
  }
42
- const envUrl = process.env.LUMO_API_URL?.trim();
43
- const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
43
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
44
44
  let url;
45
45
  if (opts.task) {
46
46
  url = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/tasks/${opts.task}/documents`;
@@ -65,7 +65,7 @@ async function docList(opts) {
65
65
  });
66
66
  if (!res.ok) {
67
67
  const text = await res.text();
68
- console.error(`Error: ${res.status} ${res.statusText}: ${text}`);
68
+ console.error(`Error: ${res.status} ${res.statusText}: ${(0, sanitize_1.sanitizeField)(text)}`);
69
69
  return 1;
70
70
  }
71
71
  const { documents } = (await res.json());