@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.
- package/assets/skill.md +228 -17
- package/dist/cli/src/commands/auth-login.js +4 -3
- package/dist/cli/src/commands/auth-logout.js +2 -1
- package/dist/cli/src/commands/doc-bind.js +3 -3
- package/dist/cli/src/commands/doc-create.js +5 -5
- package/dist/cli/src/commands/doc-delete.js +4 -4
- package/dist/cli/src/commands/doc-import-gdoc.js +82 -0
- package/dist/cli/src/commands/doc-list.js +7 -7
- package/dist/cli/src/commands/doc-move.js +8 -8
- package/dist/cli/src/commands/doc-share-list.js +11 -8
- package/dist/cli/src/commands/doc-share.js +7 -5
- package/dist/cli/src/commands/doc-show.js +6 -6
- package/dist/cli/src/commands/doc-sync.js +44 -0
- package/dist/cli/src/commands/doc-unbind.js +4 -4
- package/dist/cli/src/commands/doc-unshare.js +9 -7
- package/dist/cli/src/commands/doc-update.js +5 -5
- package/dist/cli/src/commands/hook.js +2 -2
- package/dist/cli/src/commands/memory-project-add.js +86 -0
- package/dist/cli/src/commands/memory-project-list.js +58 -0
- package/dist/cli/src/commands/memory-promote.js +52 -0
- package/dist/cli/src/commands/memory-rm.js +42 -0
- package/dist/cli/src/commands/memory-task-add.js +99 -0
- package/dist/cli/src/commands/memory-task-list.js +61 -0
- package/dist/cli/src/commands/milestone-create.js +4 -4
- package/dist/cli/src/commands/milestone-delete.js +5 -5
- package/dist/cli/src/commands/milestone-list.js +3 -3
- package/dist/cli/src/commands/milestone-show.js +5 -5
- package/dist/cli/src/commands/milestone-update.js +6 -5
- package/dist/cli/src/commands/project-list.js +3 -3
- package/dist/cli/src/commands/session-attach.js +5 -5
- package/dist/cli/src/commands/session-detach.js +3 -3
- package/dist/cli/src/commands/session-status.js +3 -3
- package/dist/cli/src/commands/setup.js +33 -7
- package/dist/cli/src/commands/sprint-add.js +3 -3
- package/dist/cli/src/commands/sprint-close.js +5 -5
- package/dist/cli/src/commands/sprint-create.js +4 -4
- package/dist/cli/src/commands/sprint-delete.js +5 -5
- package/dist/cli/src/commands/sprint-list.js +3 -3
- package/dist/cli/src/commands/sprint-remove.js +3 -3
- package/dist/cli/src/commands/sprint-show.js +4 -4
- package/dist/cli/src/commands/sprint-start.js +4 -4
- package/dist/cli/src/commands/sprint-summary.js +7 -7
- package/dist/cli/src/commands/sprint-update.js +6 -5
- package/dist/cli/src/commands/task-artifact-add.js +35 -6
- package/dist/cli/src/commands/task-artifact-list.js +5 -3
- package/dist/cli/src/commands/task-artifact-rm.js +4 -4
- package/dist/cli/src/commands/task-artifact-show.js +9 -7
- package/dist/cli/src/commands/task-artifact-update.js +15 -6
- package/dist/cli/src/commands/task-comment-list.js +111 -0
- package/dist/cli/src/commands/task-comment.js +3 -3
- package/dist/cli/src/commands/task-context.js +24 -12
- package/dist/cli/src/commands/task-create.js +7 -7
- package/dist/cli/src/commands/task-figma-add.js +3 -2
- package/dist/cli/src/commands/task-figma-context.js +61 -0
- package/dist/cli/src/commands/task-figma-list.js +3 -2
- package/dist/cli/src/commands/task-figma-refresh.js +4 -3
- package/dist/cli/src/commands/task-figma-rm.js +3 -2
- package/dist/cli/src/commands/task-list.js +1 -2
- package/dist/cli/src/commands/task-pr-show.js +66 -0
- package/dist/cli/src/commands/task-show.js +8 -7
- package/dist/cli/src/commands/task-slack-show.js +59 -0
- package/dist/cli/src/commands/task-update.js +7 -7
- package/dist/cli/src/commands/task-web-show.js +64 -0
- package/dist/cli/src/commands/whoami.js +4 -3
- package/dist/cli/src/index.js +239 -102
- package/dist/cli/src/lib/agent.js +58 -0
- package/dist/cli/src/lib/api.js +81 -1
- package/dist/cli/src/lib/config.js +2 -1
- package/dist/cli/src/lib/doc-input.js +12 -1
- package/dist/cli/src/lib/figma-api.js +1 -1
- package/dist/cli/src/lib/format.js +3 -2
- package/dist/cli/src/lib/hook-runner.js +26 -10
- package/dist/cli/src/lib/hooks-template.js +52 -7
- package/dist/cli/src/lib/memory-content.js +88 -0
- package/dist/cli/src/lib/path-guard.js +125 -0
- package/dist/cli/src/lib/resolve-bound-task.js +31 -0
- package/dist/cli/src/lib/resolve-doc-id.js +2 -1
- package/dist/cli/src/lib/resolve-member.js +2 -1
- package/dist/cli/src/lib/resolve-project.js +24 -0
- package/dist/cli/src/lib/sanitize.js +17 -0
- package/dist/cli/src/lib/tag-resolver.js +2 -1
- package/dist/cli/src/lib/update-check.js +2 -2
- 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
|
|
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
|
|
26
|
-
npx @lumoai/cli setup --user
|
|
27
|
-
npx @lumoai/cli setup --project
|
|
28
|
-
npx @lumoai/cli setup --force
|
|
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
|
-
|
|
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 `
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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());
|