@lumoai/cli 1.2.0 → 1.3.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/README.md +12 -12
- package/assets/skill.md +134 -26
- package/dist/cli/src/commands/setup.js +3 -1
- package/dist/cli/src/commands/task-artifact-add.js +90 -0
- package/dist/cli/src/commands/task-artifact-list.js +48 -0
- package/dist/cli/src/commands/task-artifact-rm.js +72 -0
- package/dist/cli/src/commands/task-artifact-show.js +61 -0
- package/dist/cli/src/commands/task-context.js +11 -2
- package/dist/cli/src/commands/task-figma-add.js +29 -0
- package/dist/cli/src/commands/task-figma-list.js +31 -0
- package/dist/cli/src/commands/task-figma-refresh.js +39 -0
- package/dist/cli/src/commands/task-figma-rm.js +18 -0
- package/dist/cli/src/commands/task-show.js +3 -2
- package/dist/cli/src/index.js +54 -0
- package/dist/cli/src/lib/figma-api.js +60 -0
- package/dist/cli/src/lib/hooks-template.js +3 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -91,18 +91,18 @@ lumo task update LUM-42 --status done
|
|
|
91
91
|
|
|
92
92
|
## Commands
|
|
93
93
|
|
|
94
|
-
| Group
|
|
95
|
-
|
|
|
96
|
-
| `setup`
|
|
97
|
-
| `auth`
|
|
98
|
-
| `whoami`
|
|
99
|
-
| `update`
|
|
100
|
-
| `task`
|
|
101
|
-
| `session`
|
|
102
|
-
| `project`
|
|
103
|
-
| `milestone`
|
|
104
|
-
| `sprint`
|
|
105
|
-
| `doc`
|
|
94
|
+
| Group | Highlights |
|
|
95
|
+
| ----------- | ---------------------------------------------------------------------- |
|
|
96
|
+
| `setup` | Install SKILL.md + hooks into `~/.claude/` or `./.claude/` |
|
|
97
|
+
| `auth` | `login`, `logout` |
|
|
98
|
+
| `whoami` | Show current identity + workspace |
|
|
99
|
+
| `update` | Self-update to the latest npm release |
|
|
100
|
+
| `task` | `create`, `update`, `list`, `show`, `comment`, `context` |
|
|
101
|
+
| `session` | `attach`, `status`, `detach` (binds Claude Code sessions) |
|
|
102
|
+
| `project` | `list` |
|
|
103
|
+
| `milestone` | `create`, `update`, `list`, `show`, `delete` |
|
|
104
|
+
| `sprint` | `create`, `start`, `close`, `list`, `show`, `add`, `remove`, `summary` |
|
|
105
|
+
| `doc` | `create`, `update`, `list`, `show`, `move`, `bind`, `share` |
|
|
106
106
|
|
|
107
107
|
Every command accepts `--help` for full flags and examples:
|
|
108
108
|
|
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 初始化".'
|
|
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", "remove artifact", "delete artifact", "spec artifact", "record spec", "attach spec", "attach plan", "记录 spec", "挂 spec", "查看 artifact", "删除 artifact", figma, attach figma, figma link, 关联 figma, 设计稿, figma design.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
## Prerequisites
|
|
@@ -187,7 +187,7 @@ Pure flag-driven update. Provide at least one of:
|
|
|
187
187
|
| `-p, --priority <lvl>` | enum | `low \| medium \| high \| urgent`. |
|
|
188
188
|
| `-a, --assignee <ref>` | string | `me`, an email, or a member name. `--assignee ""` clears the field. |
|
|
189
189
|
| `--milestone <ref>` | string | Milestone name (case-insensitive) within the task's project. `--milestone ""` unbinds. |
|
|
190
|
-
| `--sprint <ref>` | string | Sprint number or UUID to bind the task to. `--sprint ""` clears the current sprint binding (idempotent when already unbound).
|
|
190
|
+
| `--sprint <ref>` | string | Sprint number or UUID to bind the task to. `--sprint ""` clears the current sprint binding (idempotent when already unbound). |
|
|
191
191
|
| `--tag <name>` | string (repeatable) | **Bulk replace** the tag set by name. Creates tag if missing. Max 20. Mutually exclusive with `--add-tag*` / `--remove-tag*`. |
|
|
192
192
|
| `--tag-id <cuid>` | string (repeatable) | **Bulk replace** the tag set by id. Max 20. Mutually exclusive with `--add-tag*` / `--remove-tag*`. |
|
|
193
193
|
| `--add-tag <name>` | string (repeatable) | Attach tag by name (find-or-create). Max 20. |
|
|
@@ -295,6 +295,114 @@ lumo task comment LUM-42 "Reproduced the redirect bug on staging — Safari only
|
|
|
295
295
|
|
|
296
296
|
The CLI does not support @-mention chip syntax. If the user wants to ping someone, they should comment from the web UI.
|
|
297
297
|
|
|
298
|
+
### Task ↔ Spec Artifacts
|
|
299
|
+
|
|
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
|
+
|
|
302
|
+
#### `lumo task artifact add <task> --kind <kind> --title <title> --file <path> [--source <source>]`
|
|
303
|
+
|
|
304
|
+
Attaches an artifact to a task. `--kind`, `--title`, `--source` are stored verbatim — **`kind` is opaque** (no enumeration; `spec` / `plan` / `requirements` / anything is accepted). `--file` supplies the body (file contents). `--source` defaults to `claude-code`. 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
|
+
|
|
306
|
+
```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 superpowers
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Output: `Added [spec] "Spec" to LUM-42`
|
|
312
|
+
|
|
313
|
+
#### `lumo task artifact list <task>`
|
|
314
|
+
|
|
315
|
+
Lists artifacts on the task in order: `<id> <kind> <source> <title>`. Prints `No artifacts on <task>` when there are none.
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
lumo task artifact list LUM-42
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
#### `lumo task artifact show <task> <artifact-id>`
|
|
322
|
+
|
|
323
|
+
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`.
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
lumo task artifact show LUM-42 cma_xxx
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
#### `lumo task artifact rm <task> <artifact-id> --yes`
|
|
330
|
+
|
|
331
|
+
Deletes an artifact from a task. Irreversible — `--yes` is required and there is no interactive prompt (agent-friendly). On success prints `Removed <artifact-id> from <task>`. A 404 (task or artifact missing in this workspace) prints the server message and exits 1.
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
lumo task artifact rm LUM-42 cma_xxx --yes
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
When to suggest: after running a spec/plan workflow in Claude Code, offer to record the product(s) with `task artifact add` (one call per artifact). Use `task artifact list` to see what's already recorded, `task artifact show` to inspect a single artifact's content, and `task artifact rm` to drop one that's wrong or stale.
|
|
338
|
+
|
|
339
|
+
### Task ↔ Figma Designs
|
|
340
|
+
|
|
341
|
+
#### `lumo task figma add <task> <url>` — attach a Figma file/frame
|
|
342
|
+
|
|
343
|
+
Fetches file name, frame name, and thumbnail via Figma OAuth and stores them
|
|
344
|
+
on the task.
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
lumo task figma add LUM-42 "https://www.figma.com/design/abc123/Onboarding?node-id=1-234"
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
If the URL omits `node-id`, the link is stored as file-level; the CLI prints
|
|
351
|
+
a `(file-level, thumbnail from "...")` note showing the auto-picked
|
|
352
|
+
representative frame.
|
|
353
|
+
|
|
354
|
+
Idempotent — re-adding the same URL within 7 days returns the existing row
|
|
355
|
+
without re-calling Figma.
|
|
356
|
+
|
|
357
|
+
**Not connected?** Errors with:
|
|
358
|
+
|
|
359
|
+
```
|
|
360
|
+
✗ You haven't connected Figma yet.
|
|
361
|
+
Run: open https://www.uselumo.ai/settings/integrations
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
#### `lumo task figma list <task>` — list attachments
|
|
365
|
+
|
|
366
|
+
```
|
|
367
|
+
$ lumo task figma list LUM-42
|
|
368
|
+
cfl_xxx1 Onboarding Welcome screen 2026-05-28
|
|
369
|
+
cfl_xxx2 Design System Button / Primary 2026-05-27
|
|
370
|
+
cfl_xxx3 Onboarding (file-level) 2026-05-20 ⚠ thumbnail stale
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
`⚠ thumbnail stale` appears when `thumbnailFetchedAt > 25 days`.
|
|
374
|
+
|
|
375
|
+
#### `lumo task figma rm <task> <link-id-or-url>` — remove an attachment
|
|
376
|
+
|
|
377
|
+
Accepts a `cfl_*` cuid or the original URL. Idempotent (`Not linked: ...` + exit 0 when no match).
|
|
378
|
+
|
|
379
|
+
#### `lumo task figma refresh <task>` — manual refresh
|
|
380
|
+
|
|
381
|
+
Re-fetches metadata + thumbnail for every Figma link on the task. Per-link
|
|
382
|
+
failures are isolated.
|
|
383
|
+
|
|
384
|
+
```
|
|
385
|
+
$ lumo task figma refresh LUM-42
|
|
386
|
+
Refreshed 3 Figma links on LUM-42
|
|
387
|
+
✓ Onboarding · Welcome screen
|
|
388
|
+
✓ Onboarding · Sign-up form
|
|
389
|
+
✗ Design System · Button (file not accessible)
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### When to suggest the `task figma` commands
|
|
393
|
+
|
|
394
|
+
- User pastes a Figma URL or mentions a design ("here's the mock", "the
|
|
395
|
+
Figma is at...").
|
|
396
|
+
- User asks "what designs are linked to this task" or "show me the Figma
|
|
397
|
+
for LUM-42".
|
|
398
|
+
- After implementing a UI change, suggest `lumo task figma refresh <task>`
|
|
399
|
+
if the user mentioned updating the Figma source.
|
|
400
|
+
|
|
401
|
+
OAuth connection lives in the Web UI at
|
|
402
|
+
`/settings/integrations`. The CLI does **not** have a `figma auth`
|
|
403
|
+
command; if the user tries `task figma add` without connecting, the error
|
|
404
|
+
message directs them to the Web UI.
|
|
405
|
+
|
|
298
406
|
### `lumo project list` — list projects in the workspace
|
|
299
407
|
|
|
300
408
|
Prints `<slug> <Display Name>` lines. The slug column matches the `--project <ref>` argument accepted by `task create`, so users (and you) can copy a slug straight from this output into a create command.
|
|
@@ -629,11 +737,11 @@ If user creates a doc with `--task LUM-N` and the current Claude Code session is
|
|
|
629
737
|
|
|
630
738
|
Prints fixed-width rows: `<NUMBER> <STATUS> <start> <end> <name>`, sorted newest-first (server sort).
|
|
631
739
|
|
|
632
|
-
| Flag | Type | Notes
|
|
633
|
-
| ---------------------- | ------- |
|
|
740
|
+
| Flag | Type | Notes |
|
|
741
|
+
| ---------------------- | ------- | ----------------------------------------------------- |
|
|
634
742
|
| `--team <ref>` | string | Team name or slug. Required in multi-team workspaces. |
|
|
635
|
-
| `-s, --status <value>` | enum | `draft \| active \| closed`.
|
|
636
|
-
| `-n, --limit <count>` | integer | Cap output to the first N rows.
|
|
743
|
+
| `-s, --status <value>` | enum | `draft \| active \| closed`. |
|
|
744
|
+
| `-n, --limit <count>` | integer | Cap output to the first N rows. |
|
|
637
745
|
|
|
638
746
|
```bash
|
|
639
747
|
lumo sprint list
|
|
@@ -645,12 +753,12 @@ When to suggest: user asks "what sprints do we have", "which sprint is active",
|
|
|
645
753
|
|
|
646
754
|
### `lumo sprint create [flags]` — create a sprint
|
|
647
755
|
|
|
648
|
-
| Flag
|
|
649
|
-
|
|
|
650
|
-
| `--team <ref>`
|
|
651
|
-
| `--start <date>`
|
|
652
|
-
| `--end <date>`
|
|
653
|
-
| `-n, --name <>`
|
|
756
|
+
| Flag | Type | Notes |
|
|
757
|
+
| ---------------- | ------ | ----------------------------------------------------- |
|
|
758
|
+
| `--team <ref>` | string | Team name or slug. Required in multi-team workspaces. |
|
|
759
|
+
| `--start <date>` | string | **Required.** YYYY-MM-DD. |
|
|
760
|
+
| `--end <date>` | string | **Required.** YYYY-MM-DD. |
|
|
761
|
+
| `-n, --name <>` | string | Optional. Server fills a default name when omitted. |
|
|
654
762
|
|
|
655
763
|
```bash
|
|
656
764
|
lumo sprint create --start 2026-06-01 --end 2026-06-14
|
|
@@ -679,12 +787,12 @@ When to suggest: user asks "what's in sprint 3", "show me the current sprint", "
|
|
|
679
787
|
|
|
680
788
|
Updates sprint metadata. At least one flag required. **No `--status` flag** — use `lumo sprint start` / `lumo sprint close` to transition status.
|
|
681
789
|
|
|
682
|
-
| Flag
|
|
683
|
-
|
|
|
684
|
-
| `--team <ref>`
|
|
685
|
-
| `-n, --name <>`
|
|
686
|
-
| `--start <date>`
|
|
687
|
-
| `--end <date>`
|
|
790
|
+
| Flag | Type | Notes |
|
|
791
|
+
| ---------------- | ------ | --------------------------------------------------------------- |
|
|
792
|
+
| `--team <ref>` | string | Required when identifier is a number in a multi-team workspace. |
|
|
793
|
+
| `-n, --name <>` | string | New name. Cannot be empty. |
|
|
794
|
+
| `--start <date>` | string | YYYY-MM-DD. `--start ""` clears. |
|
|
795
|
+
| `--end <date>` | string | YYYY-MM-DD. `--end ""` clears. |
|
|
688
796
|
|
|
689
797
|
```bash
|
|
690
798
|
lumo sprint update 3 --name "Sprint 4 (extended)"
|
|
@@ -717,11 +825,11 @@ When to suggest: user says "start the sprint", "开始冲刺", "kick off sprint
|
|
|
717
825
|
|
|
718
826
|
Handles unfinished tasks based on flags. Without flags: closes only if all tasks are done; otherwise prints a list of unfinished tasks and refuses.
|
|
719
827
|
|
|
720
|
-
| Flag
|
|
721
|
-
|
|
|
722
|
-
| `--move-all`
|
|
723
|
-
| `--backlog-all
|
|
724
|
-
| `--yes`
|
|
828
|
+
| Flag | Type | Notes |
|
|
829
|
+
| --------------- | ------- | -------------------------------------------------------------------------------- |
|
|
830
|
+
| `--move-all` | boolean | Move all unfinished tasks to the next sprint. Requires `--yes`. |
|
|
831
|
+
| `--backlog-all` | boolean | Remove all unfinished tasks from the sprint (send to backlog). Requires `--yes`. |
|
|
832
|
+
| `--yes` | boolean | Required when `--move-all` or `--backlog-all` is given. |
|
|
725
833
|
|
|
726
834
|
```bash
|
|
727
835
|
lumo sprint close 3 # fails if unfinished tasks exist
|
|
@@ -735,9 +843,9 @@ When to suggest: user says "close the sprint", "关闭冲刺", "end sprint 3", "
|
|
|
735
843
|
|
|
736
844
|
Prints the AI-generated retrospective summary for the sprint. A 404 response means no summary has been generated yet ("no summary yet").
|
|
737
845
|
|
|
738
|
-
| Flag | Type | Notes
|
|
739
|
-
| --------- | ------- |
|
|
740
|
-
| `--retry` | boolean | Queue a regeneration (async, server returns 202).
|
|
846
|
+
| Flag | Type | Notes |
|
|
847
|
+
| --------- | ------- | ------------------------------------------------- |
|
|
848
|
+
| `--retry` | boolean | Queue a regeneration (async, server returns 202). |
|
|
741
849
|
|
|
742
850
|
```bash
|
|
743
851
|
lumo sprint summary 3
|
|
@@ -148,7 +148,9 @@ function isLumoOnPath() {
|
|
|
148
148
|
try {
|
|
149
149
|
// `command -v` is a POSIX shell builtin (`execSync` defaults to /bin/sh
|
|
150
150
|
// on Unix). `where` is a cmd.exe builtin on Windows.
|
|
151
|
-
(0, child_process_1.execSync)(process.platform === 'win32' ? 'where lumo' : 'command -v lumo', {
|
|
151
|
+
(0, child_process_1.execSync)(process.platform === 'win32' ? 'where lumo' : 'command -v lumo', {
|
|
152
|
+
stdio: 'pipe',
|
|
153
|
+
});
|
|
152
154
|
return true;
|
|
153
155
|
}
|
|
154
156
|
catch {
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.taskArtifactAdd = taskArtifactAdd;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
const doc_input_1 = require("../lib/doc-input");
|
|
7
|
+
async function taskArtifactAdd(identifier, options) {
|
|
8
|
+
if (!identifier) {
|
|
9
|
+
console.error('Error: missing <task>. Usage: lumo task artifact add <LUM-42> --kind spec --title "Spec" --file spec.md');
|
|
10
|
+
return 1;
|
|
11
|
+
}
|
|
12
|
+
const kind = options.kind?.trim();
|
|
13
|
+
const title = options.title?.trim();
|
|
14
|
+
if (!kind) {
|
|
15
|
+
console.error('Error: --kind is required and cannot be empty.');
|
|
16
|
+
return 1;
|
|
17
|
+
}
|
|
18
|
+
if (!title) {
|
|
19
|
+
console.error('Error: --title is required and cannot be empty.');
|
|
20
|
+
return 1;
|
|
21
|
+
}
|
|
22
|
+
if (!options.file) {
|
|
23
|
+
console.error('Error: --file <path> is required.');
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
let content;
|
|
27
|
+
try {
|
|
28
|
+
content = await (0, doc_input_1.readFileUtf8)(options.file);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
console.error(`Error: could not read file ${options.file}`);
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
if (content.trim().length === 0) {
|
|
35
|
+
console.error(`Error: file ${options.file} is empty.`);
|
|
36
|
+
return 1;
|
|
37
|
+
}
|
|
38
|
+
const creds = (0, config_1.readCredentials)();
|
|
39
|
+
if (!creds) {
|
|
40
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
const envUrl = process.env.LUMO_API_URL?.trim();
|
|
44
|
+
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
45
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
46
|
+
const payload = { kind, title, content };
|
|
47
|
+
if (options.source?.trim())
|
|
48
|
+
payload.source = options.source.trim();
|
|
49
|
+
let res;
|
|
50
|
+
try {
|
|
51
|
+
res = await fetch(`${base}/api/tasks/${encodeURIComponent(identifier)}/artifacts`, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: {
|
|
54
|
+
Authorization: `Bearer ${creds.token}`,
|
|
55
|
+
'Content-Type': 'application/json',
|
|
56
|
+
},
|
|
57
|
+
body: JSON.stringify(payload),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
62
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
if (res.status === 401) {
|
|
66
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
if (res.status === 404) {
|
|
70
|
+
console.error(`Error: task ${identifier} not found in workspace ${creds.workspaceSlug}`);
|
|
71
|
+
return 1;
|
|
72
|
+
}
|
|
73
|
+
if (res.status !== 201) {
|
|
74
|
+
let serverMsg = null;
|
|
75
|
+
try {
|
|
76
|
+
const errBody = (await res.json());
|
|
77
|
+
if (typeof errBody.error === 'string')
|
|
78
|
+
serverMsg = errBody.error;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* not JSON */
|
|
82
|
+
}
|
|
83
|
+
console.error(serverMsg
|
|
84
|
+
? `Error: ${serverMsg}`
|
|
85
|
+
: `Error: artifact add failed (HTTP ${res.status})`);
|
|
86
|
+
return 1;
|
|
87
|
+
}
|
|
88
|
+
const data = (await res.json());
|
|
89
|
+
process.stdout.write(`Added [${data.artifact.kind}] "${data.artifact.title}" to ${identifier}\n`);
|
|
90
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.taskArtifactList = taskArtifactList;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
async function taskArtifactList(identifier) {
|
|
7
|
+
if (!identifier) {
|
|
8
|
+
console.error('Error: missing <task>. Usage: lumo task artifact list <LUM-42>');
|
|
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/tasks/${encodeURIComponent(identifier)}/artifacts`, { headers: { Authorization: `Bearer ${creds.token}` } });
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
25
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
if (res.status === 401) {
|
|
29
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
if (res.status === 404) {
|
|
33
|
+
console.error(`Error: task ${identifier} not found in workspace ${creds.workspaceSlug}`);
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
console.error(`Error: artifact list failed (HTTP ${res.status})`);
|
|
38
|
+
return 1;
|
|
39
|
+
}
|
|
40
|
+
const { artifacts } = (await res.json());
|
|
41
|
+
if (artifacts.length === 0) {
|
|
42
|
+
process.stdout.write(`No artifacts on ${identifier}\n`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
for (const a of artifacts) {
|
|
46
|
+
process.stdout.write(`${a.id} ${a.kind.padEnd(10)} ${a.source.padEnd(12)} ${a.title}\n`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.taskArtifactRm = taskArtifactRm;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
async function taskArtifactRm(identifier, artifactId, options) {
|
|
7
|
+
if (!identifier) {
|
|
8
|
+
console.error('Error: missing <task>. Usage: lumo task artifact rm <LUM-42> <artifact-id> --yes');
|
|
9
|
+
return 1;
|
|
10
|
+
}
|
|
11
|
+
if (!artifactId) {
|
|
12
|
+
console.error('Error: missing <artifact-id>. Usage: lumo task artifact rm <LUM-42> <artifact-id> --yes');
|
|
13
|
+
return 1;
|
|
14
|
+
}
|
|
15
|
+
if (!options.yes) {
|
|
16
|
+
console.error('Error: --yes is required (artifact deletion is irreversible).');
|
|
17
|
+
return 1;
|
|
18
|
+
}
|
|
19
|
+
const creds = (0, config_1.readCredentials)();
|
|
20
|
+
if (!creds) {
|
|
21
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
22
|
+
return 1;
|
|
23
|
+
}
|
|
24
|
+
const envUrl = process.env.LUMO_API_URL?.trim();
|
|
25
|
+
const apiUrl = envUrl && envUrl.length > 0 ? envUrl : creds.apiUrl;
|
|
26
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
27
|
+
let res;
|
|
28
|
+
try {
|
|
29
|
+
res = await fetch(`${base}/api/tasks/${encodeURIComponent(identifier)}/artifacts/${encodeURIComponent(artifactId)}`, {
|
|
30
|
+
method: 'DELETE',
|
|
31
|
+
headers: { Authorization: `Bearer ${creds.token}` },
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
36
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
37
|
+
return 1;
|
|
38
|
+
}
|
|
39
|
+
if (res.status === 401) {
|
|
40
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
if (res.status === 404) {
|
|
44
|
+
let serverMsg = `task ${identifier} or artifact ${artifactId} not found in workspace ${creds.workspaceSlug}`;
|
|
45
|
+
try {
|
|
46
|
+
const errBody = (await res.json());
|
|
47
|
+
if (typeof errBody.error === 'string')
|
|
48
|
+
serverMsg = errBody.error;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
/* not JSON */
|
|
52
|
+
}
|
|
53
|
+
console.error(`Error: ${serverMsg}`);
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
if (res.status !== 204) {
|
|
57
|
+
let serverMsg = null;
|
|
58
|
+
try {
|
|
59
|
+
const errBody = (await res.json());
|
|
60
|
+
if (typeof errBody.error === 'string')
|
|
61
|
+
serverMsg = errBody.error;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
/* not JSON */
|
|
65
|
+
}
|
|
66
|
+
console.error(serverMsg
|
|
67
|
+
? `Error: ${serverMsg}`
|
|
68
|
+
: `Error: artifact rm failed (HTTP ${res.status})`);
|
|
69
|
+
return 1;
|
|
70
|
+
}
|
|
71
|
+
process.stdout.write(`Removed ${artifactId} from ${identifier}\n`);
|
|
72
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.taskArtifactShow = taskArtifactShow;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
async function taskArtifactShow(identifier, artifactId) {
|
|
7
|
+
if (!identifier) {
|
|
8
|
+
console.error('Error: missing <task>. Usage: lumo task artifact show <LUM-42> <artifact-id>');
|
|
9
|
+
return 1;
|
|
10
|
+
}
|
|
11
|
+
if (!artifactId) {
|
|
12
|
+
console.error('Error: missing <artifact-id>. Usage: lumo task artifact show <LUM-42> <artifact-id>');
|
|
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/tasks/${encodeURIComponent(identifier)}/artifacts/${encodeURIComponent(artifactId)}`, { headers: { Authorization: `Bearer ${creds.token}` } });
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
29
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
if (res.status === 401) {
|
|
33
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
if (res.status === 404) {
|
|
37
|
+
let serverMsg = `task ${identifier} or artifact ${artifactId} not found in workspace ${creds.workspaceSlug}`;
|
|
38
|
+
try {
|
|
39
|
+
const errBody = (await res.json());
|
|
40
|
+
if (typeof errBody.error === 'string')
|
|
41
|
+
serverMsg = errBody.error;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
/* not JSON */
|
|
45
|
+
}
|
|
46
|
+
console.error(`Error: ${serverMsg}`);
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
console.error(`Error: artifact show failed (HTTP ${res.status})`);
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
const { artifact } = (await res.json());
|
|
54
|
+
process.stdout.write(`id: ${artifact.id}\n`);
|
|
55
|
+
process.stdout.write(`kind: ${artifact.kind}\n`);
|
|
56
|
+
process.stdout.write(`title: ${artifact.title}\n`);
|
|
57
|
+
process.stdout.write(`source: ${artifact.source}\n`);
|
|
58
|
+
process.stdout.write(`order: ${artifact.order}\n`);
|
|
59
|
+
process.stdout.write(`task: ${identifier}\n`);
|
|
60
|
+
process.stdout.write(`\n${artifact.content}\n`);
|
|
61
|
+
}
|
|
@@ -60,8 +60,9 @@ function formatTaskContextMarkdown(data, now) {
|
|
|
60
60
|
: '';
|
|
61
61
|
lines.push(`**Milestone**: ${data.task.milestone.name} (${data.task.milestone.status}${target})`);
|
|
62
62
|
}
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
const body = data.task.descriptionMarkdown ?? data.task.description;
|
|
64
|
+
if (body && body.trim().length > 0) {
|
|
65
|
+
lines.push(`**Description**: ${body}`);
|
|
65
66
|
}
|
|
66
67
|
lines.push('');
|
|
67
68
|
// Frontload memory before sessions: it's cold context the agent should see
|
|
@@ -78,6 +79,14 @@ function formatTaskContextMarkdown(data, now) {
|
|
|
78
79
|
lines.push(data.webLinkSection.trimEnd());
|
|
79
80
|
lines.push('');
|
|
80
81
|
}
|
|
82
|
+
if (data.figmaSection && data.figmaSection.trim().length > 0) {
|
|
83
|
+
lines.push(data.figmaSection.trimEnd());
|
|
84
|
+
lines.push('');
|
|
85
|
+
}
|
|
86
|
+
if (data.artifactSection && data.artifactSection.trim().length > 0) {
|
|
87
|
+
lines.push(data.artifactSection.trimEnd());
|
|
88
|
+
lines.push('');
|
|
89
|
+
}
|
|
81
90
|
if (data.sessions.length === 0) {
|
|
82
91
|
lines.push('## Previous Sessions (0)');
|
|
83
92
|
lines.push('');
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.taskFigmaAdd = taskFigmaAdd;
|
|
4
|
+
const figma_api_1 = require("../lib/figma-api");
|
|
5
|
+
async function taskFigmaAdd(args) {
|
|
6
|
+
try {
|
|
7
|
+
const { link } = await (0, figma_api_1.addFigmaLink)(args.identifier, args.url);
|
|
8
|
+
const label = link.frameName
|
|
9
|
+
? `${link.fileName} · ${link.frameName}`
|
|
10
|
+
: `${link.fileName}${link.nodeId === '' ? ' (file-level)' : ''}`;
|
|
11
|
+
console.log(`Linked ${args.identifier} ↔ Figma "${label}"`);
|
|
12
|
+
console.log(` ${link.url}`);
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
const err = e;
|
|
17
|
+
if (err.code === 'figma_not_connected') {
|
|
18
|
+
console.error(`✗ You haven't connected Figma yet.`);
|
|
19
|
+
console.error(` Run: open https://www.uselumo.ai/settings/integrations`);
|
|
20
|
+
return 1;
|
|
21
|
+
}
|
|
22
|
+
if (err.code === 'figma_needs_reauth') {
|
|
23
|
+
console.error(`✗ Your Figma token is no longer valid. Please reconnect.`);
|
|
24
|
+
console.error(` Run: open https://www.uselumo.ai/settings/integrations`);
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
throw e; // let wrap() in cli/src/index.ts log and exit 1
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.taskFigmaList = taskFigmaList;
|
|
4
|
+
const figma_api_1 = require("../lib/figma-api");
|
|
5
|
+
const STALE_MS = 25 * 24 * 3600 * 1000;
|
|
6
|
+
function formatRow(id, fileName, frame, synced, stale) {
|
|
7
|
+
return [
|
|
8
|
+
id.padEnd(10),
|
|
9
|
+
fileName.padEnd(20),
|
|
10
|
+
frame.padEnd(24),
|
|
11
|
+
synced.padEnd(12),
|
|
12
|
+
stale ? '⚠ thumbnail stale' : '',
|
|
13
|
+
]
|
|
14
|
+
.join(' ')
|
|
15
|
+
.trimEnd();
|
|
16
|
+
}
|
|
17
|
+
async function taskFigmaList(args) {
|
|
18
|
+
const { links } = await (0, figma_api_1.listFigmaLinks)(args.identifier);
|
|
19
|
+
if (links.length === 0) {
|
|
20
|
+
console.log('No Figma links attached.');
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
for (const link of links) {
|
|
24
|
+
const frame = link.frameName ?? '(file-level)';
|
|
25
|
+
const synced = link.lastSyncedAt.slice(0, 10);
|
|
26
|
+
const stale = link.thumbnailFetchedAt === null ||
|
|
27
|
+
Date.now() - new Date(link.thumbnailFetchedAt).getTime() > STALE_MS;
|
|
28
|
+
console.log(formatRow(link.id, link.fileName, frame, synced, stale && !!link.thumbnailUrl));
|
|
29
|
+
}
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.taskFigmaRefresh = taskFigmaRefresh;
|
|
4
|
+
const figma_api_1 = require("../lib/figma-api");
|
|
5
|
+
async function taskFigmaRefresh(args) {
|
|
6
|
+
try {
|
|
7
|
+
const { links } = await (0, figma_api_1.listFigmaLinks)(args.identifier);
|
|
8
|
+
const byId = new Map(links.map(l => [l.id, l]));
|
|
9
|
+
const { results } = await (0, figma_api_1.refreshFigmaLinks)(args.identifier);
|
|
10
|
+
if (results.length === 0) {
|
|
11
|
+
console.log(`No Figma links attached to ${args.identifier}.`);
|
|
12
|
+
return 0;
|
|
13
|
+
}
|
|
14
|
+
console.log(`Refreshed ${results.length} Figma links on ${args.identifier}`);
|
|
15
|
+
for (const r of results) {
|
|
16
|
+
const link = byId.get(r.id);
|
|
17
|
+
const label = link
|
|
18
|
+
? link.frameName
|
|
19
|
+
? `${link.fileName} · ${link.frameName}`
|
|
20
|
+
: `${link.fileName} (file-level)`
|
|
21
|
+
: r.id;
|
|
22
|
+
if (r.ok)
|
|
23
|
+
console.log(` ✓ ${label}`);
|
|
24
|
+
else
|
|
25
|
+
console.log(` ✗ ${label} (${r.error})`);
|
|
26
|
+
}
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
const err = e;
|
|
31
|
+
if (err.code === 'figma_not_connected' ||
|
|
32
|
+
err.code === 'figma_needs_reauth') {
|
|
33
|
+
console.error(`✗ Figma is not connected (or token expired). Reconnect:`);
|
|
34
|
+
console.error(` open https://www.uselumo.ai/settings/integrations`);
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
throw e;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.taskFigmaRm = taskFigmaRm;
|
|
4
|
+
const figma_api_1 = require("../lib/figma-api");
|
|
5
|
+
async function taskFigmaRm(args) {
|
|
6
|
+
const { links } = await (0, figma_api_1.listFigmaLinks)(args.identifier);
|
|
7
|
+
const match = links.find(l => l.id === args.linkIdOrUrl || l.url === args.linkIdOrUrl);
|
|
8
|
+
if (!match) {
|
|
9
|
+
console.log(`Not linked: ${args.linkIdOrUrl}`);
|
|
10
|
+
return 0;
|
|
11
|
+
}
|
|
12
|
+
await (0, figma_api_1.removeFigmaLink)(args.identifier, match.id);
|
|
13
|
+
const label = match.frameName
|
|
14
|
+
? `${match.fileName} · ${match.frameName}`
|
|
15
|
+
: `${match.fileName} (file-level)`;
|
|
16
|
+
console.log(`Removed Figma link from ${args.identifier}: "${label}"`);
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
@@ -24,10 +24,11 @@ function formatTaskShow(task) {
|
|
|
24
24
|
lines.push('Assignee: (unassigned)');
|
|
25
25
|
}
|
|
26
26
|
lines.push(`URL: ${task.url}`);
|
|
27
|
-
|
|
27
|
+
const body = task.descriptionMarkdown ?? task.description;
|
|
28
|
+
if (body && body.trim().length > 0) {
|
|
28
29
|
lines.push('');
|
|
29
30
|
lines.push('Description:');
|
|
30
|
-
for (const line of
|
|
31
|
+
for (const line of body.split('\n')) {
|
|
31
32
|
lines.push(` ${line}`);
|
|
32
33
|
}
|
|
33
34
|
}
|
package/dist/cli/src/index.js
CHANGED
|
@@ -51,6 +51,14 @@ const task_update_1 = require("./commands/task-update");
|
|
|
51
51
|
const task_list_1 = require("./commands/task-list");
|
|
52
52
|
const task_show_1 = require("./commands/task-show");
|
|
53
53
|
const task_comment_1 = require("./commands/task-comment");
|
|
54
|
+
const task_figma_add_1 = require("./commands/task-figma-add");
|
|
55
|
+
const task_figma_list_1 = require("./commands/task-figma-list");
|
|
56
|
+
const task_figma_rm_1 = require("./commands/task-figma-rm");
|
|
57
|
+
const task_figma_refresh_1 = require("./commands/task-figma-refresh");
|
|
58
|
+
const task_artifact_add_1 = require("./commands/task-artifact-add");
|
|
59
|
+
const task_artifact_list_1 = require("./commands/task-artifact-list");
|
|
60
|
+
const task_artifact_show_1 = require("./commands/task-artifact-show");
|
|
61
|
+
const task_artifact_rm_1 = require("./commands/task-artifact-rm");
|
|
54
62
|
const project_list_1 = require("./commands/project-list");
|
|
55
63
|
const milestone_list_1 = require("./commands/milestone-list");
|
|
56
64
|
const milestone_create_1 = require("./commands/milestone-create");
|
|
@@ -206,6 +214,52 @@ task
|
|
|
206
214
|
.option('--tag-id <cuid>', 'Attach tag by id (repeatable)', collect, [])
|
|
207
215
|
.option('--sprint <ref>', 'Sprint number or UUID to add the task to after creation')
|
|
208
216
|
.action(wrap((title, options) => (0, task_create_1.taskCreate)(title, options)));
|
|
217
|
+
const taskFigma = task
|
|
218
|
+
.command('figma')
|
|
219
|
+
.description('Attach Figma file/frame links to a task');
|
|
220
|
+
taskFigma
|
|
221
|
+
.command('add <task> <url>')
|
|
222
|
+
.description('Attach a Figma file or frame URL to the given task. Fetches file/frame name + thumbnail via Figma OAuth.')
|
|
223
|
+
.action(wrap((taskId, url) => (0, task_figma_add_1.taskFigmaAdd)({ identifier: taskId, url: url })));
|
|
224
|
+
taskFigma
|
|
225
|
+
.command('list <task>')
|
|
226
|
+
.description('List Figma links attached to a task')
|
|
227
|
+
.action(wrap((taskId) => (0, task_figma_list_1.taskFigmaList)({ identifier: taskId })));
|
|
228
|
+
taskFigma
|
|
229
|
+
.command('rm <task> <link-id-or-url>')
|
|
230
|
+
.description('Remove a Figma link from a task (idempotent)')
|
|
231
|
+
.action(wrap((taskId, linkIdOrUrl) => (0, task_figma_rm_1.taskFigmaRm)({
|
|
232
|
+
identifier: taskId,
|
|
233
|
+
linkIdOrUrl: linkIdOrUrl,
|
|
234
|
+
})));
|
|
235
|
+
taskFigma
|
|
236
|
+
.command('refresh <task>')
|
|
237
|
+
.description('Re-fetch Figma metadata + thumbnail for every link on this task. Per-link failures isolated.')
|
|
238
|
+
.action(wrap((taskId) => (0, task_figma_refresh_1.taskFigmaRefresh)({ identifier: taskId })));
|
|
239
|
+
const taskArtifact = task
|
|
240
|
+
.command('artifact')
|
|
241
|
+
.description('Record spec-engineering artifacts (spec / plan / design …) on a task');
|
|
242
|
+
taskArtifact
|
|
243
|
+
.command('add <task>')
|
|
244
|
+
.description('Attach an artifact to a task. --kind/--title/--source are stored verbatim; --file supplies the content.')
|
|
245
|
+
.requiredOption('--kind <kind>', 'Artifact kind, e.g. spec | plan | design (opaque)')
|
|
246
|
+
.requiredOption('--title <title>', 'Artifact title')
|
|
247
|
+
.requiredOption('--file <path>', 'File whose contents become the artifact body')
|
|
248
|
+
.option('--source <source>', 'Producer label (default: claude-code)')
|
|
249
|
+
.action(wrap((taskId, options) => (0, task_artifact_add_1.taskArtifactAdd)(taskId, options)));
|
|
250
|
+
taskArtifact
|
|
251
|
+
.command('list <task>')
|
|
252
|
+
.description('List artifacts attached to a task, in order')
|
|
253
|
+
.action(wrap((taskId) => (0, task_artifact_list_1.taskArtifactList)(taskId)));
|
|
254
|
+
taskArtifact
|
|
255
|
+
.command('show <task> <artifact-id>')
|
|
256
|
+
.description('Show one artifact (key:value header + content body). Find <artifact-id> in `artifact list` column 1.')
|
|
257
|
+
.action(wrap((taskId, artifactId) => (0, task_artifact_show_1.taskArtifactShow)(taskId, artifactId)));
|
|
258
|
+
taskArtifact
|
|
259
|
+
.command('rm <task> <artifact-id>')
|
|
260
|
+
.description('Delete an artifact from a task. Irreversible — requires --yes.')
|
|
261
|
+
.option('--yes', 'Confirm deletion (required, no interactive prompt)')
|
|
262
|
+
.action(wrap((taskId, artifactId, options) => (0, task_artifact_rm_1.taskArtifactRm)(taskId, artifactId, options)));
|
|
209
263
|
const projectCmd = program
|
|
210
264
|
.command('project')
|
|
211
265
|
.description('Inspect projects from the terminal');
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.addFigmaLink = addFigmaLink;
|
|
4
|
+
exports.listFigmaLinks = listFigmaLinks;
|
|
5
|
+
exports.removeFigmaLink = removeFigmaLink;
|
|
6
|
+
exports.refreshFigmaLinks = refreshFigmaLinks;
|
|
7
|
+
const api_1 = require("./api");
|
|
8
|
+
const config_1 = require("./config");
|
|
9
|
+
function buildErr(status, body) {
|
|
10
|
+
const err = Object.assign(new Error(body.error ?? `HTTP ${status}`), {
|
|
11
|
+
status,
|
|
12
|
+
});
|
|
13
|
+
if (body.code !== undefined)
|
|
14
|
+
err.code = body.code;
|
|
15
|
+
return err;
|
|
16
|
+
}
|
|
17
|
+
async function call(path, init) {
|
|
18
|
+
const creds = (0, config_1.readCredentials)();
|
|
19
|
+
if (!creds)
|
|
20
|
+
throw new Error('Not logged in. Run: lumo auth login');
|
|
21
|
+
const apiUrl = (0, api_1.resolveApiUrl)();
|
|
22
|
+
const res = await fetch(`${(0, api_1.trimTrailingSlash)(apiUrl)}${path}`, {
|
|
23
|
+
...init,
|
|
24
|
+
headers: {
|
|
25
|
+
Authorization: `Bearer ${creds.token}`,
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
...(init.headers ?? {}),
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
let parsed = {};
|
|
32
|
+
try {
|
|
33
|
+
parsed = (await res.json());
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
/* non-JSON body */
|
|
37
|
+
}
|
|
38
|
+
throw buildErr(res.status, parsed);
|
|
39
|
+
}
|
|
40
|
+
return (await res.json());
|
|
41
|
+
}
|
|
42
|
+
async function addFigmaLink(identifier, url) {
|
|
43
|
+
return call(`/api/tasks/${encodeURIComponent(identifier)}/figma`, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
body: JSON.stringify({ url }),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async function listFigmaLinks(identifier) {
|
|
49
|
+
return call(`/api/tasks/${encodeURIComponent(identifier)}/figma`, {
|
|
50
|
+
method: 'GET',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async function removeFigmaLink(identifier, linkId) {
|
|
54
|
+
return call(`/api/tasks/${encodeURIComponent(identifier)}/figma/${encodeURIComponent(linkId)}`, { method: 'DELETE' });
|
|
55
|
+
}
|
|
56
|
+
async function refreshFigmaLinks(identifier) {
|
|
57
|
+
return call(`/api/tasks/${encodeURIComponent(identifier)}/figma/refresh`, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -49,7 +49,9 @@ function buildLumoHookFragment() {
|
|
|
49
49
|
return { hooks };
|
|
50
50
|
}
|
|
51
51
|
function mergeLumoHooks(existing) {
|
|
52
|
-
const base = existing
|
|
52
|
+
const base = existing
|
|
53
|
+
? JSON.parse(JSON.stringify(existing))
|
|
54
|
+
: {};
|
|
53
55
|
if (!base.hooks)
|
|
54
56
|
base.hooks = {};
|
|
55
57
|
const stats = { addedEvents: [], alreadyPresent: [] };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumoai/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Lumo CLI — manage tasks and sessions from the terminal",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "cli@uselumo.ai",
|
|
@@ -40,9 +40,11 @@
|
|
|
40
40
|
"prepublishOnly": "npm run clean && npm run build"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"commander": "^13.1.0"
|
|
43
|
+
"commander": "^13.1.0",
|
|
44
|
+
"markdown-it": "^14.1.1"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
47
|
+
"@types/markdown-it": "^14.1.2",
|
|
46
48
|
"@types/node": "^25",
|
|
47
49
|
"typescript": "^5"
|
|
48
50
|
}
|