@lumoai/cli 1.4.0 → 1.5.0
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 +75 -8
- package/dist/cli/src/commands/memory-project-add.js +71 -0
- package/dist/cli/src/commands/memory-project-list.js +59 -0
- package/dist/cli/src/commands/memory-promote.js +52 -0
- package/dist/cli/src/commands/memory-rm.js +43 -0
- package/dist/cli/src/commands/memory-task-add.js +84 -0
- package/dist/cli/src/commands/memory-task-list.js +62 -0
- package/dist/cli/src/commands/task-artifact-add.js +18 -1
- package/dist/cli/src/commands/task-artifact-list.js +3 -1
- package/dist/cli/src/commands/task-artifact-show.js +2 -0
- package/dist/cli/src/commands/task-artifact-update.js +10 -1
- package/dist/cli/src/index.js +72 -0
- package/dist/cli/src/lib/agent.js +49 -0
- package/dist/cli/src/lib/memory-content.js +87 -0
- package/dist/cli/src/lib/resolve-bound-task.js +31 -0
- package/dist/cli/src/lib/resolve-project.js +24 -0
- 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".'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
## Prerequisites
|
|
@@ -299,32 +299,35 @@ The CLI does not support @-mention chip syntax. If the user wants to ping someon
|
|
|
299
299
|
|
|
300
300
|
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
301
|
|
|
302
|
-
#### `lumo task artifact add <task> --kind <kind> --title <title> --file <path> --source <source>`
|
|
302
|
+
#### `lumo task artifact add <task> --kind <kind> --title <title> --file <path> --source <source> --agent <agent>`
|
|
303
303
|
|
|
304
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.
|
|
305
305
|
|
|
306
|
+
`--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).
|
|
307
|
+
|
|
306
308
|
```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"
|
|
309
|
+
lumo task artifact add LUM-42 --kind spec --title "Spec" --file docs/spec.md --source Superpowers --agent claude-code
|
|
310
|
+
lumo task artifact add LUM-42 --kind plan --title "Implementation plan" --file docs/plan.md --source "Spec Kit" --agent claude-code
|
|
309
311
|
```
|
|
310
312
|
|
|
311
313
|
Output: `Added [spec] "Spec" to LUM-42`
|
|
312
314
|
|
|
313
|
-
#### `lumo task artifact update <task> <artifact-id> [--kind <kind>] [--title <title>] [--source <source>]`
|
|
315
|
+
#### `lumo task artifact update <task> <artifact-id> [--kind <kind>] [--title <title>] [--source <source>] [--agent <agent>]`
|
|
314
316
|
|
|
315
|
-
Patches an existing artifact's metadata in place. Editable fields are **`kind`, `title`, and `
|
|
317
|
+
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
318
|
|
|
317
319
|
```bash
|
|
318
320
|
lumo task artifact update LUM-42 cma_xxx --kind plan # re-classify spec → plan
|
|
319
321
|
lumo task artifact update LUM-42 cma_xxx --source "Spec Kit" # fix the framework label
|
|
320
322
|
lumo task artifact update LUM-42 cma_xxx --title "Final spec" --source OpenSpec
|
|
323
|
+
lumo task artifact update LUM-42 cma_xxx --agent cursor # fix the agent label
|
|
321
324
|
```
|
|
322
325
|
|
|
323
326
|
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
327
|
|
|
325
328
|
#### `lumo task artifact list <task>`
|
|
326
329
|
|
|
327
|
-
Lists artifacts on the task in order: `<id> <kind> <source> <title>`. Prints `No artifacts on <task>` when there are none.
|
|
330
|
+
Lists artifacts on the task in order: `<id> <kind> <agent> <source> <title>`. Prints `No artifacts on <task>` when there are none.
|
|
328
331
|
|
|
329
332
|
```bash
|
|
330
333
|
lumo task artifact list LUM-42
|
|
@@ -332,7 +335,7 @@ lumo task artifact list LUM-42
|
|
|
332
335
|
|
|
333
336
|
#### `lumo task artifact show <task> <artifact-id>`
|
|
334
337
|
|
|
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`.
|
|
338
|
+
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
339
|
|
|
337
340
|
```bash
|
|
338
341
|
lumo task artifact show LUM-42 cma_xxx
|
|
@@ -886,6 +889,70 @@ lumo sprint remove 3 LUM-48
|
|
|
886
889
|
|
|
887
890
|
When to suggest: user says "remove LUM-48 from the sprint", "take this task out of sprint 3", "move task to backlog".
|
|
888
891
|
|
|
892
|
+
## Memory management
|
|
893
|
+
|
|
894
|
+
Record and curate the long-term Memory that Claude reads on future sessions.
|
|
895
|
+
Memories are scoped **TASK** (useful only for one task) or **PROJECT** (useful
|
|
896
|
+
across the whole project). Automated extraction (layer1) and promotion (layer2,
|
|
897
|
+
on task→done) already run; these commands are the manual override.
|
|
898
|
+
|
|
899
|
+
### Commands
|
|
900
|
+
|
|
901
|
+
```bash
|
|
902
|
+
# List
|
|
903
|
+
lumo task memory list [LUM-N] [--category trap|decision|convention|procedural] [-n N]
|
|
904
|
+
lumo project memory list [<project>] [--category ...] [-n N]
|
|
905
|
+
|
|
906
|
+
# Add (per-category fields; <task>/<project> default to the session-bound task)
|
|
907
|
+
lumo task memory add [LUM-N] --category trap --trigger "..." --outcome "..." [--workaround "..."]
|
|
908
|
+
lumo task memory add [LUM-N] --category decision --what "..." --why "..." [--alternatives "..."] [--implications "..."]
|
|
909
|
+
lumo task memory add [LUM-N] --category convention --rule "..." --applies "..."
|
|
910
|
+
lumo task memory add [LUM-N] --category procedural --workflow "..." --trigger "..." [--step "..." --step "..."]
|
|
911
|
+
lumo project memory add [<project>] --category ... # same flags; records at PROJECT scope
|
|
912
|
+
|
|
913
|
+
# Single-memory ops (memoryId from `... memory list` column 1)
|
|
914
|
+
lumo memory promote <memoryId> # TASK → PROJECT
|
|
915
|
+
lumo memory rm <memoryId> --yes # hard delete
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
When the session is bound (`lumo session attach <LUM-N>`), omit the identifier:
|
|
919
|
+
`lumo task memory add --category trap --trigger ... --outcome ...` records onto
|
|
920
|
+
the bound task; `lumo project memory add ...` records onto its project.
|
|
921
|
+
|
|
922
|
+
### When to record a memory (worthiness)
|
|
923
|
+
|
|
924
|
+
Record only knowledge that is **invisible in the codebase** — the _why_ behind a
|
|
925
|
+
choice, a gotcha that only surfaces at runtime, a rule that lives in people's
|
|
926
|
+
heads, a non-obvious failure cause, a non-trivial workflow. **Skip** routine work
|
|
927
|
+
(reading files, plain edits, normal git, successful builds) and anything a
|
|
928
|
+
developer could learn from the source, git log, or docs. When unsure, don't.
|
|
929
|
+
|
|
930
|
+
### Which category
|
|
931
|
+
|
|
932
|
+
- `trap` — a pitfall. Describe the PROBLEM ONLY (`--trigger`, `--outcome`); put any fix in a separate `procedural`.
|
|
933
|
+
- `decision` — an engineering decision (`--what` + `--why`, optional `--alternatives`/`--implications`).
|
|
934
|
+
- `convention` — a team rule (`--rule` + `--applies` = where it applies).
|
|
935
|
+
- `procedural` — a reusable workflow (`--workflow` + `--trigger` + `--step`…).
|
|
936
|
+
|
|
937
|
+
### TASK vs PROJECT (at add time)
|
|
938
|
+
|
|
939
|
+
Default to **TASK** (`lumo task memory add`). Record directly to **PROJECT**
|
|
940
|
+
(`lumo project memory add`) only when it helps _any_ task in the project: a
|
|
941
|
+
toolchain/environment trap, a team-wide convention, a cross-task decision. When
|
|
942
|
+
unsure → TASK.
|
|
943
|
+
|
|
944
|
+
### When to promote (TASK → PROJECT)
|
|
945
|
+
|
|
946
|
+
`lumo memory promote <id>` only when the lesson **recurs across 2+ different
|
|
947
|
+
tasks**, would help a _different_ task, and no equivalent PROJECT memory exists.
|
|
948
|
+
A wrong promotion is costly (every agent sees it forever) — prefer leaving it at TASK.
|
|
949
|
+
|
|
950
|
+
### When to reach for this
|
|
951
|
+
|
|
952
|
+
After a non-trivial debugging session, a pitfall you hit, or establishing a
|
|
953
|
+
convention → consider `lumo task memory add`. When you notice a lesson recurring
|
|
954
|
+
across multiple tasks → consider `lumo memory promote`.
|
|
955
|
+
|
|
889
956
|
## Session Management
|
|
890
957
|
|
|
891
958
|
### `lumo session attach <identifier>` — bind the current session to a task
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.memoryProjectAdd = memoryProjectAdd;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
const memory_content_1 = require("../lib/memory-content");
|
|
7
|
+
const resolve_1 = require("../lib/resolve");
|
|
8
|
+
const resolve_bound_task_1 = require("../lib/resolve-bound-task");
|
|
9
|
+
const resolve_project_1 = require("../lib/resolve-project");
|
|
10
|
+
async function memoryProjectAdd(refArg, options) {
|
|
11
|
+
if (!options.category) {
|
|
12
|
+
console.error('Error: --category <trap|decision|convention|procedural> is required.');
|
|
13
|
+
return 1;
|
|
14
|
+
}
|
|
15
|
+
const built = (0, memory_content_1.buildMemoryContent)(options.category, options);
|
|
16
|
+
if (!built.ok) {
|
|
17
|
+
console.error(`Error: ${built.error}`);
|
|
18
|
+
return 1;
|
|
19
|
+
}
|
|
20
|
+
const creds = (0, config_1.readCredentials)();
|
|
21
|
+
if (!creds) {
|
|
22
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
23
|
+
return 1;
|
|
24
|
+
}
|
|
25
|
+
const envUrl = process.env.LUMO_API_URL?.trim();
|
|
26
|
+
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
27
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
28
|
+
let projectId;
|
|
29
|
+
let echoSuffix = '';
|
|
30
|
+
if (refArg) {
|
|
31
|
+
projectId = await (0, resolve_1.resolveProjectId)(base, creds.token, refArg);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const bound = await (0, resolve_bound_task_1.resolveBoundTaskIdentifier)(apiUrl, creds.token);
|
|
35
|
+
if (!bound) {
|
|
36
|
+
console.error('Error: no <project-ref> given and no task bound to this session.\nPass a project, or run `lumo session attach <LUM-N>`.');
|
|
37
|
+
return 1;
|
|
38
|
+
}
|
|
39
|
+
const r = await (0, resolve_project_1.resolveBoundProjectId)(apiUrl, creds.token, bound);
|
|
40
|
+
if (!r.ok) {
|
|
41
|
+
console.error(`Error: ${r.error}`);
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
projectId = r.id;
|
|
45
|
+
echoSuffix = ` (project of bound task ${bound})`;
|
|
46
|
+
}
|
|
47
|
+
let res;
|
|
48
|
+
try {
|
|
49
|
+
res = await fetch(`${base}/api/projects/${encodeURIComponent(projectId)}/memories`, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: { Authorization: `Bearer ${creds.token}`, 'Content-Type': 'application/json' },
|
|
52
|
+
body: JSON.stringify({ category: built.category, content: built.content }),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${err instanceof Error ? err.message : String(err)})`);
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
if (res.status !== 201) {
|
|
60
|
+
let m = null;
|
|
61
|
+
try {
|
|
62
|
+
const b = (await res.json());
|
|
63
|
+
if (typeof b.error === 'string')
|
|
64
|
+
m = b.error;
|
|
65
|
+
}
|
|
66
|
+
catch { /* */ }
|
|
67
|
+
console.error(m ? `Error: ${m}` : `Error: memory add failed (HTTP ${res.status})`);
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
process.stdout.write(`Added ${built.category} PROJECT memory${echoSuffix}\n`);
|
|
71
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.memoryProjectList = memoryProjectList;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
const memory_content_1 = require("../lib/memory-content");
|
|
7
|
+
const resolve_1 = require("../lib/resolve");
|
|
8
|
+
const resolve_bound_task_1 = require("../lib/resolve-bound-task");
|
|
9
|
+
const resolve_project_1 = require("../lib/resolve-project");
|
|
10
|
+
async function memoryProjectList(refArg, options) {
|
|
11
|
+
const limit = options.limit !== undefined ? parseInt(options.limit, 10) : undefined;
|
|
12
|
+
if (limit !== undefined && (Number.isNaN(limit) || limit < 1)) {
|
|
13
|
+
console.error(`Error: invalid --limit "${options.limit}" (expected a positive integer)`);
|
|
14
|
+
return 1;
|
|
15
|
+
}
|
|
16
|
+
const creds = (0, config_1.readCredentials)();
|
|
17
|
+
if (!creds) {
|
|
18
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
const envUrl = process.env.LUMO_API_URL?.trim();
|
|
22
|
+
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
23
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
24
|
+
let projectId;
|
|
25
|
+
if (refArg) {
|
|
26
|
+
projectId = await (0, resolve_1.resolveProjectId)(base, creds.token, refArg);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
const bound = await (0, resolve_bound_task_1.resolveBoundTaskIdentifier)(apiUrl, creds.token);
|
|
30
|
+
if (!bound) {
|
|
31
|
+
console.error('Error: no <project-ref> given and no task bound to this session.\nPass a project, or run `lumo session attach <LUM-N>`.');
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
const r = await (0, resolve_project_1.resolveBoundProjectId)(apiUrl, creds.token, bound);
|
|
35
|
+
if (!r.ok) {
|
|
36
|
+
console.error(`Error: ${r.error}`);
|
|
37
|
+
return 1;
|
|
38
|
+
}
|
|
39
|
+
projectId = r.id;
|
|
40
|
+
}
|
|
41
|
+
let res;
|
|
42
|
+
try {
|
|
43
|
+
res = await fetch(`${base}/api/projects/${encodeURIComponent(projectId)}/memories`, { headers: { Authorization: `Bearer ${creds.token}` } });
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${err instanceof Error ? err.message : String(err)})`);
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
console.error(`Error: memory list failed (HTTP ${res.status})`);
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
let rows = (await res.json()).memories;
|
|
54
|
+
if (options.category)
|
|
55
|
+
rows = rows.filter(m => m.category.toLowerCase() === options.category.toLowerCase());
|
|
56
|
+
if (limit !== undefined)
|
|
57
|
+
rows = rows.slice(0, limit);
|
|
58
|
+
process.stdout.write((0, memory_content_1.formatMemoryList)(rows) + '\n');
|
|
59
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.memoryPromote = memoryPromote;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
async function memoryPromote(memoryId) {
|
|
7
|
+
if (!memoryId) {
|
|
8
|
+
console.error('Error: missing <memoryId>. Usage: lumo memory promote <memoryId>');
|
|
9
|
+
return 1;
|
|
10
|
+
}
|
|
11
|
+
const creds = (0, config_1.readCredentials)();
|
|
12
|
+
if (!creds) {
|
|
13
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
14
|
+
return 1;
|
|
15
|
+
}
|
|
16
|
+
const envUrl = process.env.LUMO_API_URL?.trim();
|
|
17
|
+
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
18
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
19
|
+
let res;
|
|
20
|
+
try {
|
|
21
|
+
res = await fetch(`${base}/api/memories/${encodeURIComponent(memoryId)}`, {
|
|
22
|
+
method: 'PATCH',
|
|
23
|
+
headers: { Authorization: `Bearer ${creds.token}`, 'Content-Type': 'application/json' },
|
|
24
|
+
body: JSON.stringify({ scope: 'PROJECT' }),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${err instanceof Error ? err.message : String(err)})`);
|
|
29
|
+
return 1;
|
|
30
|
+
}
|
|
31
|
+
if (res.status === 404) {
|
|
32
|
+
console.error(`Error: memory ${memoryId} not found`);
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
if (res.status === 409) {
|
|
36
|
+
console.error('Error: this memory is already project-scoped.');
|
|
37
|
+
return 1;
|
|
38
|
+
}
|
|
39
|
+
if (res.status !== 200) {
|
|
40
|
+
let m = null;
|
|
41
|
+
try {
|
|
42
|
+
const b = (await res.json());
|
|
43
|
+
if (typeof b.error === 'string')
|
|
44
|
+
m = b.error;
|
|
45
|
+
}
|
|
46
|
+
catch { /* */ }
|
|
47
|
+
console.error(m ? `Error: ${m}` : `Error: promote failed (HTTP ${res.status})`);
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
process.stdout.write(`Promoted ${memoryId} to PROJECT — every agent on this project now sees it.\n` +
|
|
51
|
+
'Promote only lessons that recur across 2+ tasks.\n');
|
|
52
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.memoryRm = memoryRm;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
async function memoryRm(memoryId, options) {
|
|
7
|
+
if (!memoryId) {
|
|
8
|
+
console.error('Error: missing <memoryId>. Usage: lumo memory rm <memoryId> --yes');
|
|
9
|
+
return 1;
|
|
10
|
+
}
|
|
11
|
+
if (!options.yes) {
|
|
12
|
+
console.error('Error: refusing to delete without --yes. Re-run with --yes to confirm.');
|
|
13
|
+
return 1;
|
|
14
|
+
}
|
|
15
|
+
const creds = (0, config_1.readCredentials)();
|
|
16
|
+
if (!creds) {
|
|
17
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
18
|
+
return 1;
|
|
19
|
+
}
|
|
20
|
+
const envUrl = process.env.LUMO_API_URL?.trim();
|
|
21
|
+
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
22
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
23
|
+
let res;
|
|
24
|
+
try {
|
|
25
|
+
res = await fetch(`${base}/api/memories/${encodeURIComponent(memoryId)}`, {
|
|
26
|
+
method: 'DELETE',
|
|
27
|
+
headers: { Authorization: `Bearer ${creds.token}` },
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${err instanceof Error ? err.message : String(err)})`);
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
if (res.status === 404) {
|
|
35
|
+
console.error(`Error: memory ${memoryId} not found`);
|
|
36
|
+
return 1;
|
|
37
|
+
}
|
|
38
|
+
if (res.status !== 204) {
|
|
39
|
+
console.error(`Error: delete failed (HTTP ${res.status})`);
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
process.stdout.write(`Deleted memory ${memoryId}\n`);
|
|
43
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.memoryTaskAdd = memoryTaskAdd;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
const memory_content_1 = require("../lib/memory-content");
|
|
7
|
+
const resolve_bound_task_1 = require("../lib/resolve-bound-task");
|
|
8
|
+
async function memoryTaskAdd(identifierArg, options) {
|
|
9
|
+
if (!options.category) {
|
|
10
|
+
console.error('Error: --category <trap|decision|convention|procedural> is required.');
|
|
11
|
+
return 1;
|
|
12
|
+
}
|
|
13
|
+
const built = (0, memory_content_1.buildMemoryContent)(options.category, options);
|
|
14
|
+
if (!built.ok) {
|
|
15
|
+
console.error(`Error: ${built.error}`);
|
|
16
|
+
return 1;
|
|
17
|
+
}
|
|
18
|
+
const creds = (0, config_1.readCredentials)();
|
|
19
|
+
if (!creds) {
|
|
20
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
const envUrl = process.env.LUMO_API_URL?.trim();
|
|
24
|
+
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
25
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
26
|
+
// Resolve the target: explicit arg > session-bound task.
|
|
27
|
+
const identifier = identifierArg ?? (await (0, resolve_bound_task_1.resolveBoundTaskIdentifier)(apiUrl, creds.token));
|
|
28
|
+
if (!identifier) {
|
|
29
|
+
console.error('Error: no <LUM-N> given and no task bound to this session.\n' +
|
|
30
|
+
'Pass it explicitly or run `lumo session attach <LUM-N>`.');
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
// LUM-N → task id.
|
|
34
|
+
let taskId;
|
|
35
|
+
try {
|
|
36
|
+
const r = await fetch(`${base}/api/tasks/resolve/${encodeURIComponent(identifier)}`, {
|
|
37
|
+
headers: { Authorization: `Bearer ${creds.token}` },
|
|
38
|
+
});
|
|
39
|
+
if (r.status === 401) {
|
|
40
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
if (r.status === 404) {
|
|
44
|
+
console.error(`Error: task ${identifier} not found in workspace ${creds.workspaceSlug}`);
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
if (!r.ok) {
|
|
48
|
+
console.error(`Error: resolve failed (HTTP ${r.status})`);
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
taskId = (await r.json()).id;
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${err instanceof Error ? err.message : String(err)})`);
|
|
55
|
+
return 1;
|
|
56
|
+
}
|
|
57
|
+
// POST the memory.
|
|
58
|
+
let res;
|
|
59
|
+
try {
|
|
60
|
+
res = await fetch(`${base}/api/tasks/${encodeURIComponent(taskId)}/memories`, {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: { Authorization: `Bearer ${creds.token}`, 'Content-Type': 'application/json' },
|
|
63
|
+
body: JSON.stringify({ category: built.category, content: built.content }),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${err instanceof Error ? err.message : String(err)})`);
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
if (res.status !== 201) {
|
|
71
|
+
let m = null;
|
|
72
|
+
try {
|
|
73
|
+
const b = (await res.json());
|
|
74
|
+
if (typeof b.error === 'string')
|
|
75
|
+
m = b.error;
|
|
76
|
+
}
|
|
77
|
+
catch { /* */ }
|
|
78
|
+
console.error(m ? `Error: ${m}` : `Error: memory add failed (HTTP ${res.status})`);
|
|
79
|
+
return 1;
|
|
80
|
+
}
|
|
81
|
+
process.stdout.write(`Added ${built.category} memory to ${identifier}` +
|
|
82
|
+
`${identifierArg ? '' : ' (from bound session)'}\n` +
|
|
83
|
+
'Tip: only useful for this task? If it generalizes, use `lumo project memory add` or `lumo memory promote` later.\n');
|
|
84
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.memoryTaskList = memoryTaskList;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
const memory_content_1 = require("../lib/memory-content");
|
|
7
|
+
const resolve_bound_task_1 = require("../lib/resolve-bound-task");
|
|
8
|
+
async function memoryTaskList(identifierArg, options) {
|
|
9
|
+
const limit = options.limit !== undefined ? parseInt(options.limit, 10) : undefined;
|
|
10
|
+
if (limit !== undefined && (Number.isNaN(limit) || limit < 1)) {
|
|
11
|
+
console.error(`Error: invalid --limit "${options.limit}" (expected a positive integer)`);
|
|
12
|
+
return 1;
|
|
13
|
+
}
|
|
14
|
+
const creds = (0, config_1.readCredentials)();
|
|
15
|
+
if (!creds) {
|
|
16
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
17
|
+
return 1;
|
|
18
|
+
}
|
|
19
|
+
const envUrl = process.env.LUMO_API_URL?.trim();
|
|
20
|
+
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
21
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
22
|
+
const identifier = identifierArg ?? (await (0, resolve_bound_task_1.resolveBoundTaskIdentifier)(apiUrl, creds.token));
|
|
23
|
+
if (!identifier) {
|
|
24
|
+
console.error('Error: no <LUM-N> given and no task bound to this session.\nPass it explicitly or run `lumo session attach <LUM-N>`.');
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
let taskId;
|
|
28
|
+
try {
|
|
29
|
+
const r = await fetch(`${base}/api/tasks/resolve/${encodeURIComponent(identifier)}`, { headers: { Authorization: `Bearer ${creds.token}` } });
|
|
30
|
+
if (r.status === 404) {
|
|
31
|
+
console.error(`Error: task ${identifier} not found in workspace ${creds.workspaceSlug}`);
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
if (!r.ok) {
|
|
35
|
+
console.error(`Error: resolve failed (HTTP ${r.status})`);
|
|
36
|
+
return 1;
|
|
37
|
+
}
|
|
38
|
+
taskId = (await r.json()).id;
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${err instanceof Error ? err.message : String(err)})`);
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
let res;
|
|
45
|
+
try {
|
|
46
|
+
res = await fetch(`${base}/api/tasks/${encodeURIComponent(taskId)}/memories`, { headers: { Authorization: `Bearer ${creds.token}` } });
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${err instanceof Error ? err.message : String(err)})`);
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
console.error(`Error: memory list failed (HTTP ${res.status})`);
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
let rows = (await res.json()).memories;
|
|
57
|
+
if (options.category)
|
|
58
|
+
rows = rows.filter(m => m.category.toLowerCase() === options.category.toLowerCase());
|
|
59
|
+
if (limit !== undefined)
|
|
60
|
+
rows = rows.slice(0, limit);
|
|
61
|
+
process.stdout.write((0, memory_content_1.formatMemoryList)(rows) + '\n');
|
|
62
|
+
}
|
|
@@ -4,6 +4,7 @@ exports.taskArtifactAdd = taskArtifactAdd;
|
|
|
4
4
|
const config_1 = require("../lib/config");
|
|
5
5
|
const api_1 = require("../lib/api");
|
|
6
6
|
const doc_input_1 = require("../lib/doc-input");
|
|
7
|
+
const agent_1 = require("../lib/agent");
|
|
7
8
|
async function taskArtifactAdd(identifier, options) {
|
|
8
9
|
if (!identifier) {
|
|
9
10
|
console.error('Error: missing <task>. Usage: lumo task artifact add <LUM-42> --kind spec --title "Spec" --file spec.md --source Superpowers');
|
|
@@ -24,6 +25,16 @@ async function taskArtifactAdd(identifier, options) {
|
|
|
24
25
|
console.error('Error: --source is required (the spec-gen framework, formal name e.g. Superpowers, "Spec Kit", BMad, OpenSpec, GSD).');
|
|
25
26
|
return 1;
|
|
26
27
|
}
|
|
28
|
+
const agentRaw = options.agent?.trim();
|
|
29
|
+
if (!agentRaw) {
|
|
30
|
+
console.error(`Error: --agent is required. Valid values: ${agent_1.VALID_AGENT_TOKENS.join(', ')}.`);
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
const agent = (0, agent_1.normalizeAgent)(agentRaw);
|
|
34
|
+
if (!agent) {
|
|
35
|
+
console.error(`Error: invalid --agent "${agentRaw}". Valid values: ${agent_1.VALID_AGENT_TOKENS.join(', ')}.`);
|
|
36
|
+
return 1;
|
|
37
|
+
}
|
|
27
38
|
if (!options.file) {
|
|
28
39
|
console.error('Error: --file <path> is required.');
|
|
29
40
|
return 1;
|
|
@@ -48,7 +59,13 @@ async function taskArtifactAdd(identifier, options) {
|
|
|
48
59
|
const envUrl = process.env.LUMO_API_URL?.trim();
|
|
49
60
|
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
50
61
|
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
51
|
-
const payload = {
|
|
62
|
+
const payload = {
|
|
63
|
+
kind,
|
|
64
|
+
title,
|
|
65
|
+
content,
|
|
66
|
+
source,
|
|
67
|
+
agent,
|
|
68
|
+
};
|
|
52
69
|
let res;
|
|
53
70
|
try {
|
|
54
71
|
res = await fetch(`${base}/api/tasks/${encodeURIComponent(identifier)}/artifacts`, {
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.taskArtifactList = taskArtifactList;
|
|
4
4
|
const config_1 = require("../lib/config");
|
|
5
5
|
const api_1 = require("../lib/api");
|
|
6
|
+
const agent_1 = require("../lib/agent");
|
|
6
7
|
async function taskArtifactList(identifier) {
|
|
7
8
|
if (!identifier) {
|
|
8
9
|
console.error('Error: missing <task>. Usage: lumo task artifact list <LUM-42>');
|
|
@@ -43,6 +44,7 @@ async function taskArtifactList(identifier) {
|
|
|
43
44
|
return;
|
|
44
45
|
}
|
|
45
46
|
for (const a of artifacts) {
|
|
46
|
-
|
|
47
|
+
const agentLabel = agent_1.AGENT_LABELS[a.agent] ?? a.agent;
|
|
48
|
+
process.stdout.write(`${a.id} ${a.kind.padEnd(10)} ${agentLabel.padEnd(15)} ${a.source.padEnd(12)} ${a.title}\n`);
|
|
47
49
|
}
|
|
48
50
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.taskArtifactShow = taskArtifactShow;
|
|
4
4
|
const config_1 = require("../lib/config");
|
|
5
5
|
const api_1 = require("../lib/api");
|
|
6
|
+
const agent_1 = require("../lib/agent");
|
|
6
7
|
async function taskArtifactShow(identifier, artifactId) {
|
|
7
8
|
if (!identifier) {
|
|
8
9
|
console.error('Error: missing <task>. Usage: lumo task artifact show <LUM-42> <artifact-id>');
|
|
@@ -55,6 +56,7 @@ async function taskArtifactShow(identifier, artifactId) {
|
|
|
55
56
|
process.stdout.write(`kind: ${artifact.kind}\n`);
|
|
56
57
|
process.stdout.write(`title: ${artifact.title}\n`);
|
|
57
58
|
process.stdout.write(`source: ${artifact.source}\n`);
|
|
59
|
+
process.stdout.write(`agent: ${agent_1.AGENT_LABELS[artifact.agent] ?? artifact.agent}\n`);
|
|
58
60
|
process.stdout.write(`order: ${artifact.order}\n`);
|
|
59
61
|
process.stdout.write(`task: ${identifier}\n`);
|
|
60
62
|
process.stdout.write(`\n${artifact.content}\n`);
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.taskArtifactUpdate = taskArtifactUpdate;
|
|
4
4
|
const config_1 = require("../lib/config");
|
|
5
5
|
const api_1 = require("../lib/api");
|
|
6
|
+
const agent_1 = require("../lib/agent");
|
|
6
7
|
async function taskArtifactUpdate(identifier, artifactId, options) {
|
|
7
8
|
if (!identifier) {
|
|
8
9
|
console.error('Error: missing <task>. Usage: lumo task artifact update <LUM-42> <artifact-id> --kind plan');
|
|
@@ -37,8 +38,16 @@ async function taskArtifactUpdate(identifier, artifactId, options) {
|
|
|
37
38
|
}
|
|
38
39
|
payload.source = source;
|
|
39
40
|
}
|
|
41
|
+
if (options.agent !== undefined) {
|
|
42
|
+
const agent = (0, agent_1.normalizeAgent)(options.agent);
|
|
43
|
+
if (!agent) {
|
|
44
|
+
console.error(`Error: invalid --agent "${options.agent}". Valid values: ${agent_1.VALID_AGENT_TOKENS.join(', ')}.`);
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
payload.agent = agent;
|
|
48
|
+
}
|
|
40
49
|
if (Object.keys(payload).length === 0) {
|
|
41
|
-
console.error('Error: provide at least one of --kind, --title, or --
|
|
50
|
+
console.error('Error: provide at least one of --kind, --title, --source, or --agent to update.');
|
|
42
51
|
return 1;
|
|
43
52
|
}
|
|
44
53
|
const creds = (0, config_1.readCredentials)();
|
package/dist/cli/src/index.js
CHANGED
|
@@ -55,6 +55,12 @@ const task_figma_add_1 = require("./commands/task-figma-add");
|
|
|
55
55
|
const task_figma_list_1 = require("./commands/task-figma-list");
|
|
56
56
|
const task_figma_rm_1 = require("./commands/task-figma-rm");
|
|
57
57
|
const task_figma_refresh_1 = require("./commands/task-figma-refresh");
|
|
58
|
+
const memory_task_list_1 = require("./commands/memory-task-list");
|
|
59
|
+
const memory_task_add_1 = require("./commands/memory-task-add");
|
|
60
|
+
const memory_project_list_1 = require("./commands/memory-project-list");
|
|
61
|
+
const memory_project_add_1 = require("./commands/memory-project-add");
|
|
62
|
+
const memory_promote_1 = require("./commands/memory-promote");
|
|
63
|
+
const memory_rm_1 = require("./commands/memory-rm");
|
|
58
64
|
const task_artifact_add_1 = require("./commands/task-artifact-add");
|
|
59
65
|
const task_artifact_list_1 = require("./commands/task-artifact-list");
|
|
60
66
|
const task_artifact_show_1 = require("./commands/task-artifact-show");
|
|
@@ -237,6 +243,33 @@ taskFigma
|
|
|
237
243
|
.command('refresh <task>')
|
|
238
244
|
.description('Re-fetch Figma metadata + thumbnail for every link on this task. Per-link failures isolated.')
|
|
239
245
|
.action(wrap((taskId) => (0, task_figma_refresh_1.taskFigmaRefresh)({ identifier: taskId })));
|
|
246
|
+
const taskMemory = task
|
|
247
|
+
.command('memory')
|
|
248
|
+
.description('View and record memories scoped to a task');
|
|
249
|
+
taskMemory
|
|
250
|
+
.command('list [task]')
|
|
251
|
+
.description("List a task's memories. <task> defaults to the session-bound task.")
|
|
252
|
+
.option('--category <cat>', 'Filter by trap|decision|convention|procedural')
|
|
253
|
+
.option('-n, --limit <count>', 'Cap output to the first N rows')
|
|
254
|
+
.action(wrap((taskArg, opts) => (0, memory_task_list_1.memoryTaskList)(taskArg, opts)));
|
|
255
|
+
taskMemory
|
|
256
|
+
.command('add [task]')
|
|
257
|
+
.description('Record a memory on a task (<task> defaults to the bound session). ' +
|
|
258
|
+
'Record only knowledge invisible in the codebase (the why, a runtime gotcha, a non-obvious failure, a non-trivial workflow); skip routine work. ' +
|
|
259
|
+
'Pick --category then its fields.')
|
|
260
|
+
.requiredOption('--category <cat>', 'trap | decision | convention | procedural')
|
|
261
|
+
.option('--trigger <text>', 'trap/procedural: the situation that triggers it')
|
|
262
|
+
.option('--outcome <text>', 'trap: what goes wrong')
|
|
263
|
+
.option('--workaround <text>', 'trap: optional fix')
|
|
264
|
+
.option('--what <text>', 'decision: the decision')
|
|
265
|
+
.option('--why <text>', 'decision: rationale')
|
|
266
|
+
.option('--alternatives <text>', 'decision: optional alternatives considered')
|
|
267
|
+
.option('--implications <text>', 'decision: optional implications')
|
|
268
|
+
.option('--rule <text>', 'convention: the rule')
|
|
269
|
+
.option('--applies <text>', 'convention: where the rule applies')
|
|
270
|
+
.option('--workflow <text>', 'procedural: the workflow name')
|
|
271
|
+
.option('--step <text>', 'procedural: a step (repeatable)', collect, [])
|
|
272
|
+
.action(wrap((taskArg, opts) => (0, memory_task_add_1.memoryTaskAdd)(taskArg, opts)));
|
|
240
273
|
const taskArtifact = task
|
|
241
274
|
.command('artifact')
|
|
242
275
|
.description('Record spec-engineering artifacts (spec / plan / design …) on a task');
|
|
@@ -247,6 +280,7 @@ taskArtifact
|
|
|
247
280
|
.requiredOption('--title <title>', 'Artifact title')
|
|
248
281
|
.requiredOption('--file <path>', 'File whose contents become the artifact body')
|
|
249
282
|
.requiredOption('--source <source>', 'Spec-gen framework, formal name e.g. Superpowers | "Spec Kit" | BMad | OpenSpec | GSD (opaque; quote names with spaces)')
|
|
283
|
+
.requiredOption('--agent <agent>', 'Coding tool that produced the artifact: claude-code | codex | cursor | gemini-cli | github-copilot | windsurf')
|
|
250
284
|
.action(wrap((taskId, options) => (0, task_artifact_add_1.taskArtifactAdd)(taskId, options)));
|
|
251
285
|
taskArtifact
|
|
252
286
|
.command('update <task> <artifact-id>')
|
|
@@ -254,6 +288,7 @@ taskArtifact
|
|
|
254
288
|
.option('--kind <kind>', 'New artifact kind (opaque)')
|
|
255
289
|
.option('--title <title>', 'New artifact title')
|
|
256
290
|
.option('--source <source>', 'New spec-gen framework, formal name e.g. Superpowers | "Spec Kit" | BMad | OpenSpec | GSD (quote names with spaces)')
|
|
291
|
+
.option('--agent <agent>', 'New coding tool: claude-code | codex | cursor | gemini-cli | github-copilot | windsurf')
|
|
257
292
|
.action(wrap((taskId, artifactId, options) => (0, task_artifact_update_1.taskArtifactUpdate)(taskId, artifactId, options)));
|
|
258
293
|
taskArtifact
|
|
259
294
|
.command('list <task>')
|
|
@@ -275,6 +310,43 @@ projectCmd
|
|
|
275
310
|
.command('list')
|
|
276
311
|
.description('List projects in the current workspace. Slug column matches `task create --project <ref>`.')
|
|
277
312
|
.action(wrap(() => (0, project_list_1.projectList)()));
|
|
313
|
+
const projectMemory = projectCmd
|
|
314
|
+
.command('memory')
|
|
315
|
+
.description('View and record PROJECT-scope memories');
|
|
316
|
+
projectMemory
|
|
317
|
+
.command('list [project]')
|
|
318
|
+
.description("List a project's PROJECT-scope memories. <project> defaults to the bound task's project.")
|
|
319
|
+
.option('--category <cat>', 'Filter by trap|decision|convention|procedural')
|
|
320
|
+
.option('-n, --limit <count>', 'Cap output to the first N rows')
|
|
321
|
+
.action(wrap((p, opts) => (0, memory_project_list_1.memoryProjectList)(p, opts)));
|
|
322
|
+
projectMemory
|
|
323
|
+
.command('add [project]')
|
|
324
|
+
.description("Record a PROJECT-scope memory. Use PROJECT scope only when the lesson helps any task in the project. <project> defaults to the bound task's project.")
|
|
325
|
+
.requiredOption('--category <cat>', 'trap | decision | convention | procedural')
|
|
326
|
+
.option('--trigger <text>', 'trap/procedural trigger')
|
|
327
|
+
.option('--outcome <text>', 'trap outcome')
|
|
328
|
+
.option('--workaround <text>', 'trap optional workaround')
|
|
329
|
+
.option('--what <text>', 'decision')
|
|
330
|
+
.option('--why <text>', 'decision rationale')
|
|
331
|
+
.option('--alternatives <text>', 'decision optional alternatives')
|
|
332
|
+
.option('--implications <text>', 'decision optional implications')
|
|
333
|
+
.option('--rule <text>', 'convention rule')
|
|
334
|
+
.option('--applies <text>', 'convention: where it applies')
|
|
335
|
+
.option('--workflow <text>', 'procedural workflow')
|
|
336
|
+
.option('--step <text>', 'procedural step (repeatable)', collect, [])
|
|
337
|
+
.action(wrap((p, opts) => (0, memory_project_add_1.memoryProjectAdd)(p, opts)));
|
|
338
|
+
const memoryCmd = program
|
|
339
|
+
.command('memory')
|
|
340
|
+
.description('Operate on a single memory by id (see `lumo task memory` / `lumo project memory` to list/add)');
|
|
341
|
+
memoryCmd
|
|
342
|
+
.command('promote <memoryId>')
|
|
343
|
+
.description('Promote a TASK memory to PROJECT scope. Only when the lesson recurs across 2+ tasks.')
|
|
344
|
+
.action(wrap((id) => (0, memory_promote_1.memoryPromote)(id)));
|
|
345
|
+
memoryCmd
|
|
346
|
+
.command('rm <memoryId>')
|
|
347
|
+
.description('Delete a memory (hard delete). Requires --yes.')
|
|
348
|
+
.option('--yes', 'Confirm deletion')
|
|
349
|
+
.action(wrap((id, opts) => (0, memory_rm_1.memoryRm)(id, opts)));
|
|
278
350
|
const milestoneCmd = program
|
|
279
351
|
.command('milestone')
|
|
280
352
|
.description('Inspect milestones from the terminal');
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CLI-local agent enum support. The CLI talks to the API over HTTP only and
|
|
4
|
+
* does not depend on @prisma/client, so the enum values + accepted tokens are
|
|
5
|
+
* mirrored here. Keep in sync with prisma `enum TaskArtifactAgent`.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.VALID_AGENT_TOKENS = exports.AGENT_LABELS = void 0;
|
|
9
|
+
exports.normalizeAgent = normalizeAgent;
|
|
10
|
+
/** enum value → display label (matches lib/i18n en.json taskArtifact.agent.*) */
|
|
11
|
+
exports.AGENT_LABELS = {
|
|
12
|
+
CLAUDE_CODE: 'Claude Code',
|
|
13
|
+
CODEX: 'Codex',
|
|
14
|
+
CURSOR: 'Cursor',
|
|
15
|
+
GEMINI_CLI: 'Gemini CLI',
|
|
16
|
+
GITHUB_COPILOT: 'GitHub Copilot',
|
|
17
|
+
WINDSURF: 'Windsurf',
|
|
18
|
+
};
|
|
19
|
+
/** accepted CLI token (normalized) → enum value */
|
|
20
|
+
const TOKEN_TO_ENUM = {
|
|
21
|
+
'claude-code': 'CLAUDE_CODE',
|
|
22
|
+
codex: 'CODEX',
|
|
23
|
+
cursor: 'CURSOR',
|
|
24
|
+
'gemini-cli': 'GEMINI_CLI',
|
|
25
|
+
gemini: 'GEMINI_CLI',
|
|
26
|
+
'github-copilot': 'GITHUB_COPILOT',
|
|
27
|
+
copilot: 'GITHUB_COPILOT',
|
|
28
|
+
windsurf: 'WINDSURF',
|
|
29
|
+
};
|
|
30
|
+
/** Canonical tokens shown in error/usage hints (aliases omitted). */
|
|
31
|
+
exports.VALID_AGENT_TOKENS = [
|
|
32
|
+
'claude-code',
|
|
33
|
+
'codex',
|
|
34
|
+
'cursor',
|
|
35
|
+
'gemini-cli',
|
|
36
|
+
'github-copilot',
|
|
37
|
+
'windsurf',
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Normalize a user-supplied agent string to its enum value, or null if
|
|
41
|
+
* unknown. Case-insensitive; spaces and underscores are treated as hyphens.
|
|
42
|
+
*/
|
|
43
|
+
function normalizeAgent(input) {
|
|
44
|
+
const key = input
|
|
45
|
+
.trim()
|
|
46
|
+
.toLowerCase()
|
|
47
|
+
.replace(/[\s_]+/g, '-');
|
|
48
|
+
return TOKEN_TO_ENUM[key] ?? null;
|
|
49
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Category/field metadata + builders for the `lumo memory` commands.
|
|
3
|
+
// Mirrors the four content shapes validated server-side by parseMemoryContent.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.buildMemoryContent = buildMemoryContent;
|
|
6
|
+
exports.formatMemoryList = formatMemoryList;
|
|
7
|
+
const trimmed = (v) => (v ?? '').trim();
|
|
8
|
+
/**
|
|
9
|
+
* Validate the per-category flags and assemble { category, content }. Required
|
|
10
|
+
* fields must be non-empty; optional string fields are dropped when blank;
|
|
11
|
+
* `--applies` maps to convention `content.scope`; repeated `--step` becomes a
|
|
12
|
+
* trimmed, empty-filtered `steps` array (omitted when empty).
|
|
13
|
+
*/
|
|
14
|
+
function buildMemoryContent(category, flags) {
|
|
15
|
+
const cat = category?.toLowerCase();
|
|
16
|
+
switch (cat) {
|
|
17
|
+
case 'trap': {
|
|
18
|
+
const trigger = trimmed(flags.trigger);
|
|
19
|
+
const outcome = trimmed(flags.outcome);
|
|
20
|
+
const workaround = trimmed(flags.workaround);
|
|
21
|
+
if (!trigger)
|
|
22
|
+
return { ok: false, error: '--trigger is required for category trap.' };
|
|
23
|
+
if (!outcome)
|
|
24
|
+
return { ok: false, error: '--outcome is required for category trap.' };
|
|
25
|
+
return { ok: true, category: 'TRAP', content: { trigger, outcome, ...(workaround ? { workaround } : {}) } };
|
|
26
|
+
}
|
|
27
|
+
case 'decision': {
|
|
28
|
+
const what = trimmed(flags.what);
|
|
29
|
+
const why = trimmed(flags.why);
|
|
30
|
+
const alternatives = trimmed(flags.alternatives);
|
|
31
|
+
const implications = trimmed(flags.implications);
|
|
32
|
+
if (!what)
|
|
33
|
+
return { ok: false, error: '--what is required for category decision.' };
|
|
34
|
+
if (!why)
|
|
35
|
+
return { ok: false, error: '--why is required for category decision.' };
|
|
36
|
+
return {
|
|
37
|
+
ok: true,
|
|
38
|
+
category: 'DECISION',
|
|
39
|
+
content: { what, why, ...(alternatives ? { alternatives } : {}), ...(implications ? { implications } : {}) },
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
case 'convention': {
|
|
43
|
+
const rule = trimmed(flags.rule);
|
|
44
|
+
const applies = trimmed(flags.applies);
|
|
45
|
+
if (!rule)
|
|
46
|
+
return { ok: false, error: '--rule is required for category convention.' };
|
|
47
|
+
if (!applies)
|
|
48
|
+
return { ok: false, error: '--applies is required for category convention (where the rule applies).' };
|
|
49
|
+
return { ok: true, category: 'CONVENTION', content: { rule, scope: applies } };
|
|
50
|
+
}
|
|
51
|
+
case 'procedural': {
|
|
52
|
+
const workflow = trimmed(flags.workflow);
|
|
53
|
+
const trigger = trimmed(flags.trigger);
|
|
54
|
+
const steps = (flags.step ?? []).map(s => s.trim()).filter(Boolean);
|
|
55
|
+
if (!workflow)
|
|
56
|
+
return { ok: false, error: '--workflow is required for category procedural.' };
|
|
57
|
+
if (!trigger)
|
|
58
|
+
return { ok: false, error: '--trigger is required for category procedural.' };
|
|
59
|
+
return { ok: true, category: 'PROCEDURAL', content: { workflow, trigger, ...(steps.length ? { steps } : {}) } };
|
|
60
|
+
}
|
|
61
|
+
default:
|
|
62
|
+
return { ok: false, error: `Unknown --category "${category}". Use trap | decision | convention | procedural.` };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function headline(category, content) {
|
|
66
|
+
const c = (content ?? {});
|
|
67
|
+
const key = category === 'TRAP' ? 'trigger'
|
|
68
|
+
: category === 'DECISION' ? 'what'
|
|
69
|
+
: category === 'CONVENTION' ? 'rule'
|
|
70
|
+
: 'workflow';
|
|
71
|
+
const v = c[key];
|
|
72
|
+
return typeof v === 'string' && v.length > 0 ? v : '(unparseable)';
|
|
73
|
+
}
|
|
74
|
+
/** Fixed-width rows: id SCOPE CATEGORY headline source(auto|manual). */
|
|
75
|
+
function formatMemoryList(rows) {
|
|
76
|
+
if (rows.length === 0)
|
|
77
|
+
return 'No memories.';
|
|
78
|
+
const idW = Math.max(...rows.map(r => r.id.length));
|
|
79
|
+
const scopeW = Math.max(...rows.map(r => r.scope.length));
|
|
80
|
+
const catW = Math.max(...rows.map(r => r.category.length));
|
|
81
|
+
return rows
|
|
82
|
+
.map(r => {
|
|
83
|
+
const src = r.createdByMemberId ? 'manual' : 'auto';
|
|
84
|
+
return `${r.id.padEnd(idW)} ${r.scope.padEnd(scopeW)} ${r.category.padEnd(catW)} ${headline(r.category, r.content)} ${src}`;
|
|
85
|
+
})
|
|
86
|
+
.join('\n');
|
|
87
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveBoundTaskIdentifier = resolveBoundTaskIdentifier;
|
|
4
|
+
const api_1 = require("./api");
|
|
5
|
+
/**
|
|
6
|
+
* The task identifier (LUM-N) the current Claude Code session is bound to, or
|
|
7
|
+
* null when there is no session env, no server-side session row, or no binding.
|
|
8
|
+
* Used by the memory commands to default their target to the bound task.
|
|
9
|
+
*/
|
|
10
|
+
async function resolveBoundTaskIdentifier(apiUrl, token) {
|
|
11
|
+
const sessionId = process.env.CLAUDE_CODE_SESSION_ID;
|
|
12
|
+
if (!sessionId)
|
|
13
|
+
return null;
|
|
14
|
+
const url = `${(0, api_1.trimTrailingSlash)(apiUrl)}/api/sessions/${encodeURIComponent(sessionId)}`;
|
|
15
|
+
let res;
|
|
16
|
+
try {
|
|
17
|
+
res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
if (!res.ok)
|
|
23
|
+
return null;
|
|
24
|
+
try {
|
|
25
|
+
const data = (await res.json());
|
|
26
|
+
return data.taskIdentifier ?? null;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveBoundProjectId = resolveBoundProjectId;
|
|
4
|
+
const api_1 = require("./api");
|
|
5
|
+
/** Resolve the project id of the task bound to this session (via its identifier). */
|
|
6
|
+
async function resolveBoundProjectId(apiUrl, token, boundIdentifier) {
|
|
7
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
8
|
+
let res;
|
|
9
|
+
try {
|
|
10
|
+
res = await fetch(`${base}/api/tasks/by-identifier/${encodeURIComponent(boundIdentifier)}`, {
|
|
11
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
return { ok: false, error: `could not reach Lumo API (${err instanceof Error ? err.message : String(err)})` };
|
|
16
|
+
}
|
|
17
|
+
if (!res.ok)
|
|
18
|
+
return { ok: false, error: `could not resolve bound task ${boundIdentifier} (HTTP ${res.status})` };
|
|
19
|
+
const { task } = (await res.json());
|
|
20
|
+
const id = task.project?.id;
|
|
21
|
+
if (!id)
|
|
22
|
+
return { ok: false, error: `bound task ${boundIdentifier} has no resolvable project` };
|
|
23
|
+
return { ok: true, id };
|
|
24
|
+
}
|