@cyber-dash-tech/revela 0.1.3 → 0.1.4

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 CHANGED
@@ -102,9 +102,8 @@ Three designs are bundled. Switch with `/revela designs <name>`.
102
102
 
103
103
  | Name | Description | Preview |
104
104
  |---|---|---|
105
- | `default` | Dark executive style — deep navy/slate, sharp typography, ECharts data visualization | ![default](assets/img/slide-example-default.jpg) |
106
- | `minimal` | Clean light theme — high contrast, generous whitespace, professional look | ![minimal](assets/img/slide-example-minimal.jpg) |
107
- | `editorial-ribbon` | Bold editorial layout — accent ribbons, strong headlines, high visual impact | ![editorial-ribbon](assets/img/slide-example-ribbon.jpg) |
105
+ | `aurora` | Dark executive style — deep navy/slate, sharp typography, ECharts data visualization | ![default](assets/img/slide-example-aurora.jpg) |
106
+ | `summit` | Editorial outdoor annual-report theme | ![summit](assets/img/slide-example-summit.jpg) |
108
107
 
109
108
  ---
110
109
 
package/README.zh-CN.md CHANGED
@@ -133,9 +133,8 @@ OPENCODE_ENABLE_EXA=1 opencode
133
133
 
134
134
  | 名称 | 说明 | 预览 |
135
135
  |---|---|---|
136
- | `default` | 深色商务风格 —— 深海军蓝/石板色,锐利字体,ECharts 数据可视化 | ![default](assets/img/slide-example-default.jpg) |
137
- | `minimal` | 简洁浅色主题 —— 高对比度,充足留白,专业外观 | ![minimal](assets/img/slide-example-minimal.jpg) |
138
- | `editorial-ribbon` | 大胆的编辑版式 —— 强调色横幅,醒目标题,高视觉冲击力 | ![editorial-ribbon](assets/img/slide-example-ribbon.jpg) |
136
+ | `aurora` | 颜色主题 极光, 高饱和度, ECharts 数据可视化 | ![default](assets/img/slide-example-aurora.jpg) |
137
+ | `summit` | 极简主义 - 户外,适合有丰富插图,Echart 数据可视化 | ![summit](assets/img/slide-example-summit.jpg) |
139
138
 
140
139
  ---
141
140
 
@@ -53,9 +53,13 @@ Formulate **3–6 targeted search queries** for your specific axis, covering:
53
53
 
54
54
  For Chinese topics: search in **both Chinese and English**.
55
55
 
56
- Use \`webfetch\` to retrieve specific pages for depth. Do NOT use \`websearch\` —
57
- it is not available. Use \`webfetch\` with targeted URLs from your knowledge or
58
- from initial search results.
56
+ Use **\`websearch\`** for broad keyword queries to find relevant pages, reports,
57
+ and data. Then use **\`webfetch\`** to retrieve specific pages for depth.
58
+
59
+ Search strategy:
60
+ - Start with \`websearch\` to discover relevant URLs (market reports, company pages, news)
61
+ - Follow up with \`webfetch\` on the most promising URLs for full content
62
+ - For Chinese topics: run \`websearch\` queries in both Chinese and English
59
63
 
60
64
  ---
61
65
 
package/lib/log.ts CHANGED
@@ -17,8 +17,9 @@ export const log = new Logger({
17
17
  type: "json",
18
18
  hideLogPositionForProduction: true,
19
19
  overwrite: {
20
- transportJSON: (logObj: unknown) => {
21
- process.stderr.write(JSON.stringify(logObj) + "\n")
20
+ transportJSON: (_logObj: unknown) => {
21
+ // Silenced: revela runs as an OpenCode plugin; writing to stderr
22
+ // pollutes the host terminal. Logs are intentionally suppressed.
22
23
  },
23
24
  },
24
25
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyber-dash-tech/revela",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "OpenCode plugin that turns AI into an HTML slide deck generator",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
package/plugin.ts CHANGED
@@ -111,6 +111,7 @@ const server: Plugin = (async (pluginCtx) => {
111
111
  // Permissions: read-only on edit/bash; write allowed to create researches/ files.
112
112
  // No model override — inherits from the calling primary agent.
113
113
  opencodeConfig.agent ??= {}
114
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114
115
  opencodeConfig.agent["revela-research"] = {
115
116
  description: "Revela research agent — searches and collects raw materials for presentations",
116
117
  mode: "subagent",
@@ -123,7 +124,19 @@ const server: Plugin = (async (pluginCtx) => {
123
124
  "ls": "allow",
124
125
  },
125
126
  webfetch: "allow",
126
- },
127
+ } as any,
128
+ }
129
+ // Give revela-research explicit websearch allow (overrides global deny below)
130
+ ;(opencodeConfig.agent["revela-research"].permission as any).websearch = "allow"
131
+
132
+ // Block websearch for the primary agent globally.
133
+ // permission.ask hook is not triggered by OpenCode (no R.trigger call in binary).
134
+ // tool.execute.before throw is swallowed (trigger().catch(()=>{})).
135
+ // The only working mechanism is the config-level permission ruleset.
136
+ // revela-research agent overrides this with websearch: "allow" above.
137
+ opencodeConfig.permission ??= {}
138
+ if (!(opencodeConfig.permission as Record<string, unknown>)["websearch"]) {
139
+ ;(opencodeConfig.permission as Record<string, unknown>)["websearch"] = "deny"
127
140
  }
128
141
  },
129
142
 
@@ -283,22 +296,10 @@ const server: Plugin = (async (pluginCtx) => {
283
296
  // ── Pre-read: intercept binary files before read executes ──────────────
284
297
  // Handles DOCX/PPTX/XLSX — read tool would Effect.fail on these.
285
298
  // Extracts text → writes temp .txt → redirects args.filePath.
286
- //
287
- // Also blocks websearch for the primary agent — websearch must be delegated
288
- // to the revela-research subagent. Use webfetch for specific URLs instead.
289
299
  "tool.execute.before": async (input, output) => {
300
+ log.info("[hook] tool.execute.before fired", { tool: input.tool, enabled: ctx.enabled, isResearch: ctx.isResearchAgent })
290
301
  if (!ctx.enabled) return
291
302
 
292
- // ── Block websearch for primary agent ──────────────────────────────
293
- if (input.tool === "websearch" && !ctx.isResearchAgent) {
294
- throw new Error(
295
- "[revela] websearch is not available for the primary agent. " +
296
- "Delegate web research to the revela-research subagent via the Task tool — " +
297
- "it searches systematically and saves structured findings for reuse across sessions. " +
298
- "Use the webfetch tool if you need to read a specific URL directly.",
299
- )
300
- }
301
-
302
303
  if (input.tool !== "read") return
303
304
  try {
304
305
  await preRead(output.args)
package/skill/SKILL.md CHANGED
@@ -125,9 +125,10 @@ extracts text from binary formats (PDF, Excel, Word, PowerPoint) — just call
125
125
  with `subagent_type: "revela-research"`) is available.** It is the primary
126
126
  research workhorse — not an optional enhancement.
127
127
 
128
- The research agent searches the web aggressively using `webfetch` on targeted
129
- URLs, reads workspace documents, and writes structured findings to a single
130
- file `researches/{topic-slug}/{axis-name}.md` in the workspace.
128
+ The research agent searches the web using `websearch` for broad discovery and
129
+ `webfetch` for depth on specific pages, reads workspace documents, and writes
130
+ structured findings to a single file `researches/{topic-slug}/{axis-name}.md`
131
+ in the workspace.
131
132
 
132
133
  ##### Parallelization Rule
133
134
 
@@ -1,6 +1,6 @@
1
1
  import { tool } from "@opencode-ai/plugin"
2
2
  import { readdirSync, statSync, existsSync } from "fs"
3
- import { join, relative, extname } from "path"
3
+ import { join, relative, extname, resolve, sep, isAbsolute } from "path"
4
4
 
5
5
  const DOC_EXTENSIONS = new Set([
6
6
  ".pdf", ".docx", ".doc", ".xlsx", ".xls",
@@ -113,7 +113,22 @@ export default tool({
113
113
  async execute(args, context) {
114
114
  try {
115
115
  const workspaceDir = context.directory ?? process.cwd()
116
- const scanRoot = args.path ? join(workspaceDir, args.path) : workspaceDir
116
+
117
+ // Validate and resolve scanRoot — must stay within workspaceDir
118
+ let scanRoot = workspaceDir
119
+ if (args.path) {
120
+ if (isAbsolute(args.path)) {
121
+ return JSON.stringify({ error: "path must be relative to workspace root" })
122
+ }
123
+ const candidate = join(workspaceDir, args.path)
124
+ const resolvedCandidate = resolve(candidate)
125
+ const resolvedWorkspace = resolve(workspaceDir)
126
+ if (resolvedCandidate !== resolvedWorkspace && !resolvedCandidate.startsWith(resolvedWorkspace + sep)) {
127
+ return JSON.stringify({ error: "path must be within workspace" })
128
+ }
129
+ scanRoot = candidate
130
+ }
131
+
117
132
  const maxDepth = args.max_depth ?? 6
118
133
 
119
134
  if (!existsSync(scanRoot)) {