@hybridlabor-api/aos 4.13.2 → 4.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/AGENTS.md +8 -0
- package/.agents/nodes.json +5 -2
- package/.claude/hooks/conventional-commits.mjs +14 -15
- package/.claude/hooks/env-file-protection.mjs +14 -15
- package/.claude/hooks/go-gate.mjs +152 -10
- package/.claude/hooks/go-token.mjs +55 -0
- package/.claude/hooks/memb-inject.mjs +75 -62
- package/.claude/hooks/trail-autostart.mjs +27 -0
- package/.claude/hooks/trail-relay.mjs +1 -0
- package/.claude/settings.json +22 -4
- package/.claude/workflows/startcycle-dispatch.mjs +11 -4
- package/.opencode/plugins/bdb-aos.js +98 -121
- package/.opencode/plugins/lib/trail-autostart.js +38 -0
- package/CLAUDE.md +1 -1
- package/README.de.md +6 -6
- package/README.md +6 -6
- package/README.pt.md +6 -6
- package/THIRD_PARTY_NOTICES.md +19 -3
- package/assets/header-v5.png +0 -0
- package/bin/aos-acp.mjs +211 -0
- package/bin/aos-doctor.mjs +1 -1
- package/bin/aos-uninstall.mjs +2 -2
- package/docs/master-session-acp.md +51 -0
- package/installer.js +314 -42
- package/mcps/mcsc/packages/mcp/server.js +6 -7
- package/package.json +4 -3
- package/scripts/validate-skills.mjs +76 -0
- package/skills/basic/bdbmediastorm/SKILL.md +1 -1
- package/skills/basic/godmode-shipping/SKILL.md +3 -0
- package/skills/basic/master-session/SKILL.md +89 -0
- package/skills/basic/startcycle/SKILL.md +1 -1
- package/skills/basic/startcycle-graph/SKILL.md +2 -2
- package/skills/basic/startcycle-graph-user/SKILL.md +1 -1
- package/skills/basic/teamwork-preview/SKILL.md +1 -1
- package/skills/bdbrainstorm/SKILL.md +7 -1
- package/skills/global_config/agentic-harness-patterns/SKILL.md +257 -0
- package/skills/global_config/agentic-harness-patterns/metadata.json +10 -0
- package/skills/global_config/agentic-harness-patterns/references/agent-orchestration-pattern.md +97 -0
- package/skills/global_config/agentic-harness-patterns/references/bootstrap-sequence-pattern.md +106 -0
- package/skills/global_config/agentic-harness-patterns/references/context-engineering/compress-pattern.md +78 -0
- package/skills/global_config/agentic-harness-patterns/references/context-engineering/isolate-pattern.md +82 -0
- package/skills/global_config/agentic-harness-patterns/references/context-engineering/select-pattern.md +86 -0
- package/skills/global_config/agentic-harness-patterns/references/context-engineering-pattern.md +29 -0
- package/skills/global_config/agentic-harness-patterns/references/hook-lifecycle-pattern.md +111 -0
- package/skills/global_config/agentic-harness-patterns/references/memory-persistence-pattern.md +109 -0
- package/skills/global_config/agentic-harness-patterns/references/permission-gate-pattern.md +111 -0
- package/skills/global_config/agentic-harness-patterns/references/skill-runtime-pattern.md +104 -0
- package/skills/global_config/agentic-harness-patterns/references/task-decomposition-pattern.md +92 -0
- package/skills/global_config/agentic-harness-patterns/references/tool-registry-pattern.md +101 -0
- package/skills/global_config/agenttrail/SKILL.md +8 -0
- package/skills/global_config/agenttrail/bin/agenttrail.mjs +14 -0
- package/skills/global_config/agenttrail/bin/ensure.mjs +118 -0
- package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +1 -1
- package/skills/global_config/bdb-visual-edit/SKILL.md +51 -0
- package/skills/global_config/bdb-visual-edit/references/vite-react-source-attr.md +59 -0
- package/skills/global_config/bdb-visual-edit/scripts/pick-snippet.js +27 -0
- package/skills/global_config/bdb-visual-edit/scripts/sanitize-element.mjs +123 -0
- package/skills/global_config/factory-collect/SKILL.md +74 -0
- package/skills/global_config/factory-human-digest/SKILL.md +92 -0
- package/skills/global_config/factory-lookback/SKILL.md +95 -0
- package/skills/global_config/factory-review-prs/SKILL.md +63 -0
- package/skills/global_config/git-pr-review/SKILL.md +3 -0
- package/skills/global_config/grilling/SKILL.md +2 -0
- package/skills/global_config/mcsc/SKILL.md +1 -1
- package/skills/global_config/plan-arbiter/SKILL.md +125 -0
- package/skills/global_config/plan-canvas/SKILL.md +62 -5
- package/skills/global_config/plan-canvas/scripts/lib/loopback-guard.js +19 -3
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/README.md +285 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/agent-trail.js +129 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/board-client.js +124 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/demo-plan/canvas.mdx +19 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/demo-plan/plan.mdx +18 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/recap-demo/plan.mdx +72 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/README.md +29 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/architecture.json +30 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/00_architecture.html +14950 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/canvas.mdx +511 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/plan.mdx +208 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/recap/plan.mdx +102 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/standard/plan.md +136 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/signup-storyboard/canvas.mdx +124 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/signup-storyboard/plan.mdx +37 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/index.js +188 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/kit.js +123 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/mdx.js +411 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/render.js +1291 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/plan.mdx +195 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/standard.md +95 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/plan.mdx +105 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/standard.md +76 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/canvas.mdx +81 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/plan.mdx +145 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/standard.md +76 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/plan.mdx +172 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/standard.md +100 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/plan.mdx +67 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/standard.md +49 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/canvas.mdx +63 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/plan.mdx +49 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/standard.md +39 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/plan.mdx +118 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/standard.md +57 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/plan.mdx +173 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/standard.md +96 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/plan.mdx +91 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/standard.md +54 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/canvas.mdx +53 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/plan.mdx +225 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/standard.md +111 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/theme.css +472 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/trail.js +216 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/markdown.js +1 -1
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/server.js +37 -4
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/ui.js +125 -29
- package/skills/global_config/plan-canvas/scripts/plan-canvas.js +196 -8
- package/skills/global_config/pr-recap/SKILL.md +47 -0
- package/skills/global_config/pr-recap/scripts/pr-recap.mjs +200 -0
- package/skills/global_config/quick-recap/SKILL.md +55 -0
- package/skills/global_config/stay-within-limits/SKILL.md +85 -0
- package/skills/global_config/triage/SKILL.md +3 -0
- package/skills/global_config/visual-edit/README.md +96 -0
- package/skills/global_config/visual-edit/SKILL.md +615 -0
- package/skills/global_config/visual-plan/README.md +93 -0
- package/skills/global_config/visual-plan/SKILL.md +544 -0
- package/skills/global_config/visual-plan/references/canvas.md +139 -0
- package/skills/global_config/visual-plan/references/connection.md +51 -0
- package/skills/global_config/visual-plan/references/document-quality.md +186 -0
- package/skills/global_config/visual-plan/references/exemplar.md +62 -0
- package/skills/global_config/visual-plan/references/local-files.md +99 -0
- package/skills/global_config/visual-plan/references/wireframe.md +319 -0
- package/skills/global_config/visual-recap/README.md +103 -0
- package/skills/global_config/visual-recap/SKILL.md +560 -0
- package/skills/global_config/visual-recap/references/connection.md +51 -0
- package/skills/global_config/visual-recap/references/local-files.md +99 -0
- package/skills/global_config/visual-recap/references/wireframe.md +319 -0
- package/skills/playbooks/pb-ci-fix/SKILL.md +49 -0
- package/skills/playbooks/pb-event-tracker/SKILL.md +45 -0
- package/skills/playbooks/pb-meeting-actions/SKILL.md +42 -0
- package/skills/playbooks/pb-project-new/SKILL.md +48 -0
- package/skills/playbooks/pb-week-plan/SKILL.md +45 -0
- package/assets/header-v4.jpg +0 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Derive an agenttrail plan file (skills/global_config/agenttrail "Plan
|
|
5
|
+
* convention") from a plan folder: `## Name {#id}` components with optional
|
|
6
|
+
* `needs:` / `files:` / `url:` lines and `- [ ] task {#id}` items.
|
|
7
|
+
*
|
|
8
|
+
* Sources, in document order: plan.mdx, canvas.mdx, prototype.mdx.
|
|
9
|
+
* Components: headings tagged `{#id}` and `<Section id title>`.
|
|
10
|
+
* needs: `<Section needs=[...]>`, or frontmatter `needs-<id>: a, b` for headings.
|
|
11
|
+
* files: `<ImplementationMap files=...>`. Tasks: `<Checklist>` items.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const { parseMdx } = require('./mdx');
|
|
18
|
+
const { checklistItems } = require('./render');
|
|
19
|
+
|
|
20
|
+
const ID_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
21
|
+
const HEADING_ID_RE = /\s*\{#([A-Za-z0-9-]+)\}\s*$/;
|
|
22
|
+
const DEFAULT_OUT = path.join('production_artifacts', '00_execution_plan.md');
|
|
23
|
+
const SOURCES = ['plan.mdx', 'canvas.mdx', 'prototype.mdx'];
|
|
24
|
+
|
|
25
|
+
class TrailError extends Error {}
|
|
26
|
+
|
|
27
|
+
const oneLine = (value) => String(value == null ? '' : value).replace(/\s+/g, ' ').replace(/\{#[^}]*\}/g, '').trim();
|
|
28
|
+
|
|
29
|
+
function toIdList(value) {
|
|
30
|
+
const list = Array.isArray(value) ? value : typeof value === 'string' ? value.split(',') : [];
|
|
31
|
+
return list.map((v) => String(v).trim().toLowerCase()).filter(Boolean);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function slug(value) {
|
|
35
|
+
return String(value).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function entryPath(entry) {
|
|
39
|
+
if (typeof entry === 'string') return entry.trim();
|
|
40
|
+
if (entry && typeof entry === 'object') return String(entry.path ?? entry.file ?? entry.name ?? '').trim();
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function implementationFiles(block) {
|
|
45
|
+
const raw = block.props.files;
|
|
46
|
+
let entries = [];
|
|
47
|
+
if (Array.isArray(raw)) entries = raw;
|
|
48
|
+
else if (raw && typeof raw === 'object') entries = Object.keys(raw);
|
|
49
|
+
if (!entries.length) {
|
|
50
|
+
entries = (block.children || []).filter((c) => c.type === 'prose')
|
|
51
|
+
.flatMap((c) => c.text.split('\n')).map((l) => l.replace(/^\s*[-*]\s+/, '').trim());
|
|
52
|
+
}
|
|
53
|
+
return entries.map(entryPath).filter(Boolean);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function collect(dir) {
|
|
57
|
+
const blocks = [];
|
|
58
|
+
let frontmatter = null;
|
|
59
|
+
for (const file of SOURCES) {
|
|
60
|
+
const full = path.join(dir, file);
|
|
61
|
+
if (!fs.existsSync(full)) continue;
|
|
62
|
+
const parsed = parseMdx(fs.readFileSync(full, 'utf8'));
|
|
63
|
+
if (!frontmatter) frontmatter = parsed.frontmatter || {};
|
|
64
|
+
blocks.push(...parsed);
|
|
65
|
+
}
|
|
66
|
+
return { blocks, frontmatter: frontmatter || {} };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function derive(dir, { workspaceRoot = process.cwd() } = {}) {
|
|
70
|
+
const { blocks, frontmatter } = collect(dir);
|
|
71
|
+
const components = [];
|
|
72
|
+
const used = new Set();
|
|
73
|
+
const warnings = [];
|
|
74
|
+
|
|
75
|
+
const uniqueId = (wanted, fallback) => {
|
|
76
|
+
let base = ID_RE.test(String(wanted).toLowerCase()) ? String(wanted).toLowerCase() : slug(fallback) || 'item';
|
|
77
|
+
if (!ID_RE.test(base)) base = `n-${base}`;
|
|
78
|
+
let id = base;
|
|
79
|
+
for (let n = 2; used.has(id); n += 1) id = `${base}-${n}`;
|
|
80
|
+
used.add(id);
|
|
81
|
+
return id;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const open = (title, wantedId, needs) => {
|
|
85
|
+
const component = { id: uniqueId(wantedId, title), title: oneLine(title) || wantedId, needs: toIdList(needs), files: [], url: '', tasks: [] };
|
|
86
|
+
components.push(component);
|
|
87
|
+
return component;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const walk = (list, current) => {
|
|
91
|
+
let cur = current;
|
|
92
|
+
for (const block of list) {
|
|
93
|
+
if (block.type === 'heading') {
|
|
94
|
+
const m = HEADING_ID_RE.exec(block.text);
|
|
95
|
+
if (m && m[1].toLowerCase() !== 'board') {
|
|
96
|
+
cur = open(block.text.replace(HEADING_ID_RE, ''), m[1], frontmatter[`needs-${m[1].toLowerCase()}`]);
|
|
97
|
+
} else if (block.level <= 2) {
|
|
98
|
+
cur = current;
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (block.type !== 'tag') continue;
|
|
103
|
+
if (block.name === 'Section' && block.props.id) {
|
|
104
|
+
walk(block.children || [], open(block.props.title || block.props.id, block.props.id, block.props.needs));
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if ((block.name === 'ImplementationMap' || block.name === 'Checklist') && !cur) {
|
|
108
|
+
warnings.push(`${block.name} is not under a component and was ignored; add {#id} to the heading above it`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (block.name === 'ImplementationMap' && cur) {
|
|
112
|
+
for (const file of implementationFiles(block)) {
|
|
113
|
+
if (/[,\[\]\n]/.test(file)) warnings.push(`skipped file with unsupported characters: ${file}`);
|
|
114
|
+
else if (!cur.files.includes(file)) cur.files.push(file);
|
|
115
|
+
}
|
|
116
|
+
} else if (block.name === 'Checklist' && cur) {
|
|
117
|
+
for (const item of checklistItems(block)) {
|
|
118
|
+
const m = HEADING_ID_RE.exec(item.label);
|
|
119
|
+
const label = oneLine(item.label);
|
|
120
|
+
if (!label) continue;
|
|
121
|
+
cur.tasks.push({ id: uniqueId(m ? m[1] : '', `${cur.id}-${label.slice(0, 24)}`), label, checked: item.checked });
|
|
122
|
+
}
|
|
123
|
+
} else if (block.name === 'Archify' && cur && !cur.url) {
|
|
124
|
+
const rel = path.relative(workspaceRoot, path.resolve(dir, String(block.props.src || ''))).split(path.sep).join('/');
|
|
125
|
+
if (/^production_artifacts\/\S+$/.test(rel)) cur.url = rel;
|
|
126
|
+
} else if (block.children && block.children.length) {
|
|
127
|
+
walk(block.children, cur);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
walk(blocks, null);
|
|
132
|
+
|
|
133
|
+
const ids = new Set(components.map((c) => c.id));
|
|
134
|
+
for (const c of components) {
|
|
135
|
+
const known = c.needs.filter((n) => ids.has(n) && n !== c.id);
|
|
136
|
+
if (known.length !== c.needs.length) warnings.push(`${c.id}: dropped needs that are not components`);
|
|
137
|
+
c.needs = known;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const title = oneLine(frontmatter.title) || oneLine((blocks.find((b) => b.type === 'heading' && b.level === 1) || {}).text) || 'Plan';
|
|
141
|
+
const lines = [`# ${title}`, ''];
|
|
142
|
+
for (const c of components) {
|
|
143
|
+
lines.push(`## ${c.title} {#${c.id}}`);
|
|
144
|
+
if (c.needs.length) lines.push(`needs: [${c.needs.join(', ')}]`);
|
|
145
|
+
if (c.files.length) lines.push(`files: [${c.files.join(', ')}]`);
|
|
146
|
+
if (c.url) lines.push(`url: ${c.url}`);
|
|
147
|
+
for (const t of c.tasks) lines.push(`- [${t.checked ? 'x' : ' '}] ${t.label} {#${t.id}}`);
|
|
148
|
+
lines.push('');
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
text: lines.join('\n'),
|
|
152
|
+
components: components.length,
|
|
153
|
+
tasks: components.reduce((n, c) => n + c.tasks.length, 0),
|
|
154
|
+
warnings,
|
|
155
|
+
graph: components
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function nearestRealpath(target) {
|
|
160
|
+
let cur = target;
|
|
161
|
+
const tail = [];
|
|
162
|
+
for (;;) {
|
|
163
|
+
try {
|
|
164
|
+
return path.join(fs.realpathSync(cur), ...tail);
|
|
165
|
+
} catch {
|
|
166
|
+
const parent = path.dirname(cur);
|
|
167
|
+
if (parent === cur) return target;
|
|
168
|
+
tail.unshift(path.basename(cur));
|
|
169
|
+
cur = parent;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function insideRoot(root, target) {
|
|
175
|
+
const rel = path.relative(root, target);
|
|
176
|
+
return !!rel && !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @returns {{out: string, components: number, tasks: number, next_step: string, warnings?: string[]}}
|
|
181
|
+
* @throws {TrailError} with a message fit for the user; callers map it to exit 2.
|
|
182
|
+
*/
|
|
183
|
+
function writeTrail(input, { out, force = false, workspaceRoot = process.cwd() } = {}) {
|
|
184
|
+
if (!input) throw new TrailError('trail requires a plan folder or plan.mdx path');
|
|
185
|
+
const target = path.resolve(input);
|
|
186
|
+
let dir = target;
|
|
187
|
+
if (fs.existsSync(target) && fs.statSync(target).isFile()) {
|
|
188
|
+
if (path.basename(target) !== 'plan.mdx') throw new TrailError(`expected plan.mdx (got ${path.basename(target)})`);
|
|
189
|
+
dir = path.dirname(target);
|
|
190
|
+
} else if (!fs.existsSync(path.join(target, 'plan.mdx'))) {
|
|
191
|
+
throw new TrailError(`no plan.mdx in ${input}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const root = nearestRealpath(path.resolve(workspaceRoot));
|
|
195
|
+
const outPath = path.resolve(workspaceRoot, out || DEFAULT_OUT);
|
|
196
|
+
const real = nearestRealpath(outPath);
|
|
197
|
+
if (!insideRoot(root, real)) throw new TrailError(`output must be inside the workspace root (${workspaceRoot}): ${out}`);
|
|
198
|
+
if (fs.existsSync(outPath) && !force) throw new TrailError(`${path.relative(workspaceRoot, outPath)} already exists; pass --force to overwrite`);
|
|
199
|
+
if (fs.existsSync(outPath) && fs.statSync(outPath).isDirectory()) throw new TrailError(`output is a directory: ${out}`);
|
|
200
|
+
|
|
201
|
+
const result = derive(dir, { workspaceRoot });
|
|
202
|
+
if (!result.components) throw new TrailError('no components found: tag headings with {#id} or use <Section id="..."> in the plan');
|
|
203
|
+
|
|
204
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
205
|
+
fs.writeFileSync(outPath, result.text, { flag: force ? 'w' : 'wx' });
|
|
206
|
+
const rel = path.relative(workspaceRoot, outPath).split(path.sep).join('/');
|
|
207
|
+
return {
|
|
208
|
+
out: rel,
|
|
209
|
+
components: result.components,
|
|
210
|
+
tasks: result.tasks,
|
|
211
|
+
next_step: `aos-trail . --plan ${rel} --no-open`,
|
|
212
|
+
...(result.warnings.length ? { warnings: result.warnings } : {})
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
module.exports = { derive, writeTrail, TrailError };
|
|
@@ -16,7 +16,7 @@ const fs = require('fs');
|
|
|
16
16
|
const http = require('http');
|
|
17
17
|
const path = require('path');
|
|
18
18
|
|
|
19
|
-
const { buildAllowedHostnames, isAllowedHostHeader, isAllowedOrigin } = require('../loopback-guard');
|
|
19
|
+
const { buildAllowedHostnames, isAllowedFetchSite, isAllowedHostHeader, isAllowedOrigin } = require('../loopback-guard');
|
|
20
20
|
const { renderMarkdown } = require('./markdown');
|
|
21
21
|
const { artifactSdkJs } = require('./sdk');
|
|
22
22
|
const {
|
|
@@ -24,7 +24,8 @@ const {
|
|
|
24
24
|
canvasClientJs,
|
|
25
25
|
renderCanvasHtml,
|
|
26
26
|
renderMarkdownArtifactHtml,
|
|
27
|
-
|
|
27
|
+
renderHomeHtml,
|
|
28
|
+
VISUAL_SKILLS
|
|
28
29
|
} = require('./ui');
|
|
29
30
|
|
|
30
31
|
const DEFAULT_PORT = 4519;
|
|
@@ -130,6 +131,29 @@ function sendHtml(res, statusCode, html, { csp = true } = {}) {
|
|
|
130
131
|
res.end(html);
|
|
131
132
|
}
|
|
132
133
|
|
|
134
|
+
const STATIC_READ_PATH = /^\/(sdk\.js|artifact\/[a-f0-9]{12}\/.*)$/;
|
|
135
|
+
|
|
136
|
+
function skillDescription(file) {
|
|
137
|
+
try {
|
|
138
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(fs.readFileSync(file, 'utf8'));
|
|
139
|
+
const m = fm && /^description:\s*(.*)$/m.exec(fm[1]);
|
|
140
|
+
return m ? m[1].trim().replace(/^(["'])(.*)\1$/, '$2') : '';
|
|
141
|
+
} catch { return ''; }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function homeData(store) {
|
|
145
|
+
// Lazy: plan-canvas.js requires this module at load time.
|
|
146
|
+
const { cmdTemplates, findSkillMd } = require('../../plan-canvas');
|
|
147
|
+
return {
|
|
148
|
+
sessions: store.list(),
|
|
149
|
+
templates: cmdTemplates(),
|
|
150
|
+
skills: VISUAL_SKILLS.map(name => {
|
|
151
|
+
const file = findSkillMd(name);
|
|
152
|
+
return { name, installed: Boolean(file), description: file ? skillDescription(file) : '' };
|
|
153
|
+
})
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
133
157
|
function createPlanCanvasServer({
|
|
134
158
|
store,
|
|
135
159
|
host = DEFAULT_HOST,
|
|
@@ -157,6 +181,7 @@ function createPlanCanvasServer({
|
|
|
157
181
|
let idleTimer = null;
|
|
158
182
|
let presenceSweep = null;
|
|
159
183
|
let closed = false;
|
|
184
|
+
let boundPort = null; // set once listening; pins the allowed Origin port
|
|
160
185
|
|
|
161
186
|
// --- presence + SSE ---------------------------------------------------
|
|
162
187
|
|
|
@@ -559,11 +584,18 @@ function createPlanCanvasServer({
|
|
|
559
584
|
if (!isAllowedHostHeader(req.headers.host, allowedHostnames)) {
|
|
560
585
|
return sendJson(res, 403, { error: 'forbidden host header' });
|
|
561
586
|
}
|
|
562
|
-
if (!isAllowedOrigin(req.headers.origin, allowedHostnames)) {
|
|
587
|
+
if (!isAllowedOrigin(req.headers.origin, allowedHostnames, boundPort)) {
|
|
563
588
|
return sendJson(res, 403, { error: 'forbidden origin' });
|
|
564
589
|
}
|
|
565
590
|
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
566
591
|
const { pathname } = url;
|
|
592
|
+
// The artifact iframe is sandboxed without allow-same-origin, so its script
|
|
593
|
+
// and asset loads arrive as Sec-Fetch-Site: cross-site. Only those read-only
|
|
594
|
+
// static paths may skip the fetch-site check; everything else keeps it.
|
|
595
|
+
const isStaticRead = req.method === 'GET' && STATIC_READ_PATH.test(pathname);
|
|
596
|
+
if (!isStaticRead && !isAllowedFetchSite(req.headers['sec-fetch-site'])) {
|
|
597
|
+
return sendJson(res, 403, { error: 'forbidden fetch site' });
|
|
598
|
+
}
|
|
567
599
|
|
|
568
600
|
Promise.resolve()
|
|
569
601
|
.then(() => {
|
|
@@ -578,7 +610,7 @@ function createPlanCanvasServer({
|
|
|
578
610
|
return undefined;
|
|
579
611
|
}
|
|
580
612
|
if (req.method === 'GET' && pathname === '/') {
|
|
581
|
-
return sendHtml(res, 200,
|
|
613
|
+
return sendHtml(res, 200, renderHomeHtml(homeData(store)));
|
|
582
614
|
}
|
|
583
615
|
if (req.method === 'GET' && pathname === '/canvas.css') {
|
|
584
616
|
res.writeHead(200, { 'content-type': 'text/css; charset=utf-8', 'cache-control': 'no-store' });
|
|
@@ -641,6 +673,7 @@ function createPlanCanvasServer({
|
|
|
641
673
|
return new Promise((resolve, reject) => {
|
|
642
674
|
server.once('error', reject);
|
|
643
675
|
server.listen(port, host, () => {
|
|
676
|
+
boundPort = server.address().port;
|
|
644
677
|
armIdleTimer();
|
|
645
678
|
resolve({ port: server.address().port, host });
|
|
646
679
|
});
|
|
@@ -127,6 +127,10 @@ function canvasCss() {
|
|
|
127
127
|
.panel{width:340px;flex:none;display:flex;flex-direction:column;border-left:1px solid var(--border);background:var(--bg2)}
|
|
128
128
|
.panel h2{font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--text3);padding:12px 14px 8px}
|
|
129
129
|
|
|
130
|
+
body.chat-hidden .panel{display:none}
|
|
131
|
+
.icon-btn .count{display:none;min-width:16px;height:16px;padding:0 4px;border-radius:99px;background:var(--accent);color:#fff;font-size:10px;font-weight:600;line-height:16px;text-align:center}
|
|
132
|
+
.icon-btn .count.show{display:inline-block}
|
|
133
|
+
|
|
130
134
|
.verdict{display:flex;gap:8px;padding:0 14px 12px;border-bottom:1px solid var(--border)}
|
|
131
135
|
.verdict button{flex:1;height:30px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;transition:all .12s}
|
|
132
136
|
.verdict .approve{border:1px solid var(--green);background:var(--green-glow);color:var(--green)}
|
|
@@ -236,6 +240,32 @@ function canvasClientJs() {
|
|
|
236
240
|
applyTheme(theme);
|
|
237
241
|
});
|
|
238
242
|
|
|
243
|
+
// --- conversation panel ---------------------------------------------
|
|
244
|
+
const chatKey = 'ecc-plan-canvas:chat-hidden';
|
|
245
|
+
let chatHidden = false;
|
|
246
|
+
try { chatHidden = localStorage.getItem(chatKey) === '1'; } catch { /* site data blocked */ }
|
|
247
|
+
function applyChatHidden() {
|
|
248
|
+
document.body.classList.toggle('chat-hidden', chatHidden);
|
|
249
|
+
$('chatBtn').setAttribute('aria-pressed', String(chatHidden));
|
|
250
|
+
$('chatBtnLabel').textContent = chatHidden ? 'Show chat' : 'Hide chat';
|
|
251
|
+
}
|
|
252
|
+
function updateChatCount() {
|
|
253
|
+
const el = $('chatCount');
|
|
254
|
+
el.textContent = String(queue.length);
|
|
255
|
+
el.classList.toggle('show', chatHidden && queue.length > 0);
|
|
256
|
+
}
|
|
257
|
+
function toggleChat() {
|
|
258
|
+
chatHidden = !chatHidden;
|
|
259
|
+
try { localStorage.setItem(chatKey, chatHidden ? '1' : '0'); } catch { /* site data blocked */ }
|
|
260
|
+
applyChatHidden();
|
|
261
|
+
updateChatCount();
|
|
262
|
+
}
|
|
263
|
+
applyChatHidden();
|
|
264
|
+
$('chatBtn').addEventListener('click', toggleChat);
|
|
265
|
+
document.addEventListener('keydown', e => {
|
|
266
|
+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'j') { e.preventDefault(); toggleChat(); }
|
|
267
|
+
}, true);
|
|
268
|
+
|
|
239
269
|
// --- annotate mode -------------------------------------------------
|
|
240
270
|
let annotate = true;
|
|
241
271
|
function setAnnotate(on) {
|
|
@@ -273,6 +303,7 @@ function canvasClientJs() {
|
|
|
273
303
|
function addToQueue(item) { queue.push(item); persistQueue(); renderQueue(); }
|
|
274
304
|
function renderQueue() {
|
|
275
305
|
queueEl.innerHTML = '';
|
|
306
|
+
updateChatCount();
|
|
276
307
|
queue.forEach((item, i) => {
|
|
277
308
|
const pill = document.createElement('div');
|
|
278
309
|
pill.className = 'pill kind-' + item.kind;
|
|
@@ -498,13 +529,14 @@ function renderCanvasHtml(session, { clientPath = '/client.js', cssPath = '/canv
|
|
|
498
529
|
<div id="annotate" class="toggle" role="switch" aria-pressed="true" title="Toggle annotate mode (Cmd/Ctrl+I)">
|
|
499
530
|
<span>Annotate</span><span class="track"><span class="knob"></span></span>
|
|
500
531
|
</div>
|
|
532
|
+
<button id="chatBtn" class="icon-btn" type="button" aria-pressed="false" title="Show or hide the conversation panel (Cmd/Ctrl+J)"><span id="chatBtnLabel">Hide chat</span><span id="chatCount" class="count"></span></button>
|
|
501
533
|
<button id="themeBtn" class="icon-btn" type="button">light</button>
|
|
502
534
|
<button id="reloadBtn" class="icon-btn" type="button" title="Reload artifact">Reload</button>
|
|
503
535
|
<button id="endBtn" class="icon-btn danger" type="button">End session</button>
|
|
504
536
|
</header>
|
|
505
537
|
<div class="layout">
|
|
506
538
|
<main class="frame">
|
|
507
|
-
<iframe id="artifact" title="Artifact under review" src="${artifactSrc}" data-artifact-src="${artifactSrc}" sandbox="allow-scripts allow-forms allow-popups"></iframe>
|
|
539
|
+
<iframe id="artifact" title="Artifact under review" src="${artifactSrc}" data-artifact-src="${artifactSrc}" sandbox="allow-scripts allow-forms allow-popups" allow="fullscreen"></iframe>
|
|
508
540
|
<div id="endedOverlay" class="overlay"><div class="card"><h3>Session ended</h3><p id="endedWho"></p></div></div>
|
|
509
541
|
</main>
|
|
510
542
|
<aside class="panel">
|
|
@@ -592,39 +624,100 @@ ${hasMermaid ? mermaidLoaderScript(mermaidUrl()) : ''}
|
|
|
592
624
|
</html>`;
|
|
593
625
|
}
|
|
594
626
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
627
|
+
const VISUAL_SKILLS = ['visual-plan', 'visual-recap', 'visual-review', 'visual-edit', 'prototype', 'archify', 'agenttrail'];
|
|
628
|
+
|
|
629
|
+
function artifactKind(file) {
|
|
630
|
+
const base = path.basename(String(file));
|
|
631
|
+
if (base === 'plan.builder.html') return 'BDB Plan Builder';
|
|
632
|
+
if (/\.html?$/i.test(base)) return 'HTML artifact';
|
|
633
|
+
return 'Standard markdown';
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const HOME_CSS = `
|
|
637
|
+
:root{--bg:#0a0a0a;--card:#161616;--line:#262626;--ink:#fff;--ink2:#b8b8b8;--ink3:#7a7a7a;--accent:#9b30c4;--accent2:#c05ee6;
|
|
638
|
+
--font:-apple-system,BlinkMacSystemFont,'SF Pro Display','Inter','Segoe UI',Roboto,sans-serif;--mono:'SF Mono','Fira Code','JetBrains Mono',Menlo,monospace}
|
|
639
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
640
|
+
body{font-family:var(--font);background:var(--bg);color:var(--ink);line-height:1.5;padding:32px 20px 64px}
|
|
641
|
+
main{max-width:1000px;margin:0 auto}
|
|
642
|
+
header{display:flex;align-items:center;gap:10px;margin-bottom:8px}
|
|
643
|
+
.mark{width:12px;height:12px;border-radius:3px;background:var(--accent)}
|
|
644
|
+
h1{font-size:20px;letter-spacing:-.01em}
|
|
645
|
+
h2{font-size:11px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--ink3);margin:32px 0 12px}
|
|
646
|
+
.sub{font-size:13px;color:var(--ink2)}
|
|
647
|
+
a{color:var(--accent2);text-decoration:none}
|
|
648
|
+
a:hover{text-decoration:underline}
|
|
649
|
+
.grid{display:grid;gap:10px;grid-template-columns:repeat(auto-fill,minmax(290px,1fr))}
|
|
650
|
+
.card{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:12px 14px;display:flex;flex-direction:column;gap:6px;min-width:0}
|
|
651
|
+
.card .t{font-size:14px;font-weight:600;overflow-wrap:anywhere}
|
|
652
|
+
.card .d{font-size:12.5px;color:var(--ink2)}
|
|
653
|
+
.card .u{font-size:12px;color:var(--ink3)}
|
|
654
|
+
.row{display:flex;flex-wrap:wrap;align-items:center;gap:6px}
|
|
655
|
+
.chip{font-size:10.5px;font-weight:600;padding:2px 8px;border-radius:99px;border:1px solid var(--line);color:var(--ink2);white-space:nowrap}
|
|
656
|
+
.chip.accent{border-color:var(--accent);color:var(--accent2)}
|
|
657
|
+
.chip.ok{border-color:var(--accent);color:#fff;background:rgba(155,48,196,.25)}
|
|
658
|
+
.chip.missing{color:var(--ink3)}
|
|
659
|
+
.mono,code{font-family:var(--mono);font-size:11.5px;color:var(--ink2);overflow-wrap:anywhere}
|
|
660
|
+
code.cmd{display:block;background:#0f0f0f;border:1px solid var(--line);border-radius:6px;padding:6px 8px;user-select:all}
|
|
661
|
+
.empty{color:var(--ink3);font-size:13px}
|
|
662
|
+
.card.ended{opacity:.6}
|
|
663
|
+
`;
|
|
664
|
+
|
|
665
|
+
function chip(text, cls = '', title = '') {
|
|
666
|
+
return `<span class="chip ${cls}"${title ? ` title="${escapeHtml(title)}"` : ''}>${escapeHtml(text)}</span>`;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function sessionCard(s) {
|
|
670
|
+
const ended = s.status === 'ended';
|
|
671
|
+
const base = path.basename(s.file);
|
|
672
|
+
const name = escapeHtml(/^(plan\.builder\.html|plan\.md|index\.html)$/.test(base) ? `${path.basename(path.dirname(s.file))}/${base}` : base);
|
|
673
|
+
const title = ended ? name : `<a href="/canvas/${escapeHtml(s.key)}">${name}</a>`;
|
|
674
|
+
const status = ended ? `ended by ${s.endedBy || 'agent'}` : s.status;
|
|
675
|
+
const pending = Number(s.pending) > 0 ? chip(`${s.pending} pending feedback`, 'accent') : '';
|
|
676
|
+
return `<div class="card${ended ? ' ended' : ''}"><div class="t">${title}</div>
|
|
677
|
+
<div class="row">${chip(artifactKind(s.file), 'accent')}${chip(status, ended ? '' : 'ok')}${pending}</div>
|
|
678
|
+
<div class="mono">${escapeHtml(s.file)}</div></div>`;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function templateCard(t) {
|
|
682
|
+
const board = t.hasBoard ? chip('board', 'accent', 'includes a design canvas with screens and arrows') : '';
|
|
683
|
+
return `<div class="card"><div class="t">${escapeHtml(t.label || t.id)} <span class="mono">${escapeHtml(t.id)}</span></div>
|
|
684
|
+
<div class="row">${board}</div>
|
|
685
|
+
<div class="d">${escapeHtml(t.description || '')}</div>
|
|
686
|
+
<div class="u">Use when: ${escapeHtml(t.useWhen || '')}</div>
|
|
687
|
+
<code class="cmd">aos-plan-canvas new ${escapeHtml(t.id)} <dir></code></div>`;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function skillCard(k) {
|
|
691
|
+
const desc = String(k.description || '');
|
|
692
|
+
const short = desc.length > 140 ? `${desc.slice(0, 139).trimEnd()}…` : desc;
|
|
693
|
+
return `<div class="card"><div class="t">${escapeHtml(k.name)}</div>
|
|
694
|
+
<div class="row">${k.installed ? chip('installed', 'ok') : chip('missing', 'missing')}</div>
|
|
695
|
+
<div class="d">${escapeHtml(short)}</div></div>`;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Landing page (GET /): open reviews, plan templates, visual skills. Read-only, no scripts.
|
|
699
|
+
function renderHomeHtml({ sessions = [], templates = [], skills = [] } = {}) {
|
|
700
|
+
const sorted = [...sessions].sort((a, b) =>
|
|
701
|
+
(a.status === 'ended') - (b.status === 'ended') || String(b.updatedAt || '').localeCompare(String(a.updatedAt || '')));
|
|
604
702
|
return `<!DOCTYPE html>
|
|
605
703
|
<html lang="en">
|
|
606
704
|
<head>
|
|
607
705
|
<meta charset="utf-8">
|
|
608
|
-
<
|
|
609
|
-
<
|
|
610
|
-
|
|
611
|
-
*{margin:0;padding:0;box-sizing:border-box}
|
|
612
|
-
body{font-family:var(--font);background:var(--bg);color:var(--text);padding:40px;line-height:1.5}
|
|
613
|
-
.logo{width:30px;height:30px;background:linear-gradient(135deg,var(--accent),var(--pink));border-radius:7px;display:inline-flex;align-items:center;justify-content:center;font-weight:700;color:#fff;margin-right:10px;vertical-align:middle}
|
|
614
|
-
h1{font-size:18px;display:inline-block;vertical-align:middle}
|
|
615
|
-
table{margin-top:24px;border-collapse:collapse;width:100%;max-width:900px;font-size:13px}
|
|
616
|
-
th,td{text-align:left;padding:8px 12px;border-bottom:1px solid var(--border)}
|
|
617
|
-
th{color:var(--text3);font-size:11px;text-transform:uppercase;letter-spacing:.05em}
|
|
618
|
-
a{color:var(--accent);text-decoration:none}
|
|
619
|
-
.mono{font-family:var(--mono);font-size:11.5px;color:var(--text2)}
|
|
620
|
-
.badge{font-size:11px;padding:2px 8px;border-radius:99px;background:var(--bg3);border:1px solid var(--border);color:var(--text2)}
|
|
621
|
-
.badge.open,.badge.feedback{color:var(--green);border-color:var(--green);background:var(--green-glow)}
|
|
622
|
-
.empty{margin-top:24px;color:var(--text3);font-size:13px}
|
|
623
|
-
</style>
|
|
706
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
707
|
+
<title>Plan Canvas</title>
|
|
708
|
+
<style>${HOME_CSS}</style>
|
|
624
709
|
</head>
|
|
625
710
|
<body>
|
|
626
|
-
<
|
|
627
|
-
|
|
711
|
+
<main>
|
|
712
|
+
<header><span class="mark"></span><h1>Plan Canvas</h1></header>
|
|
713
|
+
<p class="sub">Review plans and HTML artifacts with your agent. Start new ones from the terminal; this page only lists what exists.</p>
|
|
714
|
+
<h2>Open reviews</h2>
|
|
715
|
+
${sorted.length ? `<div class="grid">${sorted.map(sessionCard).join('\n')}</div>` : '<p class="empty">No sessions yet. Ask your agent to open a plan with the plan-canvas skill.</p>'}
|
|
716
|
+
<h2>Plan templates</h2>
|
|
717
|
+
${templates.length ? `<div class="grid">${templates.map(templateCard).join('\n')}</div>` : '<p class="empty">No templates found.</p>'}
|
|
718
|
+
<h2>Visual skills</h2>
|
|
719
|
+
<div class="grid">${skills.map(skillCard).join('\n')}</div>
|
|
720
|
+
</main>
|
|
628
721
|
</body>
|
|
629
722
|
</html>`;
|
|
630
723
|
}
|
|
@@ -632,7 +725,10 @@ ${sessions.length ? `<table><thead><tr><th>Artifact</th><th>Path</th><th>Status<
|
|
|
632
725
|
module.exports = {
|
|
633
726
|
canvasCss,
|
|
634
727
|
canvasClientJs,
|
|
728
|
+
mermaidLoaderScript,
|
|
729
|
+
mermaidUrl,
|
|
635
730
|
renderCanvasHtml,
|
|
636
731
|
renderMarkdownArtifactHtml,
|
|
637
|
-
|
|
732
|
+
renderHomeHtml,
|
|
733
|
+
VISUAL_SKILLS
|
|
638
734
|
};
|