@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/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, Working Slug & Active Deck
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, prior deck history, active
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
- Derive a **working slug** from the Research Brief: lowercase, hyphens, no spaces
61
- (e.g. "AI Investment Shift" `ai-investment-shift`). The slug is only a file
62
- path handle for this deck/research cache; the user does not need to supply it as
63
- a command. Tell the user: "I'll save this deck as `decks/{slug}.html`." They can
64
- correct the name at this point.
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. Run `glob researches/{slug}/*.md`.
68
- 2. If research files already exist, list them and ask whether to reuse, supplement,
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
- 3. If the user chooses reuse, read the existing files before Phase 4.
71
- 4. If the user chooses supplement or replace, use the existing files to avoid
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 working slug:
75
- - Slides file: `decks/{slug}.html`
76
- - Research dir: `researches/{slug}/`
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
- - working slug for `researches/{slug}/`
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 the slug unless the filename matters.
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 working slug for `researches/{slug}/`
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/{slug}/{axis-name}.md`.
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/{slug}/`. Each file contains
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/{slug}/{axis}.md` after agents complete
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 {slug}` or call `revela-decks` action `review` yourself. The tool must compute readiness from `DECKS.json`.
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/{slug}.html` using the deck slug confirmed in Phase 1.5
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
- Then use `revela-decks` to record written status when available. Preserve
327
- stable decisions in deck memory when useful.
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 same `decks/{slug}.html` filename.
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/{slug}/*.md`. When multiple images are needed, prefer
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/{slug}.html."),
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
- const state = readOrCreateDecksState(workspaceRoot)
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 slug = args.slug || state.activeDeck
97
+ const deckKey = state.activeDeck
96
98
  if (args.summary) {
97
- const deck = slug ? state.decks[slug] : undefined
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
- if (!args.slug) return JSON.stringify({ ok: false, error: "slug is required for upsertDeck" })
105
- const existing = state.decks[args.slug]
106
+ const deckKey = defaultSlug
107
+ const existing = state.decks[deckKey]
106
108
  const deckInput: Partial<DeckSpec> & { slug: string } = {
107
109
  ...existing,
108
- slug: args.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
- if (!args.slug) return JSON.stringify({ ok: false, error: "slug is required for upsertSlides" })
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, args.slug, args.slides as SlideSpec[])
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, args.slug)
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 a decks/*.html file with @. " +
16
- "Accepts either a deck slug from DECKS.json or a workspace-relative decks/*.html path. " +
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
- target: tool.schema
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(target, {
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
- deck: result.deck.slug,
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 slug shared by one presentation, e.g. 'ev-market'."),
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."),
@@ -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 slug shared by one presentation, e.g. 'ev-market'."),
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 slug shared by one presentation, e.g. 'ev-market'."),
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()
@@ -10,9 +10,9 @@ function today(): string {
10
10
  }
11
11
 
12
12
  /**
13
- * Sanitize a slug: lowercase, alphanumeric + hyphens only.
13
+ * Sanitize a key: lowercase, alphanumeric + hyphens only.
14
14
  */
15
- function slugify(s: string): string {
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 slug in kebab-case, e.g. 'ev-battery-market' or 'saas-competitive-analysis'. " +
53
- "All files for the same presentation share the same topic slug.",
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 topicSlug = slugify(args.topic || "research")
78
- const fileSlug = slugify(args.filename || "findings")
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", topicSlug)
80
+ const topicDir = join(workspaceDir, "researches", topicKey)
81
81
 
82
82
  mkdirSync(topicDir, { recursive: true })
83
83
 
84
- const frontmatter = buildFrontmatter(args.topic, fileSlug, args.sources ?? [])
84
+ const frontmatter = buildFrontmatter(args.topic, fileKey, args.sources ?? [])
85
85
  const fileContent = `${frontmatter}\n\n${args.content ?? ""}\n`
86
- const filePath = join(topicDir, `${fileSlug}.md`)
87
- const relPath = `researches/${topicSlug}/${fileSlug}.md`
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