@agent-workshop/adoc-plugin-kit 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/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ Copyright (C) 2026 adoc contributors
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
4
+
5
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,49 @@
1
+ # Third-party notices
2
+
3
+ adoc's original code and documentation use the Zero-Clause BSD License (0BSD). That license does not replace the
4
+ licenses of third-party components, and no third-party component is relicensed by this repository.
5
+
6
+ ## Browser distributions
7
+
8
+ Two packages carry bundled browser code:
9
+
10
+ - `@agent-workshop/adoc-webapp`: the web UI in `dist/`. Its build records the packages actually bundled in
11
+ `dist/licenses/packages.json` and copies their upstream LICENSE, COPYING, COPYRIGHT and NOTICE files beneath
12
+ `dist/licenses/`.
13
+ - `@agent-workshop/adoc-plugin-sketch`: the drawing board in `client/`, with its inventory and license files in
14
+ `client/licenses/`.
15
+
16
+ Keep these files when redistributing the built browser assets.
17
+
18
+ The main browser dependencies are React, React DOM, React Router, Mermaid (with KaTeX), xterm.js and Lucide in the web
19
+ UI, and Excalidraw with React and Radix UI in the drawing board. Most use MIT, ISC, BSD or Apache-2.0.
20
+
21
+ - DOMPurify offers MPL-2.0 OR Apache-2.0; adoc uses the Apache-2.0 option.
22
+ - Some packages are published without a license file although their manifest names one: Excalidraw, the Radix UI
23
+ primitives, react-remove-scroll-bar and fastdom (all MIT). The build includes the upstream texts kept in
24
+ `tooling/licenses/`.
25
+
26
+ ### Eclipse Layout Kernel / elkjs
27
+
28
+ Mermaid bundles elkjs, a separately licensed layout engine, which adoc uses under EPL-2.0 without modifying it. Its
29
+ license is included in the generated license inventory. Corresponding source is available from:
30
+
31
+ - https://github.com/kieler/elkjs (select the release matching the inventory version)
32
+ - https://github.com/eclipse/elk (the underlying Eclipse Layout Kernel)
33
+ - https://www.eclipse.org/legal/epl-2.0/
34
+
35
+ Recipients may obtain, modify and redistribute the EPL-covered source under EPL-2.0. adoc's 0BSD license applies to its
36
+ independent code, not to ELK.
37
+
38
+ ## Fonts
39
+
40
+ - The web UI serves Pretendard, D2Coding (both SIL Open Font License 1.1) and Symbols Nerd Font Mono; their license
41
+ files are next to them in `dist/assets/fonts/`.
42
+ - The drawing board serves the fonts that Excalidraw ships in `client/fonts/` (Excalifont, Virgil, Nunito, Lilita One,
43
+ Comic Shanns, Cascadia Code, Liberation Sans, Assistant and Xiaolai); they are distributed under the terms that
44
+ Excalidraw states for them in https://github.com/excalidraw/excalidraw.
45
+
46
+ ## Installed runtime dependencies
47
+
48
+ The packages that npm installs next to adoc, such as the MCP SDK, ws, yaml, zod, semver, commander, markdown-it and
49
+ highlight.js, are not bundled: each keeps its own license in its own package.
@@ -0,0 +1,29 @@
1
+ import { z } from 'zod';
2
+ import type { PluginDefinition } from './types.js';
3
+ import { HtmlFragment } from './types.js';
4
+ /** Validates what `summarize` returned. */
5
+ export declare const summarySchema: z.ZodObject<{
6
+ title: z.ZodString;
7
+ status: z.ZodString;
8
+ fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
9
+ }, z.core.$strip>;
10
+ /** Validates what an action handler returned. */
11
+ export declare const actionResultSchema: z.ZodObject<{
12
+ text: z.ZodOptional<z.ZodString>;
13
+ files: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
14
+ base64: z.ZodString;
15
+ }, z.core.$strict>]>>>;
16
+ companions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
17
+ base64: z.ZodString;
18
+ }, z.core.$strict>]>>>;
19
+ message: z.ZodOptional<z.ZodString>;
20
+ }, z.core.$strict>;
21
+ /** Validates what `render` returned. */
22
+ export declare const renderResultSchema: z.ZodUnion<readonly [z.ZodString, z.ZodCustom<HtmlFragment, HtmlFragment>]>;
23
+ /** Turns a zod error into one line per problem: `field.path: message`. */
24
+ export declare function describeIssues(error: z.ZodError, prefix: string): string;
25
+ /**
26
+ * Declares a plugin. Default-export its result from the plugin's `index.ts`.
27
+ * Throws a one-line error naming each wrong field, so mistakes show up when adoc loads the plugin.
28
+ */
29
+ export declare function definePlugin(definition: PluginDefinition): PluginDefinition;
package/dist/define.js ADDED
@@ -0,0 +1,65 @@
1
+ import { z } from 'zod';
2
+ import { HtmlFragment } from './types.js';
3
+ const fn = z.custom((v) => typeof v === 'function', 'must be a function');
4
+ const EXTENSION = /^\.[a-z0-9]+$/;
5
+ const layoutSchema = z.discriminatedUnion('kind', [
6
+ z.object({
7
+ kind: z.literal('file'),
8
+ extension: z.string().regex(EXTENSION, 'must look like ".md" (a dot, then lowercase letters or digits)'),
9
+ companions: z.array(z.string().regex(EXTENSION, 'each must look like ".png" (a dot, then lowercase letters or digits)')).optional(),
10
+ }),
11
+ z.object({ kind: z.literal('folder'), entry: z.string().regex(/^[^/\\]+$/, 'must be a file name inside the folder, such as "bug.yaml"') }),
12
+ ]);
13
+ /** Action names the web UI's document header sends for every document; the agent handles them, never a plugin. */
14
+ const RESERVED_ACTIONS = ['archive', 'unarchive'];
15
+ const definitionSchema = z.object({
16
+ description: z.string().min(1, 'must be a non-empty one-line description'),
17
+ layout: layoutSchema,
18
+ summarize: fn,
19
+ render: fn,
20
+ renderChanges: fn.optional(),
21
+ actions: z
22
+ .record(z.string().regex(/^[a-z][a-z0-9-]*$/, 'action names must be lowercase words such as "toggle"'), fn)
23
+ .superRefine((actions, context) => {
24
+ for (const name of RESERVED_ACTIONS.filter((reserved) => reserved in actions)) {
25
+ context.addIssue({ code: 'custom', path: [name], message: 'archive and unarchive are sent by the document header for the agent; choose another name' });
26
+ }
27
+ })
28
+ .optional(),
29
+ });
30
+ /** Validates what `summarize` returned. */
31
+ export const summarySchema = z.object({
32
+ title: z.string(),
33
+ status: z.string(),
34
+ fields: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(),
35
+ });
36
+ /** A file content in an action result: text, or binary data as base64. */
37
+ const contentSchema = z.union([z.string(), z.object({ base64: z.string() }).strict()]);
38
+ /** Validates what an action handler returned. */
39
+ export const actionResultSchema = z
40
+ .object({
41
+ text: z.string().optional(),
42
+ files: z.record(z.string(), contentSchema).optional(),
43
+ companions: z.record(z.string(), contentSchema).optional(),
44
+ message: z.string().optional(),
45
+ })
46
+ .strict();
47
+ /** Validates what `render` returned. */
48
+ export const renderResultSchema = z.union([z.string(), z.instanceof(HtmlFragment)], {
49
+ error: 'must return a string or the result of html`…`',
50
+ });
51
+ /** Turns a zod error into one line per problem: `field.path: message`. */
52
+ export function describeIssues(error, prefix) {
53
+ return error.issues.map((issue) => `${[prefix, ...issue.path.map(String)].filter(Boolean).join('.')}: ${issue.message}`).join('; ');
54
+ }
55
+ /**
56
+ * Declares a plugin. Default-export its result from the plugin's `index.ts`.
57
+ * Throws a one-line error naming each wrong field, so mistakes show up when adoc loads the plugin.
58
+ */
59
+ export function definePlugin(definition) {
60
+ const result = definitionSchema.safeParse(definition);
61
+ if (!result.success)
62
+ throw new Error(`definePlugin: ${describeIssues(result.error, '')}`);
63
+ return definition;
64
+ }
65
+ //# sourceMappingURL=define.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define.js","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"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"}
package/dist/diff.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import type { HtmlFragment } from './types.js';
2
+ /** One line of a line diff. `line` is the 1-based line in the new text for `same` and `added`, in the old text for `removed`. */
3
+ export interface DiffLine {
4
+ op: 'same' | 'added' | 'removed';
5
+ text: string;
6
+ line: number;
7
+ }
8
+ /** Compares two texts line by line (longest common subsequence). */
9
+ export declare function diffLines(oldText: string, newText: string): DiffLine[];
10
+ /**
11
+ * Renders a line diff of two texts as HTML: added lines get `adoc-added`, removed lines `adoc-removed`.
12
+ * With `file`, current lines carry their source position so that comments in the diff still point at the file.
13
+ */
14
+ export declare function sourceDiff(oldText: string, newText: string, options?: {
15
+ file?: string;
16
+ }): HtmlFragment;
17
+ /**
18
+ * Writes the changes from one text to another as a unified diff (`--- a/<file>`, `+++ b/<file>`, `@@` hunks with
19
+ * `context` unchanged lines around each change). Empty when the texts are equal.
20
+ */
21
+ export declare function unifiedDiff(oldText: string, newText: string, file: string, context?: number): string;
package/dist/diff.js ADDED
@@ -0,0 +1,87 @@
1
+ import { escapeHtml, raw } from './html.js';
2
+ const LIMIT = 4_000_000;
3
+ /** Compares two texts line by line (longest common subsequence). */
4
+ export function diffLines(oldText, newText) {
5
+ const a = oldText.split('\n');
6
+ const b = newText.split('\n');
7
+ if (a.length * b.length > LIMIT) {
8
+ return [...a.map((text, i) => ({ op: 'removed', text, line: i + 1 })), ...b.map((text, i) => ({ op: 'added', text, line: i + 1 }))];
9
+ }
10
+ const lcs = Array.from({ length: a.length + 1 }, () => new Uint32Array(b.length + 1));
11
+ for (let i = a.length - 1; i >= 0; i--) {
12
+ for (let j = b.length - 1; j >= 0; j--)
13
+ lcs[i][j] = a[i] === b[j] ? lcs[i + 1][j + 1] + 1 : Math.max(lcs[i + 1][j], lcs[i][j + 1]);
14
+ }
15
+ const out = [];
16
+ let i = 0;
17
+ let j = 0;
18
+ while (i < a.length && j < b.length) {
19
+ if (a[i] === b[j])
20
+ out.push({ op: 'same', text: b[j], line: ++j }), i++;
21
+ else if (lcs[i + 1][j] >= lcs[i][j + 1])
22
+ out.push({ op: 'removed', text: a[i], line: ++i });
23
+ else
24
+ out.push({ op: 'added', text: b[j], line: ++j });
25
+ }
26
+ while (i < a.length)
27
+ out.push({ op: 'removed', text: a[i], line: ++i });
28
+ while (j < b.length)
29
+ out.push({ op: 'added', text: b[j], line: ++j });
30
+ return out;
31
+ }
32
+ /**
33
+ * Renders a line diff of two texts as HTML: added lines get `adoc-added`, removed lines `adoc-removed`.
34
+ * With `file`, current lines carry their source position so that comments in the diff still point at the file.
35
+ */
36
+ export function sourceDiff(oldText, newText, options = {}) {
37
+ const rows = diffLines(oldText, newText).map((d) => {
38
+ const cls = d.op === 'same' ? 'adoc-same' : d.op === 'added' ? 'adoc-added' : 'adoc-removed';
39
+ const mark = d.op === 'same' ? ' ' : d.op === 'added' ? '+' : '-';
40
+ const src = options.file && d.op !== 'removed' ? ` data-adoc-source="${escapeHtml(`${options.file}:${d.line}`)}"` : '';
41
+ return `<div class="${cls}"${src}><span class="adoc-diff-mark">${mark}</span>${escapeHtml(d.text) || ' '}</div>`;
42
+ });
43
+ return raw(`<div class="adoc-diff">${rows.join('')}</div>`);
44
+ }
45
+ /**
46
+ * Writes the changes from one text to another as a unified diff (`--- a/<file>`, `+++ b/<file>`, `@@` hunks with
47
+ * `context` unchanged lines around each change). Empty when the texts are equal.
48
+ */
49
+ export function unifiedDiff(oldText, newText, file, context = 2) {
50
+ const lines = diffLines(oldText, newText);
51
+ const numbered = [];
52
+ let oldLine = 0;
53
+ let newLine = 0;
54
+ for (const line of lines) {
55
+ if (line.op !== 'added')
56
+ oldLine++;
57
+ if (line.op !== 'removed')
58
+ newLine++;
59
+ numbered.push({ op: line.op, text: line.text, oldLine, newLine });
60
+ }
61
+ const changed = numbered.map((l, i) => (l.op === 'same' ? -1 : i)).filter((i) => i >= 0);
62
+ if (!changed.length)
63
+ return '';
64
+ const hunks = [];
65
+ for (const i of changed) {
66
+ const from = Math.max(0, i - context);
67
+ const to = Math.min(numbered.length - 1, i + context);
68
+ const last = hunks.at(-1);
69
+ if (last && from <= last[1] + 1)
70
+ last[1] = Math.max(last[1], to);
71
+ else
72
+ hunks.push([from, to]);
73
+ }
74
+ const out = [`--- a/${file}`, `+++ b/${file}`];
75
+ for (const [from, to] of hunks) {
76
+ const part = numbered.slice(from, to + 1);
77
+ const oldCount = part.filter((l) => l.op !== 'added').length;
78
+ const newCount = part.filter((l) => l.op !== 'removed').length;
79
+ const oldStart = part.find((l) => l.op !== 'added')?.oldLine ?? numbered[from].oldLine;
80
+ const newStart = part.find((l) => l.op !== 'removed')?.newLine ?? numbered[from].newLine;
81
+ out.push(`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`);
82
+ for (const l of part)
83
+ out.push(`${l.op === 'added' ? '+' : l.op === 'removed' ? '-' : ' '}${l.text}`);
84
+ }
85
+ return out.join('\n');
86
+ }
87
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"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"}
package/dist/html.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { HtmlFragment } from './types.js';
2
+ /** Escapes text for HTML content and attribute values. */
3
+ export declare function escapeHtml(value: string): string;
4
+ /** Marks a string as trusted HTML so that `html` inserts it unescaped. */
5
+ export declare function raw(value: string): HtmlFragment;
6
+ /**
7
+ * Builds HTML. Interpolated values are escaped, except `HtmlFragment`s from `html`, `raw`,
8
+ * `markdown` and the markup helpers. Arrays are joined; `null`, `undefined` and `false` print nothing.
9
+ *
10
+ * html`<li ${anchor(item.id)}>${item.text}</li>`
11
+ */
12
+ export declare function html(strings: TemplateStringsArray, ...values: unknown[]): HtmlFragment;
package/dist/html.js ADDED
@@ -0,0 +1,33 @@
1
+ import { HtmlFragment } from './types.js';
2
+ const ESCAPES = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
3
+ /** Escapes text for HTML content and attribute values. */
4
+ export function escapeHtml(value) {
5
+ return value.replace(/[&<>"']/g, (c) => ESCAPES[c]);
6
+ }
7
+ /** Marks a string as trusted HTML so that `html` inserts it unescaped. */
8
+ export function raw(value) {
9
+ return new HtmlFragment(value);
10
+ }
11
+ function interpolate(value) {
12
+ if (value instanceof HtmlFragment)
13
+ return value.html;
14
+ if (Array.isArray(value))
15
+ return value.map(interpolate).join('');
16
+ if (value === null || value === undefined || value === false)
17
+ return '';
18
+ return escapeHtml(String(value));
19
+ }
20
+ /**
21
+ * Builds HTML. Interpolated values are escaped, except `HtmlFragment`s from `html`, `raw`,
22
+ * `markdown` and the markup helpers. Arrays are joined; `null`, `undefined` and `false` print nothing.
23
+ *
24
+ * html`<li ${anchor(item.id)}>${item.text}</li>`
25
+ */
26
+ export function html(strings, ...values) {
27
+ let out = strings[0];
28
+ values.forEach((value, index) => {
29
+ out += interpolate(value) + strings[index + 1];
30
+ });
31
+ return new HtmlFragment(out);
32
+ }
33
+ //# sourceMappingURL=html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.js","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,OAAO,GAA2B,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;AAEhH,0DAA0D;AAC1D,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,CAAC;AACvD,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,GAAG,CAAC,KAAa;IAC/B,OAAO,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,KAAK,YAAY,YAAY;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC;IACrD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjE,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,EAAE,CAAC;IACxE,OAAO,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACnC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,IAAI,CAAC,OAA6B,EAAE,GAAG,MAAiB;IACtE,IAAI,GAAG,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;IACtB,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QAC9B,GAAG,IAAI,WAAW,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,KAAK,GAAG,CAAC,CAAE,CAAC;IAClD,CAAC,CAAC,CAAC;IACH,OAAO,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,10 @@
1
+ export type { ActionEvent, ActionHandler, ActionResult, DocumentLayout, DocumentSummary, FileContent, PluginDefinition, PluginDocument, } from './types.js';
2
+ export { HtmlFragment } from './types.js';
3
+ export { definePlugin, summarySchema, actionResultSchema, renderResultSchema, describeIssues } from './define.js';
4
+ export { html, raw, escapeHtml } from './html.js';
5
+ export { anchor, source, action, dropTarget, ref, slug } from './markup.js';
6
+ export { diffLines, sourceDiff, unifiedDiff } from './diff.js';
7
+ export type { DiffLine } from './diff.js';
8
+ export { markdown, frontmatter, findReferences, REFERENCE_PATTERN } from './markdown.js';
9
+ export type { MarkdownOptions } from './markdown.js';
10
+ export { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { HtmlFragment } from './types.js';
2
+ export { definePlugin, summarySchema, actionResultSchema, renderResultSchema, describeIssues } from './define.js';
3
+ export { html, raw, escapeHtml } from './html.js';
4
+ export { anchor, source, action, dropTarget, ref, slug } from './markup.js';
5
+ export { diffLines, sourceDiff, unifiedDiff } from './diff.js';
6
+ export { markdown, frontmatter, findReferences, REFERENCE_PATTERN } from './markdown.js';
7
+ export { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClH,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE/D,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEzF,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,MAAM,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { HtmlFragment } from './types.js';
2
+ /** The reference notation `[[KEY]]` or `[[KEY#anchor]]`. */
3
+ export declare const REFERENCE_PATTERN: RegExp;
4
+ export interface MarkdownOptions {
5
+ /** The workspace-relative file the text comes from, usually `doc.file`. Enables source positions. */
6
+ file?: string;
7
+ /** The 1-based line in `file` where `text` starts. Default 1. */
8
+ line?: number;
9
+ /** Render a single line without a surrounding paragraph. */
10
+ inline?: boolean;
11
+ }
12
+ /**
13
+ * Renders Markdown to trusted HTML.
14
+ * - `[[KEY]]` and `[[KEY#anchor]]` become reference links.
15
+ * - With `file`, every block element gets its source position, so comments carry `file:line`.
16
+ * - A list item starting with `[ ] ` or `[x] ` gets a read-only checkbox.
17
+ * - A fenced block of a known language is syntax-highlighted; a ```mermaid block is drawn as a diagram by the web UI.
18
+ * - Raw HTML in the text is escaped.
19
+ */
20
+ export declare function markdown(text: string, options?: MarkdownOptions): HtmlFragment;
21
+ /** Lists every reference key written as `[[KEY]]` in the text. */
22
+ export declare function findReferences(text: string): string[];
23
+ /**
24
+ * Splits YAML front matter from Markdown.
25
+ * Returns the parsed data, the body and `bodyLine`, the 1-based line where the body starts,
26
+ * which is what `markdown(body, { file, line: bodyLine })` needs.
27
+ */
28
+ export declare function frontmatter(text: string): {
29
+ data: Record<string, unknown>;
30
+ body: string;
31
+ bodyLine: number;
32
+ };
@@ -0,0 +1,100 @@
1
+ import hljs from 'highlight.js/lib/common';
2
+ import markdownIt from 'markdown-it';
3
+ import { parse as parseYaml } from 'yaml';
4
+ import { raw } from './html.js';
5
+ import { ref } from './markup.js';
6
+ /** The reference notation `[[KEY]]` or `[[KEY#anchor]]`. */
7
+ export const REFERENCE_PATTERN = /\[\[([A-Z]+-[a-z0-9._-]+(?:#[^\]\s]+)?)\]\]/g;
8
+ function referenceRule(state, silent) {
9
+ const src = state.src;
10
+ if (src.charCodeAt(state.pos) !== 0x5b || src.charCodeAt(state.pos + 1) !== 0x5b)
11
+ return false;
12
+ const match = /^\[\[([A-Z]+-[a-z0-9._-]+(?:#[^\]\s]+)?)\]\]/.exec(src.slice(state.pos));
13
+ if (!match)
14
+ return false;
15
+ if (!silent) {
16
+ const token = state.push('adoc_ref', '', 0);
17
+ token.content = match[1];
18
+ }
19
+ state.pos += match[0].length;
20
+ return true;
21
+ }
22
+ /** A list item starting with `[ ] ` or `[x] ` shows a read-only checkbox, as in GitHub's task lists. */
23
+ function taskListRule(state) {
24
+ const tokens = state.tokens;
25
+ for (let i = 2; i < tokens.length; i++) {
26
+ const inline = tokens[i];
27
+ if (inline.type !== 'inline' || tokens[i - 1].type !== 'paragraph_open' || tokens[i - 2].type !== 'list_item_open')
28
+ continue;
29
+ const first = inline.children?.[0];
30
+ const match = first?.type === 'text' ? /^\[([ xX])\] /.exec(first.content) : null;
31
+ if (!first || !match)
32
+ continue;
33
+ first.content = first.content.slice(match[0].length);
34
+ const box = new state.Token('html_inline', '', 0);
35
+ box.content = `<input type="checkbox" disabled${match[1] === ' ' ? '' : ' checked'}> `;
36
+ inline.children.unshift(box);
37
+ tokens[i - 2].attrJoin('class', 'adoc-task-item');
38
+ }
39
+ }
40
+ function createParser() {
41
+ // A fence that names a known language gets highlight.js `hljs-*` classes, which the web UI theme colours.
42
+ const highlight = (code, language) => (language && hljs.getLanguage(language) ? hljs.highlight(code, { language, ignoreIllegals: true }).value : '');
43
+ const md = markdownIt({ html: false, linkify: true, highlight });
44
+ md.inline.ruler.before('link', 'adoc_ref', referenceRule);
45
+ md.core.ruler.after('inline', 'adoc_task_list', taskListRule);
46
+ md.renderer.rules.adoc_ref = (tokens, idx) => ref(tokens[idx].content).html;
47
+ // A ```mermaid fence stays its escaped source, marked for the web UI, which draws it as a diagram.
48
+ const fence = md.renderer.rules.fence;
49
+ md.renderer.rules.fence = (tokens, idx, options, env, self) => {
50
+ const token = tokens[idx];
51
+ if (token.info.trim() !== 'mermaid')
52
+ return fence(tokens, idx, options, env, self);
53
+ token.attrJoin('class', 'adoc-mermaid');
54
+ return `<pre${self.renderAttrs(token)}>${md.utils.escapeHtml(token.content)}</pre>\n`;
55
+ };
56
+ return md;
57
+ }
58
+ const parser = createParser();
59
+ /**
60
+ * Renders Markdown to trusted HTML.
61
+ * - `[[KEY]]` and `[[KEY#anchor]]` become reference links.
62
+ * - With `file`, every block element gets its source position, so comments carry `file:line`.
63
+ * - A list item starting with `[ ] ` or `[x] ` gets a read-only checkbox.
64
+ * - A fenced block of a known language is syntax-highlighted; a ```mermaid block is drawn as a diagram by the web UI.
65
+ * - Raw HTML in the text is escaped.
66
+ */
67
+ export function markdown(text, options = {}) {
68
+ if (options.inline)
69
+ return raw(parser.renderInline(text));
70
+ const tokens = parser.parse(text, {});
71
+ if (options.file !== undefined) {
72
+ const first = options.line ?? 1;
73
+ for (const token of tokens) {
74
+ if (token.map && (token.nesting === 1 || token.type === 'fence' || token.type === 'code_block' || token.type === 'hr')) {
75
+ token.attrSet('data-adoc-source', `${options.file}:${first + token.map[0]}`);
76
+ }
77
+ }
78
+ }
79
+ return raw(parser.renderer.render(tokens, parser.options, {}));
80
+ }
81
+ /** Lists every reference key written as `[[KEY]]` in the text. */
82
+ export function findReferences(text) {
83
+ return [...text.matchAll(REFERENCE_PATTERN)].map((m) => m[1]);
84
+ }
85
+ /**
86
+ * Splits YAML front matter from Markdown.
87
+ * Returns the parsed data, the body and `bodyLine`, the 1-based line where the body starts,
88
+ * which is what `markdown(body, { file, line: bodyLine })` needs.
89
+ */
90
+ export function frontmatter(text) {
91
+ const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
92
+ if (!match)
93
+ return { data: {}, body: text, bodyLine: 1 };
94
+ const data = (parseYaml(match[1]) ?? {});
95
+ if (typeof data !== 'object' || Array.isArray(data))
96
+ throw new Error('front matter must be a YAML mapping');
97
+ const bodyLine = match[0].split('\n').length - (match[0].endsWith('\n') ? 0 : 1);
98
+ return { data, body: text.slice(match[0].length), bodyLine };
99
+ }
100
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { HtmlFragment } from './types.js';
2
+ /** Attributes that make an element a document anchor. Put them inside a start tag: html`<li ${anchor('123')}>`. */
3
+ export declare function anchor(value: string | number): HtmlFragment;
4
+ /** Attributes that record the source position of an element, such as `TASK-a.md:12`. */
5
+ export declare function source(file: string, line: number): HtmlFragment;
6
+ /**
7
+ * Attributes that make an element an action control.
8
+ * - `click`: put on a `<button>`.
9
+ * - `toggle`: put on an `<input type="checkbox">`; the handler receives `checked`.
10
+ * - `drag`: put on the dragged element; pair it with `dropTarget(name, value)` on each drop zone.
11
+ */
12
+ export declare function action(spec: {
13
+ kind: 'click' | 'toggle' | 'drag';
14
+ name: string;
15
+ value?: string | number;
16
+ confirm?: string;
17
+ }): HtmlFragment;
18
+ /** Attributes that make an element a drop zone for the drag action `name`; the handler receives `value` as `to`. */
19
+ export declare function dropTarget(name: string, value: string | number): HtmlFragment;
20
+ /** A complete reference link to another document, such as `TASK-1231` or `TASK-1231#method`. */
21
+ export declare function ref(target: string, label?: string): HtmlFragment;
22
+ /** Turns heading text into an anchor value: `Completion Criteria` becomes `completion-criteria`. */
23
+ export declare function slug(text: string): string;
package/dist/markup.js ADDED
@@ -0,0 +1,42 @@
1
+ import { escapeHtml, raw } from './html.js';
2
+ function attr(name, value) {
3
+ return `${name}="${escapeHtml(value)}"`;
4
+ }
5
+ /** Attributes that make an element a document anchor. Put them inside a start tag: html`<li ${anchor('123')}>`. */
6
+ export function anchor(value) {
7
+ return raw(attr('data-adoc-anchor', String(value)));
8
+ }
9
+ /** Attributes that record the source position of an element, such as `TASK-a.md:12`. */
10
+ export function source(file, line) {
11
+ return raw(attr('data-adoc-source', `${file}:${line}`));
12
+ }
13
+ /**
14
+ * Attributes that make an element an action control.
15
+ * - `click`: put on a `<button>`.
16
+ * - `toggle`: put on an `<input type="checkbox">`; the handler receives `checked`.
17
+ * - `drag`: put on the dragged element; pair it with `dropTarget(name, value)` on each drop zone.
18
+ */
19
+ export function action(spec) {
20
+ const parts = [attr('data-adoc-action', spec.name), attr('data-adoc-kind', spec.kind), attr('data-adoc-value', String(spec.value ?? ''))];
21
+ if (spec.confirm)
22
+ parts.push(attr('data-adoc-confirm', spec.confirm));
23
+ if (spec.kind === 'drag')
24
+ parts.push('draggable="true"');
25
+ return raw(parts.join(' '));
26
+ }
27
+ /** Attributes that make an element a drop zone for the drag action `name`; the handler receives `value` as `to`. */
28
+ export function dropTarget(name, value) {
29
+ return raw(`${attr('data-adoc-drop', name)} ${attr('data-adoc-drop-value', String(value))}`);
30
+ }
31
+ /** A complete reference link to another document, such as `TASK-1231` or `TASK-1231#method`. */
32
+ export function ref(target, label) {
33
+ return raw(`<a href="#" class="adoc-ref" ${attr('data-adoc-ref', target)}>${escapeHtml(label ?? target)}</a>`);
34
+ }
35
+ /** Turns heading text into an anchor value: `Completion Criteria` becomes `completion-criteria`. */
36
+ export function slug(text) {
37
+ return text
38
+ .toLowerCase()
39
+ .replace(/[^\p{L}\p{N}]+/gu, '-')
40
+ .replace(/^-+|-+$/g, '');
41
+ }
42
+ //# sourceMappingURL=markup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markup.js","sourceRoot":"","sources":["../src/markup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAG5C,SAAS,IAAI,CAAC,IAAY,EAAE,KAAa;IACvC,OAAO,GAAG,IAAI,KAAK,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC;AAC1C,CAAC;AAED,mHAAmH;AACnH,MAAM,UAAU,MAAM,CAAC,KAAsB;IAC3C,OAAO,GAAG,CAAC,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,MAAM,CAAC,IAAY,EAAE,IAAY;IAC/C,OAAO,GAAG,CAAC,IAAI,CAAC,kBAAkB,EAAE,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CAAC,IAAoG;IACzH,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1I,IAAI,IAAI,CAAC,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IACtE,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IACzD,OAAO,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC9B,CAAC;AAED,oHAAoH;AACpH,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,KAAsB;IAC7D,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,sBAAsB,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;AAC/F,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,GAAG,CAAC,MAAc,EAAE,KAAc;IAChD,OAAO,GAAG,CAAC,gCAAgC,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,IAAI,UAAU,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC;AACjH,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,IAAI,CAAC,IAAY;IAC/B,OAAO,IAAI;SACR,WAAW,EAAE;SACb,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC;SAChC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,95 @@
1
+ /** How the files of one document are laid out on disk. */
2
+ export type DocumentLayout =
3
+ /**
4
+ * One file named `<KEY>-<local id><extension>`, such as `TODO-gui.md`, and optionally companion files with the same name
5
+ * and the listed extensions, such as `SKETCH-login.png` beside `SKETCH-login.excalidraw`. Companion files belong to
6
+ * the document but are not handed to plugin functions, since they may be binary.
7
+ */
8
+ {
9
+ kind: 'file';
10
+ extension: string;
11
+ companions?: string[];
12
+ }
13
+ /** One folder named `<KEY>-<local id>/` whose main file is `entry`, such as `BUG-42/bug.yaml`. */
14
+ | {
15
+ kind: 'folder';
16
+ entry: string;
17
+ };
18
+ /** One document as adoc hands it to a plugin function. All paths are relative to the workspace root. */
19
+ export interface PluginDocument {
20
+ /** The document key, such as `TODO-gui`. */
21
+ readonly key: string;
22
+ /** The plugin key, such as `TODO`. */
23
+ readonly pluginKey: string;
24
+ /** The part of the key after the first hyphen, such as `gui`. */
25
+ readonly localId: string;
26
+ /** The file or folder of the document. */
27
+ readonly path: string;
28
+ /** The main file: the file itself, or the entry file of a folder document. Use it for source positions. */
29
+ readonly file: string;
30
+ /** The text of the main file. Empty when the entry file of a folder document is missing. */
31
+ readonly text: string;
32
+ /** Every file of the document by path relative to the document: `{ 'TODO-gui.md': '…' }` or `{ 'bug.yaml': '…', 'notes.md': '…' }`. */
33
+ readonly files: Readonly<Record<string, string>>;
34
+ }
35
+ /** What `summarize` returns. Shown in the document list and in reference tooltips. */
36
+ export interface DocumentSummary {
37
+ title: string;
38
+ /** Free text shown as is, such as `DONE` or `3/5 done`. */
39
+ status: string;
40
+ /** Extra rows for the tooltip table. */
41
+ fields?: Record<string, string | number | boolean>;
42
+ }
43
+ /** A user operation on a control that `action()` marked. */
44
+ export interface ActionEvent {
45
+ /** `client`: sent by an element of the plugin's client module (`client/index.js`) with the `adoc-action` DOM event. */
46
+ kind: 'click' | 'toggle' | 'drag' | 'client';
47
+ /** The action name given to `action()`. */
48
+ name: string;
49
+ /** The value given to `action()`. */
50
+ value: string;
51
+ /** Toggle only: the new checked state. */
52
+ checked?: boolean;
53
+ /** Drag only: the value of the drop target given to `dropTarget()`. */
54
+ to?: string;
55
+ /** The nearest enclosing anchor of the control, if any. */
56
+ anchor?: string;
57
+ }
58
+ /** A file content in an action result: text, or binary data as base64. */
59
+ export type FileContent = string | {
60
+ base64: string;
61
+ };
62
+ /** What an action handler returns. Every field is optional. */
63
+ export interface ActionResult {
64
+ /** New text for the main file. */
65
+ text?: string;
66
+ /** New contents for files of a folder document, by path relative to the document. */
67
+ files?: Record<string, FileContent>;
68
+ /** New contents for companion files of a file document, by extension, such as `{ '.png': { base64 } }`. */
69
+ companions?: Record<string, FileContent>;
70
+ /** Text of the message sent to the agent. */
71
+ message?: string;
72
+ }
73
+ export type ActionHandler = (doc: PluginDocument, event: ActionEvent) => ActionResult | undefined | void;
74
+ /** The object a plugin's `index.ts` default-exports through `definePlugin`. */
75
+ export interface PluginDefinition {
76
+ /** One line shown by `adoc plugin list`. */
77
+ description: string;
78
+ layout: DocumentLayout;
79
+ summarize(doc: PluginDocument): DocumentSummary;
80
+ /** Returns the body HTML, usually built with `html` and `markdown`. */
81
+ render(doc: PluginDocument): HtmlFragment | string;
82
+ /**
83
+ * Optional. Returns the body HTML that shows what changed since `previous`, an earlier version of the same document.
84
+ * Without it, adoc shows a line diff of the main file (see `sourceDiff`).
85
+ */
86
+ renderChanges?(doc: PluginDocument, previous: PluginDocument): HtmlFragment | string;
87
+ /** Handlers by action name. An action without a handler goes to adoc's default handler, which only sends a request to the agent. */
88
+ actions?: Record<string, ActionHandler>;
89
+ }
90
+ /** Trusted HTML produced by `html`, `raw` and the markup helpers. */
91
+ export declare class HtmlFragment {
92
+ readonly html: string;
93
+ constructor(html: string);
94
+ toString(): string;
95
+ }
package/dist/types.js ADDED
@@ -0,0 +1,11 @@
1
+ /** Trusted HTML produced by `html`, `raw` and the markup helpers. */
2
+ export class HtmlFragment {
3
+ html;
4
+ constructor(html) {
5
+ this.html = html;
6
+ }
7
+ toString() {
8
+ return this.html;
9
+ }
10
+ }
11
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAwFA,qEAAqE;AACrE,MAAM,OAAO,YAAY;IACF,IAAI;IAAzB,YAAqB,IAAY;oBAAZ,IAAI;IAAW,CAAC;IACrC,QAAQ;QACN,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;CACF"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@agent-workshop/adoc-plugin-kit",
3
+ "version": "0.1.0",
4
+ "license": "0BSD",
5
+ "type": "module",
6
+ "description": "Everything an adoc plugin imports: definePlugin, html, markdown and markup helpers.",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/dirty49374/adoc.git",
10
+ "directory": "packages/plugin-kit"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "skill",
15
+ "LICENSE",
16
+ "THIRD_PARTY_NOTICES.md"
17
+ ],
18
+ "engines": {
19
+ "node": ">=24"
20
+ },
21
+ "publishConfig": {
22
+ "registry": "https://registry.npmjs.org",
23
+ "access": "public"
24
+ },
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
29
+ },
30
+ "./skill/SKILL.md": "./skill/SKILL.md",
31
+ "./package.json": "./package.json"
32
+ },
33
+ "dependencies": {
34
+ "highlight.js": "^11.12.0",
35
+ "markdown-it": "15.0.2",
36
+ "yaml": "2.9.0",
37
+ "zod": "4.5.4"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc -b",
41
+ "check": "tsc -b && tsc -p test",
42
+ "test": "vitest run --testTimeout 20000"
43
+ }
44
+ }
package/skill/SKILL.md ADDED
@@ -0,0 +1,267 @@
1
+ ---
2
+ name: adoc-plugin-authoring
3
+ description: "Writes adoc plugins: a folder with index.ts (definePlugin: layout, summarize, render, actions), skill/SKILL.md and optional browser code. Use when creating or changing an adoc plugin or its skill."
4
+ ---
5
+
6
+ # Writing an adoc plugin
7
+
8
+ A plugin defines one kind of document. It is **one folder with `index.ts` and `skill/SKILL.md`**. No build step: adoc loads `index.ts` directly with Node's type stripping (only a browser `client/`, below, needs one, which you run yourself).
9
+
10
+ ```
11
+ plugins/note/
12
+ index.ts ← export default definePlugin({ … })
13
+ skill/SKILL.md ← the plugin's agent skill: tells the agent how to edit these documents
14
+ package.json ← optional: npm packages it imports, and the plugin-kit range it needs
15
+ client/ ← optional: browser code (index.js, index.css), see "Browser code"
16
+ ```
17
+
18
+ Put a plugin for one project in `.adoc/plugins/<name>/`, and declare it in the workspace's `.adoc/adoc.yaml` (restart a running `adoc server run` to load it; a restart loses held messages, see "Start: claim" in the adoc skill), then check:
19
+
20
+ ```yaml
21
+ plugins:
22
+ NOTE: ./plugins/note # key: uppercase letters only, documents are named NOTE-<local id>.<ext>;
23
+ # source: a path from this file, npm:<package> or github:<owner>/<repo>/<folder>
24
+ ```
25
+
26
+ Its scope, and so where `adoc skill install` installs its skill, follows from where the folder is (see "Project scope and user scope" in the adoc skill).
27
+
28
+ ```sh
29
+ adoc plugin list # shows the plugin, or its load error
30
+ adoc check # runs summarize and render on every document; exits 1 on errors (warnings do not fail it)
31
+ ```
32
+
33
+ **Publishing.** To share a plugin, publish its folder as an npm package or put it in a GitHub repository (see "Installing plugins" in the adoc skill). Ship it ready to run, since adoc never builds or installs what it fetches: JavaScript `index.js` for npm (Node does not strip types inside `node_modules`; build `index.ts` with esbuild or tsc), the built `client/`, and `skill/`. Declare the plugin-kit range it needs as `"peerDependencies": { "@agent-workshop/adoc-plugin-kit": "^0.1.0" }`: adoc refuses to load it with a plugin-kit outside that range.
34
+
35
+ Actions run only from the web UI: start `adoc server run`, open a sample document and click, toggle or drag (or use the elements of your client module).
36
+
37
+ A running `adoc server run` reloads a plugin from a folder outside `node_modules` whenever a file at the top of its folder changes. A reload imports `index.ts` again, but the other files it imports keep their old code: restart the server after changing them.
38
+
39
+ ## What a plugin provides
40
+
41
+ | field | type | meaning |
42
+ |---|---|---|
43
+ | `description` | `string` | one line for `adoc plugin list` |
44
+ | `layout` | `{ kind: 'file', extension: '.md', companions?: ['.png'] }` or `{ kind: 'folder', entry: 'bug.yaml' }` | a document is the file `NOTE-<local id>.md` (with optional companion files, see below), or the folder `NOTE-<local id>/` with the main file `bug.yaml` |
45
+ | `summarize(doc)` | returns `{ title, status, fields? }` | shown in the list and in reference tooltips; `status` is free text; `fields` values are string, number or boolean, so join lists: `attendees.join(', ')` |
46
+ | `render(doc)` | returns `html\`…\`` | the document body; adoc draws the header (key, title, status) around it, so do not repeat the title |
47
+ | `renderChanges(doc, previous)` | returns `html\`…\`` | optional; the body showing what changed since `previous`, an earlier version that the user saw last. Without it adoc shows a line diff of the main file |
48
+ | `actions` | `{ [name]: (doc, event) => { text?, files?, companions?, message? } }` | optional; see Actions |
49
+
50
+ The agent skill is not a field: it is the file `skill/SKILL.md` next to `index.ts`, with front matter `name` and `description`; a plugin without it fails to load.
51
+
52
+ `doc` is `{ key, pluginKey, localId, path, file, text, files }`:
53
+ - `path`: the workspace-relative path of the document itself, the file or the folder;
54
+ - `file`: the workspace-relative path of the main file (the file itself, or the folder's entry file); use it for `source` and `markdown({ file })`;
55
+ - `text`: the main file's text; `files`: the files of the document, read as UTF-8 text: for a folder document every file by path relative to the folder (keep binary files out of folder documents), for a file document the main file under its name (companion files are not in it).
56
+
57
+ If a function throws, adoc shows the message as the document's error and in `adoc check`; you do not need try/catch.
58
+
59
+ The main file is written by hand, too: the agent edits it directly, and the user can edit the whole main file in the web UI. Parse it leniently (ignore lines you do not understand, give defaults for missing fields) and throw only when the file cannot be read at all.
60
+
61
+ ## Helpers (`import { … } from '@agent-workshop/adoc-plugin-kit'`)
62
+
63
+ | helper | use |
64
+ |---|---|
65
+ | `html\`…\`` | build HTML; interpolated values are escaped, arrays are joined, `null`/`undefined`/`false` print nothing |
66
+ | `raw(s)` | insert trusted HTML unescaped, e.g. `${done ? raw('checked') : ''}` |
67
+ | `markdown(text, { file, line })` | Markdown → HTML; `[[KEY]]` becomes a reference; with `file` and `line` (1-based line where `text` starts) every block gets its source position. `{ inline: true }` for one line |
68
+ | `frontmatter(text)` | `{ data, body, bodyLine }` for YAML front matter; `bodyLine` is the 1-based file line of the first body line (the line after the closing `---`), ready for `markdown(body, { file: doc.file, line: bodyLine })`. YAML dates such as `2026-09-24` stay strings |
69
+ | `parseYaml`, `stringifyYaml` | YAML for `.yaml` documents |
70
+ | `anchor(value)` | put inside a start tag: the element can be commented on as `KEY#value` |
71
+ | `source(doc.file, line)` | put inside a start tag: comments on it carry `file:line` |
72
+ | `action({ kind, name, value, confirm? })` | put inside a start tag: `click` on a `<button>`, `toggle` on an `<input type="checkbox">`, `drag` on a draggable element. With `confirm`, the web UI first asks the user that question (each user can turn it off per action), for example when the action asks the agent to start work |
73
+ | `dropTarget(name, value)` | put inside a start tag: a drop zone for the drag action `name` |
74
+ | `ref(key, label?)` | a reference link to another document |
75
+ | `slug(text)` | `'Done when'` → `'done-when'`, handy for section anchors |
76
+ | `diffLines(oldText, newText)` | `[{ op: 'same' \| 'added' \| 'removed', text, line }]` for your own `renderChanges` |
77
+ | `sourceDiff(oldText, newText, { file })` | the line diff adoc shows by default, as HTML |
78
+ | `unifiedDiff(oldText, newText, file, context?)` | a unified diff (`--- a/…`, `+++ b/…`, `@@` hunks) as text, for example for an action's message |
79
+
80
+ Markup helpers go **inside** start tags: `html\`<li ${anchor(n)} ${source(doc.file, n)}>…</li>\``. Nesting is fine: a section with `anchor()` can contain blocks with their own `source()`; adoc uses the nearest one.
81
+
82
+ Never add `<script>` or `on…=` attributes; adoc removes them. Every interaction is an action.
83
+
84
+ ## Actions
85
+
86
+ The user clicks, toggles or drags a control marked with `action()`. adoc calls `actions[name](doc, event)` on the server.
87
+
88
+ `event` is `{ kind, name, value, checked?, to?, anchor? }`:
89
+ - `kind` is the control type you gave `action()` (`click`, `toggle` or `drag`), or `client` for an `adoc-action` event of your client module; `name` selects the handler; `value` is the value you gave `action()` (or the event's `value`), always as a string;
90
+ - `checked`: the new state, for toggle; `to`: the drop target's value, for drag;
91
+ - `anchor`: the bare value of the nearest enclosing `anchor()`, such as `goal` (not `KEY#goal`).
92
+
93
+ Return any of:
94
+ - `text`: new text of the main file (adoc writes it, refusing if the file changed since it was shown);
95
+ - `files`: new contents of files in a folder document, by path relative to the folder;
96
+ - `companions`: new contents of companion files of a file document, by extension, such as `{ '.png': { base64 } }`;
97
+ - `message`: text sent to the agent.
98
+
99
+ Any other key fails the action.
100
+
101
+ A content is text, or `{ base64 }` for binary data.
102
+
103
+ A handler sends the agent a message only when it returns `message`: with `applied: true` when adoc also wrote files, with `applied: false` when it wrote nothing (a request: the agent makes the change). A handler that writes without a `message` tells the agent nothing; it sees the change only as an uncommitted edit. When a write is due but the file changed since the user saw it, adoc writes nothing, sends nothing and shows the user why.
104
+
105
+ **Without a handler** for a name, adoc sends the agent a request `user request: <name> <value>` (for a toggle the value is the new checked state, for a drag `<value> to <to>`) and changes nothing. That is often all you need: the agent then edits the file.
106
+
107
+ ## Companion files
108
+
109
+ A file document can have companion files: files beside it with the same name and another extension, declared in the layout, such as `layout: { kind: 'file', extension: '.excalidraw', companions: ['.png'] }` for `SKETCH-login.excalidraw` with `SKETCH-login.png`. They belong to the document (its version, its last update, archiving) but are not in `doc`, since they may be binary. An action writes one by returning `companions: { '.png': content }`.
110
+
111
+ ## Browser code: the client module
112
+
113
+ When HTML is not enough, for example for a drawing board, the plugin brings browser code: the folder `client/` with `index.js` (an ES module with every library bundled in) and optionally `index.css`. adoc serves it at `/assets/plugins/<KEY>/` and loads it once per page. The module defines custom elements, and `render` returns their tags:
114
+
115
+ ```ts
116
+ render: (doc) => html`<adoc-sketch data-adoc-document="${doc.key}" data-adoc-file="${doc.file}"></adoc-sketch>`,
117
+ ```
118
+
119
+ The element talks to adoc only through DOM events and two URLs:
120
+
121
+ | what | how |
122
+ |---|---|
123
+ | read the main file | `fetch('/api/documents/<key>/file')` → `{ key, version, file, text }` |
124
+ | change files | dispatch `new CustomEvent('adoc-action', { bubbles: true, detail: { name, value, reply } })`; adoc runs the action (kind `client`) with the shown version and calls `reply({ status, version?, reason?, error? })`: `applied` (with the new `version`), `sent`, `refused` (with `reason`), `failed` (with `error`) or `busy` (another action was running: try again). Pass `value` as a string, or it arrives empty |
125
+ | tell the agent | dispatch `new CustomEvent('adoc-draft', { bubbles: true, detail: { text } })`: one draft comment on the document waits in the composer; a later one replaces it |
126
+ | follow changes | `window.addEventListener('adoc-documents-changed', (e) => e.detail.keys…)`: the changed keys, or `['*']` for all (after a plugin reload) |
127
+
128
+ Keep the `render` output the same across changes of the document (load the content in the element, not in attributes), or the page replaces the element on every change and it loses its state. A `client/` built from sources (with esbuild, for example) is a build output; see the SKETCH plugin for a complete example.
129
+
130
+ ## Styling: classes and design tokens
131
+
132
+ The web UI styles your HTML; for plain HTML you write no CSS. Its look follows the opencode TUI theme: regions differ by surface (background shade) rather than borders, emphasis is an accent bar on the left edge, and no text is larger than the body text (headings stand out by colour and weight).
133
+
134
+ **Classes:**
135
+
136
+ | Class | Use |
137
+ |---|---|
138
+ | `adoc-list` + `adoc-item` | rows; `adoc-done` on an item strikes it through |
139
+ | `adoc-section` | a section, usually with an `<h2>` |
140
+ | `adoc-toolbar` | a row of small buttons and meta text |
141
+ | `adoc-muted` | quiet, smaller meta text |
142
+ | `adoc-chip` | a small label, such as a status or a tag |
143
+ | `adoc-block` | a panel block with an accent bar |
144
+ | `adoc-tone-primary`, `-secondary`, `-accent`, `-success`, `-warning`, `-error`, `-info` | the colour of an `adoc-block` bar or an `adoc-chip` text |
145
+ | `adoc-board` + `adoc-column` + `adoc-column-title` + `adoc-card` | columns of cards; columns wrap instead of scrolling |
146
+ | `adoc-trash` | a removal drop zone |
147
+ | `adoc-added`, `adoc-removed`, `adoc-changed` | marks for `renderChanges` |
148
+
149
+ Headings, lists, links, tables and code from `markdown()` get the theme's Markdown colours; a fenced code block that names its language (```` ```ts ````) is syntax-highlighted, and a ```` ```mermaid ```` block is drawn as a diagram.
150
+
151
+ **Design tokens:** when you need a colour in an inline `style="…"`, use a token instead of a literal, so that light and dark both work: `var(--adoc-text)`, `--adoc-text-muted`, `--adoc-surface`, `--adoc-surface-panel`, `--adoc-surface-element`, `--adoc-primary`, `--adoc-secondary`, `--adoc-accent`, `--adoc-success`, `--adoc-warning`, `--adoc-error`, `--adoc-info`, `--adoc-border`, `--adoc-border-subtle`. Sizes: `--adoc-size-base`, `--adoc-size-small`, `--adoc-size-tiny`; fonts: `--adoc-font-text`, `--adoc-font-mono`.
152
+
153
+ ## Rules that avoid load errors
154
+
155
+ - Import types with `import type { PluginDocument } from '@agent-workshop/adoc-plugin-kit';` (a separate `import type` line). Node strips types; a value import of a type fails.
156
+ - No TypeScript `enum`, `namespace` or parameter properties (`constructor(private x)`): Node cannot strip them.
157
+ - `index.ts` is the entry. It may import other files of the plugin folder (a server restart picks up changes to them, see above), `@agent-workshop/adoc-plugin-kit` (adoc provides it wherever the plugin folder is), Node built-ins, and npm packages listed in the plugin's own `package.json` and installed with `npm install` in the plugin folder.
158
+ - Action names are lowercase kebab-case: `toggle`, `move`, `add-card`. Never name one `archive` or `unarchive`: the document header sends those, and the agent archives or restores the document.
159
+
160
+ ## Complete example: a checklist plugin
161
+
162
+ A checklist whose boxes the user ticks; the handler writes the tick itself. (The TODO plugin of adoc instead sends a request, so that the agent does the item.)
163
+
164
+ ```ts
165
+ import { action, anchor, definePlugin, html, markdown, raw, source } from '@agent-workshop/adoc-plugin-kit';
166
+ import type { PluginDocument } from '@agent-workshop/adoc-plugin-kit';
167
+
168
+ const ITEM = /^(\s*)- \[( |x|X)\] (.*)$/;
169
+
170
+ function parse(doc: PluginDocument) {
171
+ const lines = doc.text.split('\n');
172
+ const title = lines.find((l) => l.startsWith('# '))?.slice(2).trim();
173
+ const items = lines.flatMap((l, i) => {
174
+ const m = ITEM.exec(l);
175
+ return m ? [{ line: i + 1, done: m[2] !== ' ', text: m[3]! }] : [];
176
+ });
177
+ return { title, items, lines };
178
+ }
179
+
180
+ export default definePlugin({
181
+ description: 'A checklist whose boxes the user ticks, as a Markdown task list.',
182
+ layout: { kind: 'file', extension: '.md' },
183
+
184
+ summarize(doc) {
185
+ const { title, items } = parse(doc);
186
+ const done = items.filter((i) => i.done).length;
187
+ return { title: title ?? doc.key, status: `${done}/${items.length} done`, fields: { open: items.length - done, done } };
188
+ },
189
+
190
+ render(doc) {
191
+ const { items } = parse(doc);
192
+ return html`
193
+ <ul class="adoc-list">
194
+ ${items.map(
195
+ (item) => html`
196
+ <li class="adoc-item ${item.done ? 'adoc-done' : ''}" ${anchor(item.line)} ${source(doc.file, item.line)}>
197
+ <input type="checkbox" ${action({ kind: 'toggle', name: 'toggle', value: item.line })} ${item.done ? raw('checked') : ''} />
198
+ <span>${markdown(item.text, { inline: true })}</span>
199
+ </li>`,
200
+ )}
201
+ </ul>`;
202
+ },
203
+
204
+ actions: {
205
+ toggle(doc, event) {
206
+ const { lines } = parse(doc);
207
+ const index = Number(event.value) - 1;
208
+ const m = ITEM.exec(lines[index] ?? '');
209
+ if (!m) throw new Error(`line ${event.value} is not a task item`);
210
+ lines[index] = `${m[1]}- [${event.checked ? 'x' : ' '}] ${m[3]}`;
211
+ return { text: lines.join('\n'), message: `${doc.key}#${event.value} ${event.checked ? 'checked' : 'unchecked'}: ${m[3]}` };
212
+ },
213
+ },
214
+ });
215
+ ```
216
+
217
+ ## skill/SKILL.md
218
+
219
+ The plugin's skill is what the agent reads before it touches the plugin's documents, and what the user reads in the web UI (`SKILL.md` beside the plugin key, where they may comment on it or edit it; `adoc skill update` installs a changed one). `adoc skill install` installs the folder `skill/` for the agents the skills CLI detects, so keep only the skill in it, never code.
220
+
221
+ **Front matter.** `name` is `adoc-` and the plugin key in lowercase. The `description` is all an agent sees when it chooses a skill, so write it in the third person and say what the documents are and **when to use them**, with the words a user would use (the plugin key, the file extension, the kind of work). At most 1024 characters, no XML tags.
222
+
223
+ **Body.** Cover these, in whatever order and depth fits the plugin and the project:
224
+
225
+ 1. **Purpose:** where these documents are used and what they achieve.
226
+ 2. **States and workflow:** the states a document goes through and the recommended flow between them, including who moves it (the agent, or only the user).
227
+ 3. **Instructions for the agent:** what to do, what not to do, and what to do when something happens: "do X", "never do Y", "when Z, do W".
228
+ 4. **File:** the format, with a short example, and the recommended local id.
229
+ 5. **Anchors:** what an anchor is, so that the agent finds a commented place.
230
+ 6. **Actions:** for each action, what the plugin already did and what the agent is expected to do.
231
+
232
+ Write it short (the agent is capable; explain only what it cannot know), use one term for one thing throughout, and leave out what goes stale, such as dates or current counts.
233
+
234
+ Here is a sensible starting point; it is an example only, so change, drop or add sections to suit the plugin and the project:
235
+
236
+ ```markdown
237
+ ---
238
+ name: adoc-review
239
+ description: "REVIEW documents: one code review each, with its findings and their resolution. Use when the user asks for a review, comments on a finding, or wants to know what is still open before a release."
240
+ ---
241
+
242
+ # REVIEW documents
243
+
244
+ Document keys look like `REVIEW-<local id>`. Read the general workflow with `adoc skill view adoc`.
245
+
246
+ ## Purpose
247
+ One REVIEW records the review of one change: what was looked at, each finding, and how it was resolved, so that nothing found in a review is lost.
248
+
249
+ ## States and workflow
250
+ `OPEN` → `ANSWERED` → `CLOSED`. You open a review and answer findings; only the user closes it.
251
+
252
+ ## Instructions
253
+ - Write one finding per `##` section, with the file and line it concerns.
254
+ - Never delete a finding; mark it resolved and say how.
255
+ - When a comment disagrees with a finding, answer it under the finding and keep the review `ANSWERED`.
256
+
257
+ ## File
258
+ `REVIEW-<local id>.md`: front matter `title`, `status`, then one `##` section per finding. Recommended local id: today's date (yymmdd) and a title, such as `REVIEW-261002-login-form`.
259
+
260
+ ## Anchors
261
+ The anchor of a finding is its heading in lowercase with hyphens: `REVIEW-…#sql-in-loop`.
262
+
263
+ ## Actions
264
+ | action | what adoc already did | what you do |
265
+ |---|---|---|
266
+ | `resolve` | nothing (`applied: false`) | mark the finding resolved, as the user asks |
267
+ ```