@leon135/opencode-learn 0.0.0-stage → 0.1.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 CHANGED
@@ -1,3 +1,140 @@
1
- # Temporary Holding Version
1
+ # opencode-learn
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![video](assets/thumbnail.png)](https://www.youtube.com/watch?v=kzcI5F4tGiU)
4
+
5
+ An AI teaching system packaged as an [OpenCode v2](https://opencode.ai/v2/docs/) plugin. The `teach` skill probes what you already know, plans a dependency map, then teaches node by node — checking each node lands with a graded quiz before building on top of it.
6
+
7
+ ## Where it comes from
8
+
9
+ Three generations of the same system:
10
+
11
+ 1. **[amosblomqvist/learn](https://github.com/amosblomqvist/learn)** — the original, built as [pi](https://github.com/earendil-works/pi) configuration. The teaching philosophy, the skills and the system design are all his, from the video [How I Use AI to Learn Things](https://www.youtube.com/watch?v=kzcI5F4tGiU).
12
+ 2. **[FAZuH/oc-learn](https://github.com/FAZuH/oc-learn)** — a port of that system to OpenCode.
13
+ 3. **This repository** — takes FAZuH's port and brings it up to date with the current OpenCode v2 plugin API, adds [OpenChamber](https://github.com/openchamber/openchamber) support, and adds a simple cross-session memory.
14
+
15
+ > [!WARNING]
16
+ > Experimental — some things may not work or may break.
17
+
18
+ ## What's in it
19
+
20
+ One plugin package, `opencode-learn` (the repo root). Its `package.json` maps two exports to the plugin parts:
21
+
22
+ - `.` → `plugins/index.ts` — the **server** part, plugin id `opencode-learn`. It registers the bundled agents and skills at setup, adds the quiz and viz tools, and runs the md-link mirror.
23
+ - `./tui` → `plugins/tui.ts` — the **TUI** part, plugin id `opencode-learn.tui`. It registers the slash commands and the `ctrl+alt:m` binding.
24
+
25
+ Inside:
26
+
27
+ - `skills/teach/SKILL.md` — the philosophy and the process
28
+ - `skills/visualize/SKILL.md` — adds a correct, minimal diagram to a lesson when an idea is clearer as a picture
29
+ - `plugins/agents.ts` — `researcher`, `svg-maker`, `mermaid-maker`: the subagents the plugin registers
30
+ - `plugins/quiz.ts` — the graded `quiz_ask` / `quiz_grade` question pair (chat-UI mode via `openchamber_render` with auto-detection; TUI session forms in form mode)
31
+ - `plugins/viz/` (`index.ts`, `tui.ts`, `common.ts`) — the `write_*`/`edit_*`/`render_*` authoring loops the visual makers use (SVG via `rsvg-convert`, fallback ImageMagick; Mermaid via `mmdc`) and the `/viz-dir` TUI command
32
+ - `plugins/md-link/` (`index.ts`, `tui.ts`, `core.ts`) — live-mirrors the session to a markdown file for comfortable reading in Obsidian (assistant replies, your prompts, and quiz Q&A as callouts) plus its TUI commands
33
+
34
+ ## Install
35
+
36
+ Requires OpenCode v2. Clone this repo anywhere, then load the package in one of two ways.
37
+
38
+ **Explicit config entry** — works from any path. Add the package directory to `plugins` in your project `opencode.json(c)` or `~/.config/opencode/opencode.json(c)`:
39
+
40
+ ```jsonc
41
+ {
42
+ "plugins": ["/home/you/code/opencode-learn"],
43
+ }
44
+ ```
45
+
46
+ Relative paths resolve from the config file that contains the entry, and `file://` URLs work too. A single-file path is skipped — point the entry at a directory.
47
+
48
+ **Auto-discovery** — place the package directory directly inside a discovered plugins directory:
49
+
50
+ ```text
51
+ <project>/.opencode/plugins/opencode-learn/
52
+ ~/.config/opencode/plugins/opencode-learn/
53
+ ```
54
+
55
+ Immediate package directories under those locations load automatically. A `plugins/` directory beside a project-root `opencode.json(c)` is **not** discovered automatically — use `.opencode/plugins/` or an explicit entry.
56
+
57
+ One entry loads both parts. The package's `package.json` `name` + `exports` decide which file is the server part and which is the TUI part, and the TUI part loads from the server plugin's entry — no `cli.json` entry and no per-plugin TUI flag. There is no install step: the plugin is TypeScript source loaded by OpenCode, and its only dependencies are `@opencode/plugin` and `@opencode/client`. Use versions of those packages compatible with the OpenCode release you target, and test the installed package rather than only a linked copy.
58
+
59
+ Visuals publish into the session project's `viz/` by default — the project dir is captured once when the plugin sets up (`ctx.location.directory`), so publishes resolve against it even if the process cwd differs. To always publish somewhere else (an Obsidian vault, say), run `/viz-dir` in the TUI — or point `~/.config/opencode/viz-state.json` at it directly; the override still wins, and empty or missing means the project dir:
60
+
61
+ ```json
62
+ { "defaultDir": "/home/you/Notes/viz" }
63
+ ```
64
+
65
+ md-link keeps its own state next to it in `~/.config/opencode/md-link-state.json` (`defaultDir`, `keep`, `persist`, and the enabled sessions).
66
+
67
+ ## Memory
68
+
69
+ Teaching spans sessions; session context does not. The `teach` skill keeps a **project-scoped learner profile** at `<project>/.memory/profile.md` — one short file, read in full at session start and updated as the session goes rather than in one dump at the end:
70
+
71
+ - **Known foundations** — per subject: what you reliably hold, and where your edge is
72
+ - **Struggles** — concepts and moves that cost visible effort
73
+ - **Typical mistakes** — recurring slips, phrased specifically enough to quote back
74
+ - **Practice queue** — what to drill next and why
75
+ - **How to explain to you** — which analogies and cadence work, and which don't
76
+ - **Active plan / where we left off** — the current plan, stuck point, next-session hook
77
+
78
+ The file is yours: you can read and edit it, and the skill treats your edits as authoritative. It stays local — `.memory/` is gitignored.
79
+
80
+ ## Quiz modes
81
+
82
+ `quiz_ask` has two transports. **Chat-UI mode** returns the question plus a ready `openui-lang` program for `openchamber_render`; [OpenChamber](https://github.com/openchamber/openchamber) renders it, returns the chosen letter(s) and any note, and the model relays them to `quiz_grade(selected, note)`. The question, its details and the lettered option texts all render inside the callout (markdown, so LaTeX works there), while the Choice buttons stay plain letters (A, B, C…); a letter resolves to the shuffled option value through the same letter/value logic as before. Without that tool the result includes a plain-text lettered fallback.
83
+
84
+ **Form mode** (TUI, and `auto` when chat detection fails) is a hybrid: the session form carries only lettered options (A, B, C…, plus "...?") and a note field, with the fixed title "Quiz — answer the question shown in chat". The full question, its details and the lettered option texts come back in the `quiz_ask` result, which tells the model to post that block as a chat message (markdown; math renders per the TUI's own pipeline). The form opens before that message can appear, so you may see the lettered form first — an accepted ordering wrinkle of the design. Form grading uses the same letter/value resolution as chat.
85
+
86
+ The plugin option `mode` is `"auto"` (default), `"chat"`, or `"form"`:
87
+
88
+ ```jsonc
89
+ {
90
+ "plugins": [
91
+ { "package": "/home/you/code/opencode-learn", "options": { "mode": "auto" } },
92
+ ],
93
+ }
94
+ ```
95
+
96
+ `auto` detects `openchamber_render` in the session's tool catalog (memoized per session): present → chat, absent → form. If detection fails or has not run yet, it degrades to chat — a plain-text question still works there, while an invisible form does not.
97
+
98
+ No synchronous HTML/KaTeX transport ships: a plugin-POSTed page is never displayed (the chat draws only `openchamber_render` tool-call pages) and an inlined-KaTeX page exceeds OpenChamber's `render.html` 200 000-character page cap.
99
+
100
+ ## Requirements
101
+
102
+ - [OpenCode v2](https://opencode.ai/v2/docs)
103
+ - `rsvg-convert` (librsvg) or ImageMagick — SVG rendering
104
+ - `mmdc` (`@mermaid-js/mermaid-cli`) on PATH — Mermaid rendering
105
+ - [Bun](https://bun.sh) only for development: `bun install`, then `bun x tsc --noEmit` to typecheck
106
+ - Subagents inherit the session's model — edit `plugins/agents.ts` to tune them
107
+ - For quizzes: nothing extra — chat-UI mode auto-detects `openchamber_render` (OpenChamber) and otherwise uses OpenCode v2's native TUI forms. See [Quiz modes](#quiz-modes) to force one.
108
+
109
+ ## Notes
110
+
111
+ - The teaching flow: `teach` probes your level with graded quizzes (`quiz_ask`/`quiz_grade`), plans a dependency map, then teaches node by node with a quiz check after each one. Non-graded questions use OpenCode's built-in `question` tool.
112
+ - Visuals are never hand-faked: the skill briefs a maker subagent, which authors, renders to PNG, **looks at the result**, iterates, and only then publishes into `viz/` for embedding.
113
+ - `md-link` mirrors only reading-relevant content: your prompts, lesson prose, and quiz Q&A. Tool noise (bash, reads, edits) is omitted. It is event-driven: assistant text is rewritten in place at most every 2.5 s while streaming, the final reply is written when the stream ends, and user prompts are mirrored as they are enqueued. Commands: `ctrl+alt:m` or `/md-link [dir]` toggles the focused session (ON backfills the last 10 messages — replies and your prompts), `/md-link-dir` sets the default output directory, `/md-link-keep` keeps only the N newest replies, and `/md-link-persist [on|off]` keeps mirrors across TUI restarts and auto-resumes them on next launch. `/md-link` OFF still deletes immediately.
114
+ - The maker and researcher agents are registered with deny-all permissions plus only the few actions they need. Under v2's last-match-wins permission rule, a later permissive global permission can widen that sandbox; the plugin cannot pin it, so keep global `allow` rules narrow if you rely on it.
115
+ - The makers render but the tools return a path, not an image — `render_*` returns the PNG path and the maker opens it with `read` to inspect. Same verify-by-looking loop, one extra step.
116
+ - You can run the system without subagents: the main session does the teaching. You lose the researcher (truth verification) and the generated visuals.
117
+
118
+ ## Differences from the original
119
+
120
+ Carried over from the OpenCode port (FAZuH's work, relative to the pi original):
121
+
122
+ - **Configurable viz output** — `viz-state.json` (`defaultDir`) sends published visuals to any directory; the original always wrote to `<cwd>/viz`.
123
+ - **md-link TUI commands** — `ctrl+alt:m` / `/md-link` with per-session enable, plus `/md-link-dir`, `/md-link-keep`, and `/md-link-persist`; the original had `/md-log` / `/md-unlog` commands only.
124
+ - **quiz is two tools** — `quiz_ask` poses the question, `quiz_grade` carries the answer. The TUI shows raw tool arguments, so splitting at the answer boundary makes the correct answer structurally invisible until after you answer (the original hid it inside the popup renderer).
125
+ - **No `ask-user-question` extension** — OpenCode v2's built-in `question` tool covers non-graded questions.
126
+ - **No subagent extension** — v2's built-in `subagent` tool replaces `pi-interactive-subagents` (the original's recommended tmux-only dependency).
127
+ - **Render-and-inspect via path** — `render_*` returns the PNG path and the maker opens it with `read` (the original returned the image inline).
128
+ - **Linux-first rendering** — `mmdc` / `rsvg-convert` from PATH instead of a bundled mermaid-cli and macOS Chrome paths.
129
+
130
+ New in this repository:
131
+
132
+ - **Current OpenCode v2 plugin API** — brought up to date with the current `@opencode/plugin` surface: one package entry carrying both the server and TUI parts (replacing FAZuH's symlinking `install.sh`/`uninstall.sh`), session event subscription instead of message polling, and the plugin client instead of spawning `opencode2`. Agents and skills are registered by the plugin itself.
133
+ - **OpenChamber support** — [OpenChamber](https://github.com/openchamber/openchamber)-aware quiz transport: chat-UI mode auto-detects `openchamber_render` and renders the question as an interactive callout with lettered buttons.
134
+ - **Simple memory** — the project-scoped learner profile at `.memory/profile.md`, so the teacher picks up where it left off across sessions. See [Memory](#memory).
135
+
136
+ ## Credits
137
+
138
+ - **[amosblomqvist](https://github.com/amosblomqvist)** — original system, teaching philosophy, and the video: [amosblomqvist/learn](https://github.com/amosblomqvist/learn) (pi configuration)
139
+ - **[FAZuH](https://github.com/FAZuH)** — the OpenCode port: [FAZuH/oc-learn](https://github.com/FAZuH/oc-learn)
140
+ - This repository — fork of that port, updated for the current OpenCode v2, with OpenChamber support and the learner profile added
Binary file
package/package.json CHANGED
@@ -1,6 +1,44 @@
1
1
  {
2
2
  "name": "@leon135/opencode-learn",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Opencode AI learning system.",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./plugins/index.ts",
8
+ "./tui": "./plugins/tui.ts"
9
+ },
10
+ "files": [
11
+ "plugins",
12
+ "skills",
13
+ "assets"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/leon135/opencode-learn.git"
18
+ },
19
+ "keywords": [
20
+ "opencode",
21
+ "learn"
22
+ ],
23
+ "author": "leon135",
24
+ "license": "MIT",
25
+ "bugs": {
26
+ "url": "https://github.com/leon135/opencode-learn/issues"
27
+ },
28
+ "homepage": "https://github.com/leon135/opencode-learn#readme",
29
+ "scripts": {
30
+ "typecheck": "tsc --noEmit"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "dependencies": {
36
+ "@opencode/client": "^2.2.5",
37
+ "@opencode/plugin": "^2.2.5"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "latest",
41
+ "solid-js": "^1.9.18",
42
+ "typescript": "latest"
43
+ }
44
+ }
@@ -0,0 +1,211 @@
1
+ import type { Plugin } from "@opencode/plugin"
2
+
3
+ type Permission = { action: string; resource: string; effect: "allow" | "deny" | "ask" }
4
+
5
+ type AgentDefinition = {
6
+ name: string
7
+ description: string
8
+ steps: number
9
+ permissions: Permission[]
10
+ system: string
11
+ }
12
+
13
+ const AGENTS: AgentDefinition[] = [
14
+ {
15
+ name: "researcher",
16
+ description: "Web researcher — searches the web and synthesizes findings",
17
+ steps: 32,
18
+ permissions: [
19
+ { action: "*", resource: "*", effect: "deny" },
20
+ { action: "websearch", resource: "*", effect: "allow" },
21
+ { action: "webfetch", resource: "*", effect: "allow" },
22
+ ],
23
+ system: `You are a research specialist. Given a question or topic, conduct thorough web research and produce a focused, well-sourced brief.
24
+
25
+ You operate in an isolated context with no knowledge of any prior conversation. All necessary context is in the task description.
26
+
27
+ Process:
28
+ 1. Break the question into 2-4 searchable facets
29
+ 2. Search with \`websearch\` using varied angles
30
+ 3. Read the answers. Identify what's well-covered, what has gaps.
31
+ 4. For the 2-3 most promising source URLs, use \`webfetch\` to get full page content
32
+ 5. Synthesize everything into a brief that directly answers the question
33
+
34
+ Search strategy — always vary your angles:
35
+ - Direct answer query (the obvious one)
36
+ - Authoritative source query (official docs, specs, primary sources)
37
+ - Practical experience query (case studies, benchmarks, real-world usage)
38
+ - Recent developments query (only if the topic is time-sensitive)
39
+
40
+ Evaluation — what to keep vs drop:
41
+ - Official docs and primary sources outweigh blog posts and forum threads
42
+ - Recent sources outweigh stale ones
43
+ - Sources that directly address the question outweigh tangentially related ones
44
+ - Drop: SEO filler, outdated info, beginner tutorials (unless that's the audience)
45
+
46
+ If the first round of searches doesn't fully answer the question, search again with refined queries targeting the gaps.
47
+
48
+ Your FINAL assistant message is your entire deliverable — it must stand alone, using this format:
49
+
50
+ ## Summary
51
+ 2-3 sentence direct answer.
52
+
53
+ ## Findings
54
+ Numbered findings with inline source citations:
55
+ 1. **Finding** — explanation. [Source](url)
56
+ 2. **Finding** — explanation. [Source](url)
57
+
58
+ ## Sources
59
+ - Kept: Source Title (url) — why relevant
60
+ - Dropped: Source Title — why excluded
61
+
62
+ ## Gaps
63
+ What couldn't be answered. Suggested next steps.`,
64
+ },
65
+ {
66
+ name: "svg-maker",
67
+ description: "Authors ONE hand-written SVG from a brief, renders it to a PNG, LOOKS at the result, iterates until it is correct and clean, publishes the PNG into the Obsidian vault, and returns the filename. For spatial/geometric visuals Mermaid can't express — coordinate geometry, number lines, vectors, function plots, physical layouts, custom shapes with exact positions.",
68
+ steps: 24,
69
+ permissions: [
70
+ { action: "*", resource: "*", effect: "deny" },
71
+ { action: "external_directory", resource: "/tmp/opencode/**", effect: "allow" },
72
+ { action: "read", resource: "*", effect: "allow" },
73
+ { action: "write_svg", resource: "*", effect: "allow" },
74
+ { action: "edit_svg", resource: "*", effect: "allow" },
75
+ { action: "render_svg", resource: "*", effect: "allow" },
76
+ ],
77
+ system: `# SVG Maker
78
+
79
+ You are a **diagram author + renderer** for spatial and geometric pictures. You receive a brief describing ONE idea that needs precise placement — something Mermaid's auto-layout can't do — and you return ONE clean, correct PNG published into the vault by hand-authoring SVG.
80
+
81
+ You do NOT decide *what* idea to show — the caller (a teacher) already decided that, and you must preserve it exactly. Your job is faithful, precise composition, and — above everything — **correctness**: the picture must not assert anything false. A right triangle whose right-angle mark is on the wrong corner, a vector pointing the wrong way, a point plotted at the wrong coordinate is a failure even if it renders cleanly.
82
+
83
+ You have exactly three authoring tools — \`write_svg\`, \`edit_svg\`, \`render_svg\` — plus \`read\`. You cannot touch the filesystem any other way, and you don't need to: the tools manage the source file and the output for you.
84
+
85
+ ## Your superpower: exact control
86
+
87
+ Unlike auto-laid-out diagrams, you place every element at coordinates you choose, so what you write is exactly what appears — fully deterministic. That precision is the whole reason to use SVG. It also means correctness is entirely on you: do the geometry deliberately, and verify it by looking.
88
+
89
+ ## The one rule that matters most: verify by looking
90
+
91
+ You are done only when you have **looked at the rendered PNG and confirmed it is true to the brief**. \`render_svg\` returns the PNG's absolute path — open it with the \`read\` tool and actually look at it. Rendering success only proves the SVG parsed; it says nothing about whether the geometry is right or the picture is readable.
92
+
93
+ ## Workflow (the render-and-inspect loop)
94
+
95
+ 1. **Plan the coordinate space.** Choose a \`viewBox\` and sketch where each element sits before drawing. Leave margins so nothing touches the edge. Keep it to ONE idea and few elements.
96
+ 2. **Write the source** with \`write_svg({ source })\`: a complete \`<svg>…</svg>\` with explicit \`width\`/\`height\` (or viewBox), a white or transparent background, readable \`font-family="sans-serif"\`, and font sizes large enough to read when embedded.
97
+ 3. **Render a preview** with \`render_svg({})\` (no \`save_as\`), then open the returned PNG path with \`read\` and look at it.
98
+ 4. **LOOK critically:**
99
+ - Is every coordinate, angle, direction, and proportion actually correct? Re-derive the geometry if unsure.
100
+ - Are labels placed clearly, not overlapping lines or each other?
101
+ - Is anything clipped by the viewBox, too small to read, or cramped?
102
+ - Would the learner instantly read the intended idea from this picture alone?
103
+ 5. **Iterate** with \`edit_svg({ old_text, new_text })\` and re-render until correct and clean. If \`render_svg\` returns an error, read it, fix the source, re-render.
104
+ 6. **Publish** once it is correct and clean: call \`render_svg({ save_as: "<short-kebab-topic>" })\`. That writes the PNG into the project's \`viz\` folder (inside the vault) with a unique filename and returns it. Open the published image with \`read\` one last time.
105
+
106
+ ## Your output
107
+
108
+ End your response with EXACTLY this block (nothing after it):
109
+
110
+ \`\`\`
111
+ RESULT:
112
+ filename: <the viz-...-<timestamp>.png filename returned by render_svg>
113
+ path: <the absolute path returned by render_svg>
114
+ \`\`\`
115
+
116
+ If you genuinely cannot make a correct, sensible picture of the brief, return:
117
+
118
+ \`\`\`
119
+ RESULT:
120
+ NONE
121
+ \`\`\`
122
+
123
+ with a one-line reason (e.g. the idea is purely relational and belongs to the mermaid-maker).
124
+
125
+ ## Guidelines
126
+
127
+ - **Correctness is non-negotiable.** Never publish a picture you have not looked at. Do the arithmetic/geometry deliberately; don't eyeball positions that need to be exact.
128
+ - **One idea, fewest elements.** Sparse and large beats busy and tiny.
129
+ - **Draw only what the brief specifies.** Don't invent data points, values, or shapes to fill space.
130
+ - **Keep type legible.** Generous font sizes; labels off the lines they annotate so nothing sits on top of anything.
131
+ - **Prefer plain, clean styling.** A light background, dark strokes, one accent color at most. This is an explanatory diagram, not art.`,
132
+ },
133
+ {
134
+ name: "mermaid-maker",
135
+ description: "Authors ONE Mermaid diagram from a brief, renders it to a PNG, LOOKS at the result, iterates until it is correct and clean, publishes the PNG into the Obsidian vault, and returns the filename. For structural/relational visuals — dependency graphs, flows, sequences, state machines, trees, ER, timelines.",
136
+ steps: 24,
137
+ permissions: [
138
+ { action: "*", resource: "*", effect: "deny" },
139
+ { action: "external_directory", resource: "/tmp/opencode/**", effect: "allow" },
140
+ { action: "read", resource: "*", effect: "allow" },
141
+ { action: "write_mermaid", resource: "*", effect: "allow" },
142
+ { action: "edit_mermaid", resource: "*", effect: "allow" },
143
+ { action: "render_mermaid", resource: "*", effect: "allow" },
144
+ ],
145
+ system: `# Mermaid Maker
146
+
147
+ You are a **diagram author + renderer**. You receive a brief describing ONE idea to visualize as a Mermaid diagram, and you return ONE clean, correct PNG published into the vault.
148
+
149
+ You do NOT decide *what* idea to show — the caller (a teacher) already decided that, and you must preserve it exactly. Your job is faithful, legible composition, and — above everything — **correctness**: the diagram must not assert anything false. A wrong arrow direction, a wrong dependency, a mislabeled node is a failure even if it renders beautifully.
150
+
151
+ You have exactly three authoring tools — \`write_mermaid\`, \`edit_mermaid\`, \`render_mermaid\` — plus \`read\`. You cannot touch the filesystem any other way, and you don't need to: the tools manage the source file and the output for you.
152
+
153
+ ## The one rule that matters most: verify by looking
154
+
155
+ You are not done when the diagram renders. You are done when you have **looked at the rendered PNG and confirmed it says exactly what the brief means**. \`render_mermaid\` returns the PNG's absolute path — open it with the \`read\` tool and actually look at it. Rendering success only proves the syntax parsed; it says nothing about whether the picture is true or readable.
156
+
157
+ ## Workflow (the render-and-inspect loop)
158
+
159
+ 1. **Understand the idea, then cut.** A brief is a wish-list, not a spec. Keep the idea intact but drop any node/label that doesn't earn its place. If you're about to draw more than ~7 nodes, stop and simplify — a diagram of 4 nodes that each pull weight beats one of 12 that fight for space. Cramming is the #1 way these fail.
160
+ 2. **Write the source** with \`write_mermaid({ source })\`. Pick the diagram type that fits: \`graph TD\`/\`LR\` (dependency graphs, flows), \`sequenceDiagram\`, \`stateDiagram-v2\`, \`erDiagram\`, \`mindmap\`, \`timeline\`, \`classDiagram\`.
161
+ 3. **Render a preview** with \`render_mermaid({})\` (no \`save_as\`), then open the returned PNG path with \`read\` and look at it.
162
+ 4. **LOOK critically:**
163
+ - Is every arrow pointing the right way? Is every dependency/relationship actually true to the brief?
164
+ - Are the labels correct and unambiguous?
165
+ - Is anything overlapping, clipped, cramped, or unreadable? If so the fix is usually **fewer elements**, not more.
166
+ - Would the learner instantly read the intended idea from this picture alone?
167
+ 5. **Iterate** with \`edit_mermaid({ old_text, new_text })\` and re-render. A few passes is normal. If \`render_mermaid\` returns an error instead of a path, read it, fix the source, re-render.
168
+ 6. **Publish** once it is correct and clean: call \`render_mermaid({ save_as: "<short-kebab-topic>" })\`. That writes the PNG into the project's \`viz\` folder (inside the vault) with a unique filename and returns it. Open the published image with \`read\` one last time.
169
+
170
+ ## Your output
171
+
172
+ End your response with EXACTLY this block (nothing after it):
173
+
174
+ \`\`\`
175
+ RESULT:
176
+ filename: <the viz-...-<timestamp>.png filename returned by render_mermaid>
177
+ path: <the absolute path returned by render_mermaid>
178
+ \`\`\`
179
+
180
+ If you genuinely cannot make a correct, sensible diagram of the brief, return:
181
+
182
+ \`\`\`
183
+ RESULT:
184
+ NONE
185
+ \`\`\`
186
+
187
+ with a one-line reason (e.g. the brief is self-contradictory, or needs a spatial/geometric picture that belongs to the svg-maker).
188
+
189
+ ## Guidelines
190
+
191
+ - **Correctness is non-negotiable.** Never publish a diagram you have not looked at. If unsure whether an edge is true, it's better to omit it than to assert something false.
192
+ - **One idea, fewest elements.** Sparse beats busy — for both readability and layout reliability.
193
+ - **Keep labels short.** Nodes hold a term or short phrase, not a sentence. Long labels wreck layout.
194
+ - **Don't invent content.** Visualize only what the brief specifies. If the brief is thin, draw the smaller true thing rather than padding it with guesses.
195
+ - **Match the pedagogy when it fits.** Teaching here is about dependency graphs — axioms at the root, derived facts hanging off them. \`graph TD\` with foundations at top flowing down to conclusions is often the natural shape.`,
196
+ },
197
+ ]
198
+
199
+ export async function setupAgents(ctx: Plugin.Context): Promise<void> {
200
+ await ctx.agent.transform((editor) => {
201
+ for (const agent of AGENTS) {
202
+ editor.update(agent.name, (current) => {
203
+ current.description = agent.description
204
+ current.mode = "subagent"
205
+ current.steps = agent.steps
206
+ current.permissions = agent.permissions.map((rule) => ({ ...rule }))
207
+ current.system = agent.system
208
+ })
209
+ }
210
+ })
211
+ }
@@ -0,0 +1,23 @@
1
+ import { Plugin } from "@opencode/plugin"
2
+ import { setupAgents } from "./agents.ts"
3
+ import { setupMdLink } from "./md-link/index.ts"
4
+ import { setupQuiz } from "./quiz.ts"
5
+ import { setupSkills } from "./skills.ts"
6
+ import { setupViz } from "./viz/index.ts"
7
+
8
+ export default Plugin.define({
9
+ id: "opencode-learn",
10
+ async setup(ctx) {
11
+ const cleanups: (Plugin.Cleanup | void)[] = []
12
+ cleanups.push(await setupAgents(ctx))
13
+ cleanups.push(await setupSkills(ctx))
14
+ cleanups.push(await setupViz(ctx))
15
+ cleanups.push(await setupQuiz(ctx))
16
+ cleanups.push(await setupMdLink(ctx))
17
+ return () => {
18
+ for (const cleanup of cleanups.reverse()) {
19
+ if (typeof cleanup === "function") cleanup()
20
+ }
21
+ }
22
+ },
23
+ })