@cyber-dash-tech/revela 0.7.8 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -14
- package/README.zh-CN.md +14 -14
- package/designs/starter/DESIGN.md +118 -14
- package/designs/starter/preview.html +48 -12
- package/lib/agents/research-prompt.ts +4 -4
- package/lib/commands/designs-new.ts +14 -0
- 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/design/designs.ts +20 -0
- package/lib/edit/deck-state.ts +8 -2
- package/lib/edit/open.ts +31 -3
- package/lib/edit/resolve-deck.ts +20 -75
- package/lib/edit/server.ts +196 -54
- package/package.json +1 -1
- package/plugin.ts +37 -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/skill/SKILL.md
CHANGED
|
@@ -49,31 +49,35 @@ If the brief is unclear, ask 1–3 targeted clarification questions. Do not forc
|
|
|
49
49
|
the user to provide a research topic command; the working topic emerges from the
|
|
50
50
|
conversation.
|
|
51
51
|
|
|
52
|
-
### Phase 1.5 — Project State,
|
|
52
|
+
### Phase 1.5 — Project State, Output File & Current Deck
|
|
53
53
|
|
|
54
54
|
Before research, use the `revela-decks` tool with action `read` or `init` to
|
|
55
55
|
inspect `DECKS.json`. Treat it as the source of truth for project context,
|
|
56
|
-
source material index, explicit user preferences,
|
|
56
|
+
source material index, explicit user preferences, current deck state, active
|
|
57
57
|
deck specs, per-slide content/layout/components, write readiness, and open
|
|
58
58
|
questions. Do not write or patch `DECKS.json` directly.
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
Treat the workspace folder as the deck project boundary. The `revela-decks` tool
|
|
61
|
+
derives its internal deck key from the workspace folder name; this key is not
|
|
62
|
+
user-facing.
|
|
63
|
+
|
|
64
|
+
Derive an **output file** for the current deck. Default to
|
|
65
|
+
`decks/{workspace-name}.html` using the normalized workspace folder name unless
|
|
66
|
+
the user explicitly asks for a different filename. Tell the user: "I'll save this
|
|
67
|
+
deck as `decks/<filename>.html`." They can correct the filename at this point.
|
|
65
68
|
|
|
66
69
|
Check whether this deck has been worked on before:
|
|
67
|
-
1.
|
|
68
|
-
2.
|
|
70
|
+
1. Use the workspace-derived internal key from `DECKS.json.activeDeck` when available; otherwise use the normalized workspace folder name for `researches/{workspace-key}/`.
|
|
71
|
+
2. Run `glob researches/{workspace-key}/*.md`.
|
|
72
|
+
3. If research files already exist, list them and ask whether to reuse, supplement,
|
|
69
73
|
or replace the existing research.
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
4. If the user chooses reuse, read the existing files before Phase 4.
|
|
75
|
+
5. If the user chooses supplement or replace, use the existing files to avoid
|
|
72
76
|
duplicate work and proceed through Phase 3 only for missing or stale axes.
|
|
73
77
|
|
|
74
|
-
All subsequent file paths in this session use the
|
|
75
|
-
- Slides file: `decks
|
|
76
|
-
- Research dir: `researches/{
|
|
78
|
+
All subsequent file paths in this session use the current workspace deck:
|
|
79
|
+
- Slides file: the confirmed `decks/*.html` output path
|
|
80
|
+
- Research dir: `researches/{workspace-key}/`
|
|
77
81
|
|
|
78
82
|
Create or update the active deck in `DECKS.json` through `revela-decks` actions
|
|
79
83
|
`upsertDeck` and `upsertSlides`. Keep the deck spec current as work progresses:
|
|
@@ -132,14 +136,14 @@ user 1–3 focused questions before launching agents.
|
|
|
132
136
|
#### Research Brief Before Agents
|
|
133
137
|
|
|
134
138
|
Before starting research agents, write a brief for yourself with:
|
|
135
|
-
-
|
|
139
|
+
- workspace-derived research key for `researches/{workspace-key}/`
|
|
136
140
|
- user goal and audience
|
|
137
141
|
- thesis or decision the deck should support
|
|
138
142
|
- key questions and time period
|
|
139
143
|
- relevant `DECKS.json` sourceMaterials or user-provided files
|
|
140
144
|
- axes to research and desired output for each axis
|
|
141
145
|
|
|
142
|
-
You do not need to ask the user to approve
|
|
146
|
+
You do not need to ask the user to approve an internal key. Ask only if the visible output filename matters.
|
|
143
147
|
|
|
144
148
|
#### Deep Research via `revela-research` Subagents
|
|
145
149
|
|
|
@@ -152,14 +156,14 @@ axis gets one focused subagent brief. When multiple axes are needed, launch all
|
|
|
152
156
|
agents in a single message with parallel Task tool calls.
|
|
153
157
|
|
|
154
158
|
Each subagent brief must specify:
|
|
155
|
-
- shared
|
|
159
|
+
- shared workspace-derived research key for `researches/{workspace-key}/`
|
|
156
160
|
- axis filename, such as `market-data`, `competitor-profile`, or `technology-trends`
|
|
157
161
|
- the research question, time period, geography, and evidence standard
|
|
158
162
|
- relevant `DECKS.json` sourceMaterials or user files to prioritize
|
|
159
163
|
- whether web research is needed and what types of sources are preferred
|
|
160
164
|
|
|
161
165
|
The subagent writes exactly one file through `revela-research-save`:
|
|
162
|
-
`researches/{
|
|
166
|
+
`researches/{workspace-key}/{axis-name}.md`.
|
|
163
167
|
|
|
164
168
|
#### Workspace Memory and Freshness
|
|
165
169
|
|
|
@@ -177,7 +181,7 @@ deep-read files that are relevant to the current Research Brief.
|
|
|
177
181
|
|
|
178
182
|
#### After Agents Complete
|
|
179
183
|
|
|
180
|
-
List and read the findings files in `researches/{
|
|
184
|
+
List and read the findings files in `researches/{workspace-key}/`. Each file contains
|
|
181
185
|
structured `## Data`, `## Cases`, `## Images`, and `## Gaps` sections. Use these
|
|
182
186
|
directly as slide material, cross-reference them with workspace documents, and
|
|
183
187
|
flag contradictions.
|
|
@@ -202,7 +206,7 @@ already checked and what specific missing information is needed.
|
|
|
202
206
|
- **NEVER** call `revela-research` as a tool; use Task with `subagent_type: "revela-research"`
|
|
203
207
|
- **NEVER** collapse distinct research axes into one broad agent brief when parallel focused briefs would be clearer
|
|
204
208
|
- **ALWAYS** use `revela-decks` action `read` before deciding what research is needed
|
|
205
|
-
- **ALWAYS** read each `researches/{
|
|
209
|
+
- **ALWAYS** read each `researches/{workspace-key}/{axis}.md` after agents complete
|
|
206
210
|
- Use the `read` tool for all file types — binary formats are handled transparently
|
|
207
211
|
---
|
|
208
212
|
|
|
@@ -302,7 +306,7 @@ After the user confirms the slide plan, update `DECKS.json` through `revela-deck
|
|
|
302
306
|
3. Call `revela-designs` tool with `action: "read"` and `component` set to ALL component
|
|
303
307
|
names you plan to use (comma-separated, e.g. `component: "card,stat-card,evidence-list"`).
|
|
304
308
|
4. Use `revela-decks` action `upsertDeck` to mark `requiredInputs.designLayoutsFetched` complete.
|
|
305
|
-
5. Run `/revela review
|
|
309
|
+
5. Run `/revela review` or call `revela-decks` action `review` yourself. The tool must compute readiness from `DECKS.json`.
|
|
306
310
|
6. Use `revela-decks` action `read` and confirm `writeReadiness.status` is `ready` with no blockers.
|
|
307
311
|
7. Generate HTML that **exactly matches** the fetched examples — copy the HTML structure verbatim.
|
|
308
312
|
|
|
@@ -313,7 +317,7 @@ Once the fetch is complete, generate the complete HTML file in one shot.
|
|
|
313
317
|
|
|
314
318
|
- Output **only** the raw HTML — no markdown fences, no explanation before or after
|
|
315
319
|
- Create a `decks/` directory in the current working directory if it doesn't already exist
|
|
316
|
-
- Write the file to `decks
|
|
320
|
+
- Write the file to the `decks/*.html` output path confirmed in Phase 1.5
|
|
317
321
|
- The file must be completely self-contained (all CSS and JS inline)
|
|
318
322
|
|
|
319
323
|
### Phase 6 — Iterate
|
|
@@ -323,11 +327,12 @@ After generating, briefly tell the user:
|
|
|
323
327
|
- How to navigate (arrow keys / swipe)
|
|
324
328
|
- One line invitation to request changes
|
|
325
329
|
|
|
326
|
-
|
|
327
|
-
|
|
330
|
+
Keep `DECKS.json` focused on the current slide specs, research/read state,
|
|
331
|
+
output path, and explicit preferences. The HTML file is the source of truth for
|
|
332
|
+
the produced artifact.
|
|
328
333
|
|
|
329
334
|
For change requests: re-generate the **entire** file (don't patch). Apply the
|
|
330
|
-
change and silently overwrite the
|
|
335
|
+
change and silently overwrite the confirmed `decks/*.html` output file.
|
|
331
336
|
|
|
332
337
|
---
|
|
333
338
|
|
|
@@ -402,7 +407,7 @@ element selector list, and `window.getEditedHTML()` definition.
|
|
|
402
407
|
|
|
403
408
|
- When research findings contain image leads that should appear in the final deck,
|
|
404
409
|
first call `revela-research-images-list` to inspect structured candidates from
|
|
405
|
-
`researches/{
|
|
410
|
+
`researches/{workspace-key}/*.md`. When multiple images are needed, prefer
|
|
406
411
|
`revela-media-batch-save` to save the selected candidates in one call. Use
|
|
407
412
|
`revela-media-save` for one-off cases. Then reference the returned local file
|
|
408
413
|
path in HTML. Do not use remote image URLs directly in final slides.
|
package/tools/decks.ts
CHANGED
|
@@ -2,11 +2,13 @@ import { tool } from "@opencode-ai/plugin"
|
|
|
2
2
|
import {
|
|
3
3
|
createDeckSpec,
|
|
4
4
|
DECKS_STATE_FILE,
|
|
5
|
+
normalizeWorkspaceDeckState,
|
|
5
6
|
readOrCreateDecksState,
|
|
6
7
|
reviewDeckState,
|
|
7
8
|
upsertDeck,
|
|
8
9
|
upsertSlides,
|
|
9
10
|
writeDecksState,
|
|
11
|
+
workspaceDeckSlug,
|
|
10
12
|
type DeckSpec,
|
|
11
13
|
type RequiredInputs,
|
|
12
14
|
type ResearchAxis,
|
|
@@ -22,13 +24,12 @@ export default tool({
|
|
|
22
24
|
action: tool.schema
|
|
23
25
|
.enum(["read", "init", "upsertDeck", "upsertSlides", "review", "remember"])
|
|
24
26
|
.describe("Action to perform on DECKS.json."),
|
|
25
|
-
slug: tool.schema.string().optional().describe("Deck slug for read/upsert/review actions."),
|
|
26
27
|
summary: tool.schema.boolean().optional().describe("For read: return a compact summary instead of full state."),
|
|
27
28
|
goal: tool.schema.string().optional().describe("For upsertDeck: deck goal."),
|
|
28
29
|
audience: tool.schema.string().optional().describe("For upsertDeck: deck audience."),
|
|
29
30
|
language: tool.schema.string().optional().describe("For upsertDeck: deck language."),
|
|
30
31
|
slideCount: tool.schema.number().optional().describe("For upsertDeck: expected slide count."),
|
|
31
|
-
outputPath: tool.schema.string().optional().describe("For upsertDeck: target output path, normally decks/{
|
|
32
|
+
outputPath: tool.schema.string().optional().describe("For upsertDeck: target output path, normally decks/{workspace-name}.html."),
|
|
32
33
|
design: tool.schema.string().optional().describe("For upsertDeck: active design name."),
|
|
33
34
|
domain: tool.schema.string().optional().describe("For upsertDeck: active domain name."),
|
|
34
35
|
memory: tool.schema.string().optional().describe("For remember: explicit user or workflow preference to store."),
|
|
@@ -84,7 +85,8 @@ export default tool({
|
|
|
84
85
|
async execute(args, context) {
|
|
85
86
|
try {
|
|
86
87
|
const workspaceRoot = context.directory ?? process.cwd()
|
|
87
|
-
|
|
88
|
+
let state = normalizeWorkspaceDeckState(readOrCreateDecksState(workspaceRoot), workspaceRoot)
|
|
89
|
+
const defaultSlug = workspaceDeckSlug(workspaceRoot)
|
|
88
90
|
|
|
89
91
|
if (args.action === "init") {
|
|
90
92
|
writeDecksState(workspaceRoot, state)
|
|
@@ -92,20 +94,20 @@ export default tool({
|
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
if (args.action === "read") {
|
|
95
|
-
const
|
|
97
|
+
const deckKey = state.activeDeck
|
|
96
98
|
if (args.summary) {
|
|
97
|
-
const deck =
|
|
99
|
+
const deck = deckKey ? state.decks[deckKey] : undefined
|
|
98
100
|
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, activeDeck: state.activeDeck, deck }, null, 2)
|
|
99
101
|
}
|
|
100
102
|
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, state }, null, 2)
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
if (args.action === "upsertDeck") {
|
|
104
|
-
|
|
105
|
-
const existing = state.decks[
|
|
106
|
+
const deckKey = defaultSlug
|
|
107
|
+
const existing = state.decks[deckKey]
|
|
106
108
|
const deckInput: Partial<DeckSpec> & { slug: string } = {
|
|
107
109
|
...existing,
|
|
108
|
-
slug:
|
|
110
|
+
slug: deckKey,
|
|
109
111
|
goal: args.goal ?? existing?.goal ?? "",
|
|
110
112
|
audience: args.audience ?? existing?.audience,
|
|
111
113
|
language: args.language ?? existing?.language,
|
|
@@ -127,15 +129,15 @@ export default tool({
|
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
if (args.action === "upsertSlides") {
|
|
130
|
-
|
|
132
|
+
const deckKey = state.activeDeck || defaultSlug
|
|
131
133
|
if (!args.slides) return JSON.stringify({ ok: false, error: "slides are required for upsertSlides" })
|
|
132
|
-
const next = upsertSlides(state,
|
|
134
|
+
const next = upsertSlides(state, deckKey, args.slides as SlideSpec[])
|
|
133
135
|
writeDecksState(workspaceRoot, next)
|
|
134
136
|
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, deck: next.activeDeck ? next.decks[next.activeDeck] : undefined }, null, 2)
|
|
135
137
|
}
|
|
136
138
|
|
|
137
139
|
if (args.action === "review") {
|
|
138
|
-
const reviewed = reviewDeckState(state
|
|
140
|
+
const reviewed = reviewDeckState(state)
|
|
139
141
|
writeDecksState(workspaceRoot, reviewed.state)
|
|
140
142
|
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: reviewed.result }, null, 2)
|
|
141
143
|
}
|
package/tools/edit.ts
CHANGED
|
@@ -12,16 +12,12 @@ export function createEditTool(options: { client: any; workspaceRoot: string; op
|
|
|
12
12
|
description:
|
|
13
13
|
"Open Revela's visual comment editor for an existing slide deck. " +
|
|
14
14
|
"Use this when the user asks to edit, revise, annotate, or visually comment on a deck, " +
|
|
15
|
-
"including when they reference
|
|
16
|
-
"
|
|
15
|
+
"including when they reference the current deck. " +
|
|
16
|
+
"Revela 0.8 opens the only HTML deck in decks/. " +
|
|
17
17
|
"This opens a local browser editor where the user can Ctrl/Cmd-click deck elements, write comments, " +
|
|
18
18
|
"and send precise edit requests back to the current OpenCode session.",
|
|
19
|
-
args: {
|
|
20
|
-
|
|
21
|
-
.string()
|
|
22
|
-
.describe("Deck slug or workspace-relative deck HTML path, e.g. investor-update or decks/investor-update.html."),
|
|
23
|
-
},
|
|
24
|
-
async execute({ target }, context: any) {
|
|
19
|
+
args: {},
|
|
20
|
+
async execute(_args, context: any) {
|
|
25
21
|
const sessionID = context?.sessionID ?? context?.session?.id ?? ""
|
|
26
22
|
if (!sessionID) {
|
|
27
23
|
return JSON.stringify({
|
|
@@ -31,7 +27,7 @@ export function createEditTool(options: { client: any; workspaceRoot: string; op
|
|
|
31
27
|
}
|
|
32
28
|
|
|
33
29
|
try {
|
|
34
|
-
const result = openEditableDeck(
|
|
30
|
+
const result = openEditableDeck("", {
|
|
35
31
|
client: options.client,
|
|
36
32
|
sessionID,
|
|
37
33
|
workspaceRoot: options.workspaceRoot,
|
|
@@ -40,7 +36,7 @@ export function createEditTool(options: { client: any; workspaceRoot: string; op
|
|
|
40
36
|
|
|
41
37
|
return JSON.stringify({
|
|
42
38
|
ok: true,
|
|
43
|
-
|
|
39
|
+
deckKey: result.deck.slug,
|
|
44
40
|
file: result.deck.file,
|
|
45
41
|
source: result.source,
|
|
46
42
|
url: result.url,
|
|
@@ -6,7 +6,7 @@ export default tool({
|
|
|
6
6
|
"Save a selected batch of research-found image leads into workspace assets and update the media manifest. " +
|
|
7
7
|
"Use this after the primary agent has chosen multiple images from researches/{topic}/*.md.",
|
|
8
8
|
args: {
|
|
9
|
-
topic: tool.schema.string().describe("Topic
|
|
9
|
+
topic: tool.schema.string().describe("Topic key shared by one presentation, e.g. 'ev-market'."),
|
|
10
10
|
items: tool.schema.array(tool.schema.object({
|
|
11
11
|
candidateId: tool.schema.string().describe("Stable candidate id returned by revela-research-images-list."),
|
|
12
12
|
description: tool.schema.string().describe("Candidate description from research findings."),
|
package/tools/media-save.ts
CHANGED
|
@@ -7,7 +7,7 @@ export default tool({
|
|
|
7
7
|
"Supports either a sourceUrl or a sourcePath. Records both success and failure states. " +
|
|
8
8
|
"Use this when a research-found image or an existing local image should become a formal project asset.",
|
|
9
9
|
args: {
|
|
10
|
-
topic: tool.schema.string().describe("Topic
|
|
10
|
+
topic: tool.schema.string().describe("Topic key shared by one presentation, e.g. 'ev-market'."),
|
|
11
11
|
id: tool.schema.string().describe("Stable asset id within the topic, e.g. 'tesla-logo-01'."),
|
|
12
12
|
type: tool.schema.enum(["image"]).describe("Asset type. Stage 1 only supports 'image'."),
|
|
13
13
|
purpose: tool.schema
|
|
@@ -6,7 +6,7 @@ export default tool({
|
|
|
6
6
|
"List structured image leads from researches/{topic}/*.md. " +
|
|
7
7
|
"Parses ## Images sections and returns candidate image records for the primary agent to review.",
|
|
8
8
|
args: {
|
|
9
|
-
topic: tool.schema.string().describe("Topic
|
|
9
|
+
topic: tool.schema.string().describe("Topic key shared by one presentation, e.g. 'ev-market'."),
|
|
10
10
|
uses: tool.schema
|
|
11
11
|
.array(tool.schema.enum(["logo", "portrait", "screenshot", "unknown"]))
|
|
12
12
|
.optional()
|
package/tools/research-save.ts
CHANGED
|
@@ -10,9 +10,9 @@ function today(): string {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Sanitize a
|
|
13
|
+
* Sanitize a key: lowercase, alphanumeric + hyphens only.
|
|
14
14
|
*/
|
|
15
|
-
function
|
|
15
|
+
function keyify(s: string): string {
|
|
16
16
|
return s
|
|
17
17
|
.toLowerCase()
|
|
18
18
|
.replace(/[^a-z0-9]+/g, "-")
|
|
@@ -49,8 +49,8 @@ export default tool({
|
|
|
49
49
|
topic: tool.schema
|
|
50
50
|
.string()
|
|
51
51
|
.describe(
|
|
52
|
-
"Topic
|
|
53
|
-
"All files for the same presentation share the same topic
|
|
52
|
+
"Topic key in kebab-case, e.g. 'ev-battery-market' or 'saas-competitive-analysis'. " +
|
|
53
|
+
"All files for the same presentation share the same topic key.",
|
|
54
54
|
),
|
|
55
55
|
filename: tool.schema
|
|
56
56
|
.string()
|
|
@@ -74,17 +74,17 @@ export default tool({
|
|
|
74
74
|
},
|
|
75
75
|
async execute(args, context) {
|
|
76
76
|
try {
|
|
77
|
-
const
|
|
78
|
-
const
|
|
77
|
+
const topicKey = keyify(args.topic || "research")
|
|
78
|
+
const fileKey = keyify(args.filename || "findings")
|
|
79
79
|
const workspaceDir = context.directory ?? process.cwd()
|
|
80
|
-
const topicDir = join(workspaceDir, "researches",
|
|
80
|
+
const topicDir = join(workspaceDir, "researches", topicKey)
|
|
81
81
|
|
|
82
82
|
mkdirSync(topicDir, { recursive: true })
|
|
83
83
|
|
|
84
|
-
const frontmatter = buildFrontmatter(args.topic,
|
|
84
|
+
const frontmatter = buildFrontmatter(args.topic, fileKey, args.sources ?? [])
|
|
85
85
|
const fileContent = `${frontmatter}\n\n${args.content ?? ""}\n`
|
|
86
|
-
const filePath = join(topicDir, `${
|
|
87
|
-
const relPath = `researches/${
|
|
86
|
+
const filePath = join(topicDir, `${fileKey}.md`)
|
|
87
|
+
const relPath = `researches/${topicKey}/${fileKey}.md`
|
|
88
88
|
|
|
89
89
|
writeFileSync(filePath, fileContent, "utf-8")
|
|
90
90
|
|