@cyber-dash-tech/revela 0.7.9 → 0.8.2
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 +9 -11
- package/README.zh-CN.md +9 -11
- package/lib/agents/research-prompt.ts +4 -4
- package/lib/commands/edit.ts +3 -6
- package/lib/commands/help.ts +2 -2
- package/lib/commands/init.ts +6 -5
- package/lib/commands/review.ts +5 -8
- package/lib/decks-state.ts +58 -12
- package/lib/edit/deck-state.ts +10 -10
- package/lib/edit/open.ts +32 -7
- package/lib/edit/resolve-deck.ts +20 -75
- package/lib/edit/server.ts +207 -54
- package/package.json +1 -1
- package/plugin.ts +40 -3
- package/skill/SKILL.md +31 -26
- package/tools/decks.ts +13 -11
- package/tools/edit.ts +6 -10
- package/tools/media-batch-save.ts +1 -1
- package/tools/media-save.ts +1 -1
- package/tools/research-images-list.ts +1 -1
- package/tools/research-save.ts +10 -10
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**English** | [中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](tests/) [](https://opencode.ai) [](https://bun.sh)
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<img src="assets/img/logo.png" alt="Revela" width="800" />
|
|
@@ -112,7 +112,7 @@ Create a 6-slide HTML deck on humanoid robotics supply chains. Cite the main mar
|
|
|
112
112
|
Before the agent writes `decks/humanoid-robotics.html`, it must update `DECKS.json` through the `revela-decks` tool with the active deck, confirmed slide specs, layouts, components, and computed `writeReadiness.status: ready`. You can ask for an explicit readiness check at any time:
|
|
113
113
|
|
|
114
114
|
```text
|
|
115
|
-
/revela review
|
|
115
|
+
/revela review
|
|
116
116
|
```
|
|
117
117
|
|
|
118
118
|
Export when needed, either manually or by asking the agent to export:
|
|
@@ -138,9 +138,9 @@ Disable presentation mode when done:
|
|
|
138
138
|
/revela disable disable presentation mode
|
|
139
139
|
|
|
140
140
|
/revela init initialize or refresh workspace DECKS.json
|
|
141
|
-
/revela review
|
|
141
|
+
/revela review review current deck readiness before writing HTML
|
|
142
142
|
/revela remember <text> save an explicit user/workflow preference
|
|
143
|
-
/revela edit
|
|
143
|
+
/revela edit open visual editor for the only deck in decks/
|
|
144
144
|
|
|
145
145
|
/revela designs list installed designs
|
|
146
146
|
/revela designs <name> activate a design
|
|
@@ -185,7 +185,7 @@ Revela uses a workspace-root `DECKS.json` file for cross-session continuity and
|
|
|
185
185
|
It has two jobs:
|
|
186
186
|
|
|
187
187
|
- workspace memory: stable project context, source materials, explicit user preferences, deck history, and open questions
|
|
188
|
-
- active deck spec: current deck
|
|
188
|
+
- active deck spec: current deck output path, prerequisites, research plan, per-slide content, layouts, components, evidence, visuals, blockers, and write readiness
|
|
189
189
|
|
|
190
190
|
`DECKS.json` is the source of truth for workspace memory and deck readiness.
|
|
191
191
|
|
|
@@ -198,7 +198,7 @@ Create or refresh it with:
|
|
|
198
198
|
Review the current deck state with:
|
|
199
199
|
|
|
200
200
|
```text
|
|
201
|
-
/revela review
|
|
201
|
+
/revela review
|
|
202
202
|
```
|
|
203
203
|
|
|
204
204
|
`/revela review` does not write the final HTML deck. It reads and updates `DECKS.json` through the `revela-decks` tool, checks what is missing, and sets `writeReadiness.status` to `ready` only when the deck is ready to generate.
|
|
@@ -593,19 +593,17 @@ A custom domain is a folder containing `INDUSTRY.md`.
|
|
|
593
593
|
|
|
594
594
|
## Visual Editing
|
|
595
595
|
|
|
596
|
-
Open the visual editor for the
|
|
596
|
+
Open the visual editor for the only HTML deck in `decks/`:
|
|
597
597
|
|
|
598
598
|
```text
|
|
599
599
|
/revela edit
|
|
600
|
-
/revela edit my-deck
|
|
601
|
-
/revela edit decks/my-deck.html
|
|
602
600
|
```
|
|
603
601
|
|
|
604
|
-
|
|
602
|
+
`/revela edit` does not accept a target in Revela 0.8. If `decks/` contains exactly one `.html` file, Revela opens it. If `decks/` contains zero or multiple `.html` files, Revela asks you to generate a deck first or move extra decks to separate workspaces.
|
|
605
603
|
|
|
606
604
|
The editor opens in your browser. Use `Ctrl`/`Cmd` + click to reference deck elements, write a natural-language comment, then send it back to OpenCode. Revela sends a structured edit prompt that includes the deck file, slide context, selected element metadata, and your comment.
|
|
607
605
|
|
|
608
|
-
LLM tool equivalent: `revela-edit` with
|
|
606
|
+
LLM tool equivalent: `revela-edit` with no target. This lets the agent open the same editor when you say things like “I want to edit the deck”.
|
|
609
607
|
|
|
610
608
|
`/revela edit` prepares minimal `DECKS.json` state for the existing HTML deck if needed, so the normal deck write gate can still protect `decks/*.html` while allowing targeted edits.
|
|
611
609
|
|
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | **中文**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](tests/) [](https://opencode.ai) [](https://bun.sh)
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<img src="assets/img/logo.png" alt="Revela" width="800" />
|
|
@@ -111,7 +111,7 @@ Create a 6-slide HTML deck on humanoid robotics supply chains. Cite the main mar
|
|
|
111
111
|
在 agent 写入 `decks/humanoid-robotics.html` 之前,它必须通过 `revela-decks` 工具更新 `DECKS.json`:记录 active deck、已确认的逐页规格、layout、component,并由工具计算出 `writeReadiness.status: ready`。你也可以随时显式触发 readiness 检查:
|
|
112
112
|
|
|
113
113
|
```text
|
|
114
|
-
/revela review
|
|
114
|
+
/revela review
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
需要导出时,可以手动调用,也可以让 agent 直接导出:
|
|
@@ -137,9 +137,9 @@ Create a 6-slide HTML deck on humanoid robotics supply chains. Cite the main mar
|
|
|
137
137
|
/revela disable 关闭演示文稿模式
|
|
138
138
|
|
|
139
139
|
/revela init 初始化或刷新工作区 DECKS.json
|
|
140
|
-
/revela review
|
|
140
|
+
/revela review 写 HTML 前检查当前 deck readiness
|
|
141
141
|
/revela remember <text> 保存明确的用户/工作流偏好
|
|
142
|
-
/revela edit
|
|
142
|
+
/revela edit 打开 decks/ 下唯一 deck 的可视化编辑器
|
|
143
143
|
|
|
144
144
|
/revela designs 列出已安装 design
|
|
145
145
|
/revela designs <name> 激活某个 design
|
|
@@ -184,7 +184,7 @@ Revela 使用工作区根目录的 `DECKS.json` 做跨会话记忆和 deck 生
|
|
|
184
184
|
它有两个职责:
|
|
185
185
|
|
|
186
186
|
- 工作区记忆:稳定项目背景、源材料、明确用户偏好、历史 deck 和开放问题
|
|
187
|
-
- active deck 规格:当前 deck
|
|
187
|
+
- active deck 规格:当前 deck 输出路径、前置条件、research plan、逐页内容、layout、component、证据、视觉需求、blocker 和 write readiness
|
|
188
188
|
|
|
189
189
|
`DECKS.json` 是工作区记忆和 deck readiness 的 source of truth。
|
|
190
190
|
|
|
@@ -197,7 +197,7 @@ Revela 使用工作区根目录的 `DECKS.json` 做跨会话记忆和 deck 生
|
|
|
197
197
|
检查当前 deck 状态:
|
|
198
198
|
|
|
199
199
|
```text
|
|
200
|
-
/revela review
|
|
200
|
+
/revela review
|
|
201
201
|
```
|
|
202
202
|
|
|
203
203
|
`/revela review` 不会写最终 HTML deck。它通过 `revela-decks` 工具读取并更新 `DECKS.json`,检查缺失项,并且只有在 deck 真的可以生成时才把 `writeReadiness.status` 设为 `ready`。
|
|
@@ -558,19 +558,17 @@ Prompt 注入规则:
|
|
|
558
558
|
|
|
559
559
|
## 可视化编辑
|
|
560
560
|
|
|
561
|
-
|
|
561
|
+
打开 `decks/` 下唯一 HTML deck 的可视化编辑器:
|
|
562
562
|
|
|
563
563
|
```text
|
|
564
564
|
/revela edit
|
|
565
|
-
/revela edit my-deck
|
|
566
|
-
/revela edit decks/my-deck.html
|
|
567
565
|
```
|
|
568
566
|
|
|
569
|
-
|
|
567
|
+
Revela 0.8 中 `/revela edit` 不接受 target。如果 `decks/` 下正好有一个 `.html` 文件,Revela 会打开它。如果 `decks/` 下没有 HTML 或有多个 HTML,Revela 会提示先生成 deck,或把多余 deck 移到独立 workspace。
|
|
570
568
|
|
|
571
569
|
编辑器会在浏览器中打开。使用 `Ctrl`/`Cmd` + 点击 deck 元素来引用它们,写一段自然语言评论,然后发送回 OpenCode。Revela 会把 deck 文件、slide 上下文、选中元素 metadata 和你的评论整理成结构化 edit prompt。
|
|
572
570
|
|
|
573
|
-
对应的 LLM tool:`revela-edit
|
|
571
|
+
对应的 LLM tool:`revela-edit`,不需要 target。因此当你说“我要编辑这个 deck”时,agent 也可以主动打开同一个编辑器。
|
|
574
572
|
|
|
575
573
|
如果已有 HTML deck 缺少 `DECKS.json` 状态,`/revela edit` 会自动准备最小 deck state,让正常的 `decks/*.html` 写入门禁仍然生效,同时允许后续精准修改。
|
|
576
574
|
|
|
@@ -29,7 +29,7 @@ Given a research brief specifying your topic and axis, you will:
|
|
|
29
29
|
2. Use \`DECKS.json\` through \`revela-decks\` as the workspace material index when it exists
|
|
30
30
|
3. Run a lightweight workspace freshness check when needed
|
|
31
31
|
4. Search the web for current data, reports, and case studies when the brief requires it
|
|
32
|
-
5. Write all findings to ONE structured file: \`researches/{topic-
|
|
32
|
+
5. Write all findings to ONE structured file: \`researches/{topic-key}/{axis-name}.md\`
|
|
33
33
|
6. Return a brief summary of what you found
|
|
34
34
|
|
|
35
35
|
---
|
|
@@ -37,7 +37,7 @@ Given a research brief specifying your topic and axis, you will:
|
|
|
37
37
|
## Step 1 — Research brief and workspace memory
|
|
38
38
|
|
|
39
39
|
Start from the research brief supplied by the primary agent. It should include:
|
|
40
|
-
- shared topic
|
|
40
|
+
- shared topic key
|
|
41
41
|
- your axis filename
|
|
42
42
|
- the specific question for this axis
|
|
43
43
|
- time period, geography, and evidence standard
|
|
@@ -100,7 +100,7 @@ Search strategy:
|
|
|
100
100
|
|
|
101
101
|
Use **\`revela-research-save\`** to write ONE file with all your findings.
|
|
102
102
|
|
|
103
|
-
- \`topic\`: kebab-case
|
|
103
|
+
- \`topic\`: kebab-case topic key shared across all agents for this presentation
|
|
104
104
|
- \`filename\`: your axis name (e.g. \`market-data\`, \`catl-profile\`, \`tech-trends\`)
|
|
105
105
|
- \`content\`: structured findings using the four sections below
|
|
106
106
|
- \`sources\`: list of all URLs and filenames used
|
|
@@ -140,7 +140,7 @@ Content rules:
|
|
|
140
140
|
After writing the file, return this summary (do NOT include the raw data):
|
|
141
141
|
|
|
142
142
|
\`\`\`
|
|
143
|
-
Research complete: {axis-name} → researches/{topic-
|
|
143
|
+
Research complete: {axis-name} → researches/{topic-key}/{axis-name}.md
|
|
144
144
|
|
|
145
145
|
Key findings (3–5, most argument-worthy only):
|
|
146
146
|
- {1–2 sentence highlight with source}
|
package/lib/commands/edit.ts
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
import { openEditableDeck } from "../edit/open"
|
|
2
2
|
|
|
3
3
|
export async function handleEdit(
|
|
4
|
-
input: string,
|
|
5
4
|
options: { client: any; sessionID: string; workspaceRoot: string },
|
|
6
5
|
send: (text: string) => Promise<void>,
|
|
7
6
|
): Promise<void> {
|
|
8
|
-
const target = input.trim()
|
|
9
|
-
|
|
10
7
|
try {
|
|
11
|
-
const result = openEditableDeck(
|
|
8
|
+
const result = openEditableDeck("", {
|
|
12
9
|
client: options.client,
|
|
13
10
|
sessionID: options.sessionID,
|
|
14
11
|
workspaceRoot: options.workspaceRoot,
|
|
15
12
|
})
|
|
16
13
|
|
|
17
14
|
await send(
|
|
18
|
-
`Opened visual editor for deck
|
|
19
|
-
`File: \`${result.deck.file}
|
|
15
|
+
`Opened visual editor for the only deck in \`decks/\`.\n` +
|
|
16
|
+
`File: \`${result.deck.file}\`\n` +
|
|
20
17
|
`${result.stateNote}\n` +
|
|
21
18
|
`URL: ${result.url}\n\n` +
|
|
22
19
|
`Use Ctrl/Cmd + click in the browser to reference elements, write a comment, then send comments. Revela mode has been enabled for the edit prompt.`
|
package/lib/commands/help.ts
CHANGED
|
@@ -27,8 +27,8 @@ export async function handleHelp(
|
|
|
27
27
|
`\`/revela enable\` — enable slide generation mode\n` +
|
|
28
28
|
`\`/revela disable\` — disable slide generation mode\n` +
|
|
29
29
|
`\`/revela init\` — initialize or refresh workspace DECKS.json\n` +
|
|
30
|
-
`\`/revela review
|
|
31
|
-
`\`/revela edit
|
|
30
|
+
`\`/revela review\` — review current deck readiness before writing HTML\n` +
|
|
31
|
+
`\`/revela edit\` — open visual editor for the only deck in decks/\n` +
|
|
32
32
|
`\`/revela remember <text>\` — save an explicit preference to DECKS.json\n` +
|
|
33
33
|
`\`/revela designs\` — list installed designs\n` +
|
|
34
34
|
`\`/revela designs <name>\` — activate a design\n` +
|
package/lib/commands/init.ts
CHANGED
|
@@ -16,9 +16,9 @@ export function buildInitPrompt({
|
|
|
16
16
|
Goal:
|
|
17
17
|
- Build or update ${DECKS_STATE_FILE}, the workspace-level machine-readable state file for slide deck work.
|
|
18
18
|
- Use the \`revela-decks\` tool for state updates. Do not write or patch ${DECKS_STATE_FILE} directly.
|
|
19
|
-
- Capture stable project context, available source materials,
|
|
20
|
-
- Do not treat initialization as permission to write a slide deck;
|
|
21
|
-
- ${DECKS_STATE_FILE} is the source of truth for
|
|
19
|
+
- Capture stable project context, available source materials, the current deck spec, slide plan, and open questions for future sessions.
|
|
20
|
+
- Do not treat initialization as permission to write a slide deck; the current deck must pass a later readiness review.
|
|
21
|
+
- ${DECKS_STATE_FILE} is the source of truth for the single current workspace deck.
|
|
22
22
|
|
|
23
23
|
Current state:
|
|
24
24
|
- ${mode}
|
|
@@ -40,12 +40,12 @@ Workflow:
|
|
|
40
40
|
- \`presentations/**/*.html\`
|
|
41
41
|
- \`decks/**/*.pdf\`
|
|
42
42
|
- \`slides/**/*.pdf\`
|
|
43
|
-
Run these searches only inside the current workspace root. These are generated/output decks, not necessarily source materials.
|
|
43
|
+
Run these searches only inside the current workspace root. These are generated/output decks, not necessarily source materials. If \`decks/\` contains exactly one HTML file, treat it as the current deck artifact. If \`decks/\` contains multiple HTML files, stop and ask the user to move extra decks to separate workspaces before adopting one.
|
|
44
44
|
3. Select the files that look most relevant for future slide decks. Prioritize source decks, PDFs, Word docs, spreadsheets, CSVs, Markdown, text notes, and relevant existing generated decks.
|
|
45
45
|
4. For selected PDF/PPTX/DOCX/XLSX files, call \`revela-extract-document-materials\` before deciding what to summarize.
|
|
46
46
|
5. Read only the materials needed to form a conservative workspace memory. Do not exhaustively read every file if the workspace is large.
|
|
47
47
|
6. Call \`revela-decks\` with action \`init\` to create ${DECKS_STATE_FILE} if needed.
|
|
48
|
-
7. If this conversation already contains a concrete deck task, call \`revela-decks\` with action \`upsertDeck\` and later \`upsertSlides\` only for explicit deck spec information. Do not mark readiness ready during init.
|
|
48
|
+
7. If this conversation already contains a concrete deck task, call \`revela-decks\` with action \`upsertDeck\` and later \`upsertSlides\` only for explicit deck spec information. Do not pass or ask for a deck key; the tool uses the workspace folder name internally. Do not mark readiness ready during init.
|
|
49
49
|
8. Report what was initialized or updated and list any open questions.
|
|
50
50
|
|
|
51
51
|
Memory rules:
|
|
@@ -54,6 +54,7 @@ Memory rules:
|
|
|
54
54
|
- Do not infer personal preferences from one-off requests.
|
|
55
55
|
- Do not store secrets, credentials, API keys, tokens, account details, or sensitive personal information.
|
|
56
56
|
- Do not mark writeReadiness as ready during init unless the current deck has already passed an explicit \`revela-decks\` review.
|
|
57
|
+
- Treat this workspace as a single deck project. If the user wants another deck, guide them to create another workspace/folder rather than adding a second deck record.
|
|
57
58
|
- If new evidence conflicts with existing memory, preserve both briefly and add an Open Question instead of silently overwriting.
|
|
58
59
|
|
|
59
60
|
Start now by scanning the workspace.`
|
package/lib/commands/review.ts
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import { DECKS_STATE_FILE } from "../decks-state"
|
|
2
2
|
|
|
3
3
|
export function buildReviewPrompt({
|
|
4
|
-
slug,
|
|
5
4
|
exists,
|
|
6
5
|
workspaceRoot,
|
|
7
6
|
}: {
|
|
8
|
-
slug?: string
|
|
9
7
|
exists: boolean
|
|
10
8
|
workspaceRoot?: string
|
|
11
9
|
}): string {
|
|
12
|
-
const target = slug?.trim()
|
|
13
|
-
const deckTarget = target ? `the deck slug or output path matching \`${target}\`` : "the current active deck"
|
|
14
10
|
const state = exists
|
|
15
11
|
? `${DECKS_STATE_FILE} exists. Read it through the revela-decks tool.`
|
|
16
12
|
: `${DECKS_STATE_FILE} does not exist yet. Create it through the revela-decks tool if there is enough deck context.`
|
|
@@ -18,7 +14,7 @@ export function buildReviewPrompt({
|
|
|
18
14
|
return `Review Revela deck write readiness.
|
|
19
15
|
|
|
20
16
|
Goal:
|
|
21
|
-
- Use ${DECKS_STATE_FILE} as the source of truth for whether
|
|
17
|
+
- Use ${DECKS_STATE_FILE} as the source of truth for whether the current workspace deck is ready to be written to \`decks/*.html\`.
|
|
22
18
|
- Preserve the deck spec for future sessions: every slide's content, layout, components, evidence, visuals, and production status.
|
|
23
19
|
- Do not write, patch, or directly edit ${DECKS_STATE_FILE}. Use the \`revela-decks\` tool for all state changes.
|
|
24
20
|
- Let \`revela-decks\` action \`review\` compute writeReadiness; do not manually set readiness to ready.
|
|
@@ -34,11 +30,11 @@ Workspace boundary rules:
|
|
|
34
30
|
- For Glob/file searches, use the current workspace as the search root. Do not set the search root to a parent directory or home directory.
|
|
35
31
|
|
|
36
32
|
Workflow:
|
|
37
|
-
1. Call \`revela-decks\` with action \`read\` for
|
|
38
|
-
2. If no
|
|
33
|
+
1. Call \`revela-decks\` with action \`read\` for the current workspace deck.
|
|
34
|
+
2. If no current deck exists but the conversation contains enough deck context, call \`revela-decks\` action \`upsertDeck\` with goal, outputPath, theme, requiredInputs, and researchPlan. Do not invent or ask for a deck key; the tool uses the workspace folder name internally.
|
|
39
35
|
3. If a user-confirmed slide plan is available, call \`revela-decks\` action \`upsertSlides\` with every slide's title, purpose, layout, components, structured content, evidence, visuals, and status.
|
|
40
36
|
4. Only set requiredInputs fields true when explicit conversation state, files read, research findings read, selected design, fetched layouts/components, or user confirmation supports them. Do not infer completion.
|
|
41
|
-
5. Call \`revela-decks\` action \`review
|
|
37
|
+
5. Call \`revela-decks\` action \`review\`. The tool computes and writes \`writeReadiness\` for the current workspace deck.
|
|
42
38
|
6. Briefly report whether the deck is ready. If blocked, list the exact blockers returned by the tool.
|
|
43
39
|
|
|
44
40
|
Minimum conditions for \`ready\`:
|
|
@@ -53,6 +49,7 @@ Minimum conditions for \`ready\`:
|
|
|
53
49
|
|
|
54
50
|
Rules:
|
|
55
51
|
- Do not write or overwrite \`decks/*.html\` during review.
|
|
52
|
+
- Treat the workspace as one deck project. If the user wants another deck, tell them to use a separate workspace/folder.
|
|
56
53
|
- Do not write, patch, or directly edit ${DECKS_STATE_FILE}; use \`revela-decks\`.
|
|
57
54
|
- Do not store secrets, credentials, tokens, or sensitive personal information.
|
|
58
55
|
- Do not add inferred user preferences to long-term preference state.
|
package/lib/decks-state.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"
|
|
2
|
-
import { basename, dirname, join } from "path"
|
|
2
|
+
import { basename, dirname, join, resolve } from "path"
|
|
3
3
|
|
|
4
4
|
export const DECKS_STATE_FILE = "DECKS.json"
|
|
5
5
|
|
|
@@ -154,6 +154,29 @@ export function createEmptyDecksState(): DecksState {
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
export function workspaceDeckSlug(workspaceRoot: string): string {
|
|
158
|
+
return normalizeSlug(basename(resolve(workspaceRoot)) || "deck") || "deck"
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function normalizeWorkspaceDeckState(state: DecksState, workspaceRoot: string): DecksState {
|
|
162
|
+
const normalized = normalizeDecksState(state)
|
|
163
|
+
const keys = Object.keys(normalized.decks)
|
|
164
|
+
if (keys.length !== 1) return normalized
|
|
165
|
+
|
|
166
|
+
const slug = workspaceDeckSlug(workspaceRoot)
|
|
167
|
+
const existingKey = keys[0]
|
|
168
|
+
if (existingKey === slug) {
|
|
169
|
+
normalized.activeDeck = slug
|
|
170
|
+
return normalized
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const deck = createDeckSpec({ ...normalized.decks[existingKey], slug })
|
|
174
|
+
delete normalized.decks[existingKey]
|
|
175
|
+
normalized.decks[slug] = deck
|
|
176
|
+
normalized.activeDeck = slug
|
|
177
|
+
return normalized
|
|
178
|
+
}
|
|
179
|
+
|
|
157
180
|
export function defaultRequiredInputs(overrides?: Partial<RequiredInputs>): RequiredInputs {
|
|
158
181
|
return {
|
|
159
182
|
topicClarified: false,
|
|
@@ -210,6 +233,10 @@ export function readOrCreateDecksState(workspaceRoot: string): DecksState {
|
|
|
210
233
|
export function upsertDeck(state: DecksState, input: Partial<DeckSpec> & { slug: string }): DecksState {
|
|
211
234
|
const normalized = normalizeDecksState(state)
|
|
212
235
|
const slug = normalizeSlug(input.slug)
|
|
236
|
+
const existingKey = currentDeckKey(normalized)
|
|
237
|
+
if (existingKey && slug !== existingKey && !normalized.decks[slug]) {
|
|
238
|
+
throw new Error(`${DECKS_STATE_FILE} already has a current deck (${existingKey}). Use a separate workspace for another deck.`)
|
|
239
|
+
}
|
|
213
240
|
const existing = normalized.decks[slug]
|
|
214
241
|
const next = createDeckSpec({ ...existing, ...input, slug })
|
|
215
242
|
normalized.decks[slug] = next
|
|
@@ -220,6 +247,10 @@ export function upsertDeck(state: DecksState, input: Partial<DeckSpec> & { slug:
|
|
|
220
247
|
export function upsertSlides(state: DecksState, slug: string, slides: SlideSpec[]): DecksState {
|
|
221
248
|
const normalized = normalizeDecksState(state)
|
|
222
249
|
const key = normalizeSlug(slug)
|
|
250
|
+
const existingKey = currentDeckKey(normalized)
|
|
251
|
+
if (existingKey && key !== existingKey && !normalized.decks[key]) {
|
|
252
|
+
throw new Error(`${DECKS_STATE_FILE} already has a current deck (${existingKey}). Use a separate workspace for another deck.`)
|
|
253
|
+
}
|
|
223
254
|
const deck = normalized.decks[key] ?? createDeckSpec({ slug: key })
|
|
224
255
|
const byIndex = new Map(deck.slides.map((slide) => [slide.index, slide]))
|
|
225
256
|
for (const slide of normalizeSlides(slides)) byIndex.set(slide.index, slide)
|
|
@@ -231,7 +262,7 @@ export function upsertSlides(state: DecksState, slug: string, slides: SlideSpec[
|
|
|
231
262
|
|
|
232
263
|
export function reviewDeckState(state: DecksState, slug?: string): { state: DecksState; result: DeckStateReadinessResult } {
|
|
233
264
|
const normalized = normalizeDecksState(state)
|
|
234
|
-
const key = normalizeSlug(slug || normalized
|
|
265
|
+
const key = normalizeSlug(slug || currentDeckKey(normalized) || "")
|
|
235
266
|
const deck = key ? normalized.decks[key] : undefined
|
|
236
267
|
if (!deck) {
|
|
237
268
|
const missing = key || "active deck"
|
|
@@ -276,13 +307,14 @@ export function evaluateDeckStateWriteReadiness(state: DecksState, filePath: str
|
|
|
276
307
|
const targetPath = normalizeDeckPath(filePath)
|
|
277
308
|
const targetSlug = deckSlugFromPath(targetPath)
|
|
278
309
|
const normalized = normalizeDecksState(state)
|
|
279
|
-
const
|
|
310
|
+
const key = currentDeckKey(normalized)
|
|
311
|
+
const deck = key ? normalized.decks[key] : undefined
|
|
280
312
|
if (!deck) {
|
|
281
313
|
return {
|
|
282
314
|
ready: false,
|
|
283
315
|
slug: targetSlug,
|
|
284
|
-
blocker:
|
|
285
|
-
blockers: [
|
|
316
|
+
blocker: currentDeckBlocker(normalized),
|
|
317
|
+
blockers: [currentDeckBlocker(normalized)],
|
|
286
318
|
}
|
|
287
319
|
}
|
|
288
320
|
|
|
@@ -324,16 +356,17 @@ export function extractDecksStateTargetsFromPatch(patchText: string): string[] {
|
|
|
324
356
|
export function buildDecksStatePromptLayer(workspaceRoot: string, maxChars = 14000): string {
|
|
325
357
|
if (!hasDecksState(workspaceRoot)) return ""
|
|
326
358
|
const state = readDecksState(workspaceRoot)
|
|
327
|
-
const
|
|
359
|
+
const activeKey = currentDeckKey(state)
|
|
360
|
+
const active = activeKey ? state.decks[activeKey] : undefined
|
|
328
361
|
const compact = {
|
|
329
362
|
sourceOfTruth: DECKS_STATE_FILE,
|
|
330
|
-
activeDeck:
|
|
363
|
+
activeDeck: activeKey,
|
|
331
364
|
workspace: state.workspace,
|
|
332
365
|
deck: active,
|
|
333
366
|
}
|
|
334
367
|
let text = JSON.stringify(compact, null, 2)
|
|
335
368
|
if (text.length > maxChars) text = text.slice(0, maxChars).trimEnd() + "\n[DECKS.json state truncated for prompt size.]"
|
|
336
|
-
return `---\n\n# Revela Workspace State From ${DECKS_STATE_FILE}\n\n\`\`\`json\n${text}\n\`\`\`\n\nRules for this state layer:\n- Treat ${DECKS_STATE_FILE} as the source of truth for deck specs, slide
|
|
369
|
+
return `---\n\n# Revela Workspace State From ${DECKS_STATE_FILE}\n\n\`\`\`json\n${text}\n\`\`\`\n\nRules for this state layer:\n- Treat ${DECKS_STATE_FILE} as the source of truth for the single current deck's specs, slide plan, and write readiness.\n- The decks map is compatibility storage; operate only on the current workspace deck.\n- Do not edit ${DECKS_STATE_FILE} directly; use the revela-decks tool.\n- Before writing decks/*.html, the current deck must have writeReadiness.status=ready and a complete slide spec, and its outputPath must match the target file.`
|
|
337
370
|
}
|
|
338
371
|
|
|
339
372
|
function normalizeDecksState(input: DecksState): DecksState {
|
|
@@ -357,9 +390,26 @@ function normalizeDecksState(input: DecksState): DecksState {
|
|
|
357
390
|
state.decks[normalizedSlug] = createDeckSpec({ ...deck, slug: normalizedSlug })
|
|
358
391
|
}
|
|
359
392
|
if (state.activeDeck && !state.decks[state.activeDeck]) state.activeDeck = undefined
|
|
393
|
+
if (!state.activeDeck) {
|
|
394
|
+
const keys = Object.keys(state.decks)
|
|
395
|
+
if (keys.length === 1) state.activeDeck = keys[0]
|
|
396
|
+
}
|
|
360
397
|
return state
|
|
361
398
|
}
|
|
362
399
|
|
|
400
|
+
function currentDeckKey(state: DecksState): string | undefined {
|
|
401
|
+
if (state.activeDeck && state.decks[state.activeDeck]) return state.activeDeck
|
|
402
|
+
const keys = Object.keys(state.decks)
|
|
403
|
+
if (keys.length === 1) return keys[0]
|
|
404
|
+
return undefined
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function currentDeckBlocker(state: DecksState): string {
|
|
408
|
+
const count = Object.keys(state.decks).length
|
|
409
|
+
if (count === 0) return `No current deck exists in ${DECKS_STATE_FILE}. Use revela-decks upsertDeck/upsertSlides/review before writing deck HTML.`
|
|
410
|
+
return `${DECKS_STATE_FILE} contains multiple deck records and no activeDeck. Select one current deck explicitly or move extra decks to separate workspaces.`
|
|
411
|
+
}
|
|
412
|
+
|
|
363
413
|
function computeDeckBlockers(deck: DeckSpec): string[] {
|
|
364
414
|
const blockers: string[] = []
|
|
365
415
|
if (!deck.goal.trim()) blockers.push("Deck goal is missing")
|
|
@@ -402,10 +452,6 @@ function normalizeSlides(slides: SlideSpec[]): SlideSpec[] {
|
|
|
402
452
|
.sort((a, b) => a.index - b.index)
|
|
403
453
|
}
|
|
404
454
|
|
|
405
|
-
function findDeckForTarget(state: DecksState, targetPath: string, targetSlug: string): DeckSpec | undefined {
|
|
406
|
-
return Object.values(state.decks).find((deck) => normalizeDeckPath(deck.outputPath) === targetPath) ?? state.decks[normalizeSlug(targetSlug)]
|
|
407
|
-
}
|
|
408
|
-
|
|
409
455
|
function hasSlideContent(slide: SlideSpec): boolean {
|
|
410
456
|
const content = slide.content ?? {}
|
|
411
457
|
return Boolean(
|
package/lib/edit/deck-state.ts
CHANGED
|
@@ -3,25 +3,27 @@ import { activeDesign } from "../design/designs"
|
|
|
3
3
|
import { activeDomain } from "../domain/domains"
|
|
4
4
|
import {
|
|
5
5
|
defaultRequiredInputs,
|
|
6
|
+
DECKS_STATE_FILE,
|
|
7
|
+
normalizeWorkspaceDeckState,
|
|
6
8
|
readOrCreateDecksState,
|
|
7
|
-
reviewDeckState,
|
|
8
9
|
upsertDeck,
|
|
9
10
|
upsertSlides,
|
|
10
11
|
writeDecksState,
|
|
11
|
-
type DeckStateReadinessResult,
|
|
12
12
|
type SlideSpec,
|
|
13
13
|
} from "../decks-state"
|
|
14
14
|
import type { EditableDeck } from "./resolve-deck"
|
|
15
15
|
|
|
16
16
|
export interface EditDeckStatePreflightResult {
|
|
17
17
|
changed: boolean
|
|
18
|
-
readiness: DeckStateReadinessResult
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
export function ensureEditableDeckState(workspaceRoot: string, deck: EditableDeck): EditDeckStatePreflightResult {
|
|
22
|
-
let state = readOrCreateDecksState(workspaceRoot)
|
|
21
|
+
let state = normalizeWorkspaceDeckState(readOrCreateDecksState(workspaceRoot), workspaceRoot)
|
|
22
|
+
const active = state.activeDeck ? state.decks[state.activeDeck] : undefined
|
|
23
|
+
if (active && active.slug !== deck.slug && active.outputPath !== deck.file) {
|
|
24
|
+
throw new Error(`${DECKS_STATE_FILE} already points to ${active.outputPath}. Revela 0.8 expects one deck per workspace; move extra decks to a separate workspace.`)
|
|
25
|
+
}
|
|
23
26
|
const existing = state.decks[deck.slug]
|
|
24
|
-
const existingReady = existing?.writeReadiness?.status === "ready" && existing.writeReadiness.blockers.length === 0
|
|
25
27
|
let changed = !existing || existing.outputPath !== deck.file
|
|
26
28
|
|
|
27
29
|
state = upsertDeck(state, {
|
|
@@ -58,12 +60,10 @@ export function ensureEditableDeckState(workspaceRoot: string, deck: EditableDec
|
|
|
58
60
|
changed = true
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
writeDecksState(workspaceRoot, reviewed.state)
|
|
63
|
+
writeDecksState(workspaceRoot, state)
|
|
63
64
|
|
|
64
65
|
return {
|
|
65
|
-
changed
|
|
66
|
-
readiness: reviewed.result,
|
|
66
|
+
changed,
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -112,7 +112,7 @@ function safeActiveDesign(): string {
|
|
|
112
112
|
try {
|
|
113
113
|
return activeDesign()
|
|
114
114
|
} catch {
|
|
115
|
-
return "
|
|
115
|
+
return "aurora"
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
package/lib/edit/open.ts
CHANGED
|
@@ -14,6 +14,9 @@ export interface OpenEditableDeckResult {
|
|
|
14
14
|
source: string
|
|
15
15
|
stateNote: string
|
|
16
16
|
preflightChanged: boolean
|
|
17
|
+
reusedSession: boolean
|
|
18
|
+
liveSession: boolean
|
|
19
|
+
openedBrowser: boolean
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
export interface OpenEditableDeckOptions {
|
|
@@ -21,6 +24,11 @@ export interface OpenEditableDeckOptions {
|
|
|
21
24
|
sessionID: string
|
|
22
25
|
workspaceRoot: string
|
|
23
26
|
openBrowser?: boolean
|
|
27
|
+
openUrl?: (url: string) => void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface EnsureEditableDeckOpenResult extends OpenEditableDeckResult {
|
|
31
|
+
skippedReason?: "live-session"
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
export function openUrl(url: string): void {
|
|
@@ -41,11 +49,23 @@ export function openUrl(url: string): void {
|
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
export function openEditableDeck(target: string, options: OpenEditableDeckOptions): OpenEditableDeckResult {
|
|
52
|
+
return openEditableDeckInternal(target, options, { skipLiveSession: false })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function ensureEditableDeckOpenForChange(
|
|
56
|
+
target: string,
|
|
57
|
+
options: OpenEditableDeckOptions,
|
|
58
|
+
): EnsureEditableDeckOpenResult {
|
|
59
|
+
return openEditableDeckInternal(target, options, { skipLiveSession: true })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function openEditableDeckInternal(
|
|
63
|
+
target: string,
|
|
64
|
+
options: OpenEditableDeckOptions,
|
|
65
|
+
behavior: { skipLiveSession: boolean },
|
|
66
|
+
): EnsureEditableDeckOpenResult {
|
|
44
67
|
const deck = resolveEditableDeck(options.workspaceRoot, target)
|
|
45
68
|
const preflight = ensureEditableDeckState(options.workspaceRoot, deck)
|
|
46
|
-
if (!preflight.readiness.ready) {
|
|
47
|
-
throw new Error(preflight.readiness.blocker || "Deck is not ready for HTML edits.")
|
|
48
|
-
}
|
|
49
69
|
|
|
50
70
|
ctx.enabled = true
|
|
51
71
|
if (!existsSync(ACTIVE_PROMPT_FILE)) {
|
|
@@ -55,16 +75,17 @@ export function openEditableDeck(target: string, options: OpenEditableDeckOption
|
|
|
55
75
|
}
|
|
56
76
|
|
|
57
77
|
const editServer = startEditServer()
|
|
58
|
-
const
|
|
78
|
+
const session = editServer.getOrCreateSession({
|
|
59
79
|
client: options.client,
|
|
60
80
|
sessionID: options.sessionID,
|
|
61
81
|
deck,
|
|
62
82
|
})
|
|
63
|
-
const url = `${editServer.baseUrl}/edit?token=${encodeURIComponent(token)}`
|
|
64
|
-
|
|
83
|
+
const url = `${editServer.baseUrl}/edit?token=${encodeURIComponent(session.token)}`
|
|
84
|
+
const shouldOpen = options.openBrowser !== false && !(behavior.skipLiveSession && session.live)
|
|
85
|
+
if (shouldOpen) (options.openUrl ?? openUrl)(url)
|
|
65
86
|
|
|
66
87
|
const source = deck.source === "decks-state" ? "DECKS.json" : deck.source === "file-path" ? "file path" : "fallback path"
|
|
67
|
-
const stateNote = preflight.changed ? "Deck state was prepared in DECKS.json
|
|
88
|
+
const stateNote = preflight.changed ? "Deck state was prepared in DECKS.json for visual editing." : "Deck state already points to this visual edit target."
|
|
68
89
|
|
|
69
90
|
return {
|
|
70
91
|
deck,
|
|
@@ -72,5 +93,9 @@ export function openEditableDeck(target: string, options: OpenEditableDeckOption
|
|
|
72
93
|
source,
|
|
73
94
|
stateNote,
|
|
74
95
|
preflightChanged: preflight.changed,
|
|
96
|
+
reusedSession: session.reused,
|
|
97
|
+
liveSession: session.live,
|
|
98
|
+
openedBrowser: shouldOpen,
|
|
99
|
+
skippedReason: behavior.skipLiveSession && session.live ? "live-session" : undefined,
|
|
75
100
|
}
|
|
76
101
|
}
|