agent-canvas 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.
Files changed (65) hide show
  1. package/bin/agent-canvas.js +14 -0
  2. package/bin/agent-canvas.ts +377 -0
  3. package/daemon/build.ts +185 -0
  4. package/daemon/client/AnnotationProvider.tsx +185 -0
  5. package/daemon/client/AnnotationSidebar.tsx +314 -0
  6. package/daemon/client/App.tsx +611 -0
  7. package/daemon/client/CompareView.tsx +333 -0
  8. package/daemon/client/FileBrowser.tsx +188 -0
  9. package/daemon/client/FileIcon.tsx +76 -0
  10. package/daemon/client/FileViewer.tsx +207 -0
  11. package/daemon/client/PlanRenderer.tsx +665 -0
  12. package/daemon/client/Popover.tsx +285 -0
  13. package/daemon/client/ResponsePreview.tsx +260 -0
  14. package/daemon/client/RevisionSelect.tsx +97 -0
  15. package/daemon/client/SessionContext.tsx +3 -0
  16. package/daemon/client/SessionSwitcher.tsx +72 -0
  17. package/daemon/client/annotationContext.ts +114 -0
  18. package/daemon/client/components/Callout.tsx +25 -0
  19. package/daemon/client/components/Checklist.tsx +37 -0
  20. package/daemon/client/components/Choice.tsx +164 -0
  21. package/daemon/client/components/CodeBlock.tsx +35 -0
  22. package/daemon/client/components/Diff.tsx +65 -0
  23. package/daemon/client/components/FilePreview.tsx +88 -0
  24. package/daemon/client/components/Image.tsx +45 -0
  25. package/daemon/client/components/Mermaid.tsx +277 -0
  26. package/daemon/client/components/Note.tsx +13 -0
  27. package/daemon/client/components/Priority.tsx +19 -0
  28. package/daemon/client/components/ResponseNote.tsx +24 -0
  29. package/daemon/client/components/Section.tsx +32 -0
  30. package/daemon/client/components/Table.tsx +35 -0
  31. package/daemon/client/components/Task.tsx +75 -0
  32. package/daemon/client/components/UserInput.tsx +180 -0
  33. package/daemon/client/components/index.ts +15 -0
  34. package/daemon/client/domDiff.ts +336 -0
  35. package/daemon/client/exportMarkdown.ts +267 -0
  36. package/daemon/client/generateMarkdown.ts +194 -0
  37. package/daemon/client/highlightRange.ts +233 -0
  38. package/daemon/client/index.html +26 -0
  39. package/daemon/client/lcs.ts +19 -0
  40. package/daemon/client/popoverPosition.ts +31 -0
  41. package/daemon/client/runtime.ts +89 -0
  42. package/daemon/client/styles.css +306 -0
  43. package/daemon/client/theme.css +66 -0
  44. package/daemon/client/unifiedDiff.ts +604 -0
  45. package/daemon/dist/client.css +2 -0
  46. package/daemon/dist/client.js +68 -0
  47. package/daemon/dist/components.js +5 -0
  48. package/daemon/dist/index.html +108 -0
  49. package/daemon/dist/jsx-dev-runtime-shim.js +17 -0
  50. package/daemon/dist/jsx-runtime-shim.js +17 -0
  51. package/daemon/dist/react-dom-client-shim.js +4 -0
  52. package/daemon/dist/react-dom-shim.js +4 -0
  53. package/daemon/dist/react-shim.js +4 -0
  54. package/daemon/dist/runtime.js +1 -0
  55. package/daemon/dist/theme.css +66 -0
  56. package/daemon/src/compiler.ts +92 -0
  57. package/daemon/src/paths.ts +22 -0
  58. package/daemon/src/server.ts +448 -0
  59. package/daemon/src/session.ts +274 -0
  60. package/daemon/src/watcher.ts +59 -0
  61. package/hooks/session-start.sh +11 -0
  62. package/package.json +39 -0
  63. package/skills/canvas/SKILL.md +174 -0
  64. package/skills/canvas/components.md +349 -0
  65. package/skills/canvas/flows.md +406 -0
@@ -0,0 +1,108 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="dark">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Agent Canvas</title>
7
+ <script>document.documentElement.dataset.theme = localStorage.getItem('canvas-theme') || 'dark';</script>
8
+ <link rel="stylesheet" href="/assets/client.css" />
9
+ <link rel="stylesheet" href="https://unpkg.com/@highlightjs/cdn-assets@11.11.1/styles/github-dark-dimmed.min.css" />
10
+ <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
11
+ <style type="text/tailwindcss">
12
+ @theme {
13
+ /* Colors — initial values, overridden by [data-theme] in styles.css */
14
+ --color-bg-base: #1a1915;
15
+ --color-bg-surface: #1f1e18;
16
+ --color-bg-elevated: #25241d;
17
+ --color-bg-code: #1c1b16;
18
+ --color-bg-input: rgba(255, 248, 220, 0.03);
19
+ --color-bg-elevated-half: rgba(37, 36, 29, 0.5);
20
+
21
+ --color-text-primary: #e8e4df;
22
+ --color-text-secondary: #a09a92;
23
+ --color-text-tertiary: #847d75;
24
+ --color-text-code: #c4beb7;
25
+ --color-text-disabled: #3a3530;
26
+ --color-text-inverse: #1a1a1a;
27
+
28
+ --color-border-subtle: rgba(255, 248, 240, 0.06);
29
+ --color-border-medium: rgba(255, 248, 240, 0.08);
30
+ --color-border-hover: rgba(255, 248, 240, 0.12);
31
+ --color-border-strong: rgba(255, 248, 240, 0.15);
32
+
33
+ --color-accent-green: #4a9e6d;
34
+ --color-accent-green-muted: rgba(74, 158, 109, 0.12);
35
+ --color-accent-amber: #c49a3a;
36
+ --color-accent-amber-muted: rgba(196, 154, 58, 0.12);
37
+ --color-accent-red: #c45a5a;
38
+ --color-accent-red-muted: rgba(196, 90, 90, 0.12);
39
+ --color-accent-blue: #5a8ec4;
40
+ --color-accent-blue-muted: rgba(90, 142, 196, 0.10);
41
+
42
+ --color-highlight-bg: rgba(255, 220, 100, 0.15);
43
+ --color-highlight-border: rgba(255, 220, 100, 0.35);
44
+ --color-highlight-annotation: rgba(255, 220, 100, 0.18);
45
+ --color-highlight-active: rgba(255, 220, 100, 0.25);
46
+ --color-highlight-selected: rgba(255, 220, 100, 0.10);
47
+
48
+ --color-badge-bg: rgba(196, 154, 58, 0.20);
49
+ --color-btn-primary: #e8e4df;
50
+ --color-btn-primary-text: #1a1a1a;
51
+
52
+ /* Fonts */
53
+ --font-heading: 'Instrument Serif', Georgia, 'Times New Roman', serif;
54
+ --font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
55
+ --font-mono: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
56
+
57
+ /* Font sizes */
58
+ --text-section: 1.75rem;
59
+ --text-section--line-height: 1.25;
60
+ --text-section--letter-spacing: -0.01em;
61
+ --text-task-label: 0.9375rem;
62
+ --text-task-label--line-height: 1.4;
63
+ --text-body: 0.875rem;
64
+ --text-body--line-height: 1.65;
65
+ --text-code: 0.8125rem;
66
+ --text-code--line-height: 1.6;
67
+ --text-meta: 0.75rem;
68
+ --text-meta--line-height: 1.4;
69
+ --text-meta--letter-spacing: 0.02em;
70
+ --text-tiny: 0.6875rem;
71
+ --text-tiny--line-height: 1.4;
72
+
73
+ /* Shadows */
74
+ --shadow-sm: 0 1px 2px var(--color-shadow);
75
+ --shadow-md: 0 4px 12px var(--color-shadow);
76
+ --shadow-lg: 0 8px 24px var(--color-shadow);
77
+ }
78
+
79
+ </style>
80
+ </head>
81
+ <body class="min-h-screen">
82
+ <div id="root"></div>
83
+ <script src="https://unpkg.com/@highlightjs/cdn-assets@11.11.1/highlight.min.js"></script>
84
+ <script src="https://unpkg.com/mermaid@11.4.1/dist/mermaid.min.js"></script>
85
+ <script>mermaid.initialize({ startOnLoad: false, theme: document.documentElement.dataset.theme === 'light' ? 'default' : 'dark' });</script>
86
+ <script src="https://unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
87
+ <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
88
+ <script>
89
+ // Create ESM modules from UMD globals
90
+ window.__REACT_ESM__ = Object.keys(React).reduce((m, k) => { m[k] = React[k]; return m; }, { default: React, __esModule: true });
91
+ window.__REACT_DOM_ESM__ = Object.keys(ReactDOM).reduce((m, k) => { m[k] = ReactDOM[k]; return m; }, { default: ReactDOM, __esModule: true });
92
+ </script>
93
+ <script type="importmap">
94
+ {
95
+ "imports": {
96
+ "react": "/assets/react-shim.js",
97
+ "react-dom": "/assets/react-dom-shim.js",
98
+ "react-dom/client": "/assets/react-dom-client-shim.js",
99
+ "react/jsx-runtime": "/assets/jsx-runtime-shim.js",
100
+ "react/jsx-dev-runtime": "/assets/jsx-dev-runtime-shim.js",
101
+ "#canvas/components": "/assets/components.js",
102
+ "#canvas/runtime": "/assets/runtime.js"
103
+ }
104
+ }
105
+ </script>
106
+ <script type="module" src="/assets/client.js"></script>
107
+ </body>
108
+ </html>
@@ -0,0 +1,17 @@
1
+
2
+ const R = window.React;
3
+ export const Fragment = R.Fragment;
4
+ function _jsx(type, props, key) {
5
+ if (props && 'children' in props) {
6
+ const { children, ...rest } = props;
7
+ if (key !== undefined && key !== null) rest.key = key;
8
+ return Array.isArray(children)
9
+ ? R.createElement(type, rest, ...children)
10
+ : R.createElement(type, rest, children);
11
+ }
12
+ if (key !== undefined && key !== null) props = { ...props, key };
13
+ return R.createElement(type, props);
14
+ }
15
+ export const jsx = _jsx;
16
+ export const jsxs = _jsx;
17
+ export function jsxDEV(type, props, key) { return _jsx(type, props, key); }
@@ -0,0 +1,17 @@
1
+
2
+ const R = window.React;
3
+ export const Fragment = R.Fragment;
4
+ function _jsx(type, props, key) {
5
+ if (props && 'children' in props) {
6
+ const { children, ...rest } = props;
7
+ if (key !== undefined && key !== null) rest.key = key;
8
+ return Array.isArray(children)
9
+ ? R.createElement(type, rest, ...children)
10
+ : R.createElement(type, rest, children);
11
+ }
12
+ if (key !== undefined && key !== null) props = { ...props, key };
13
+ return R.createElement(type, props);
14
+ }
15
+ export const jsx = _jsx;
16
+ export const jsxs = _jsx;
17
+ export function jsxDEV(type, props, key) { return _jsx(type, props, key); }
@@ -0,0 +1,4 @@
1
+
2
+ const RD = window.ReactDOM;
3
+ export const { createRoot, hydrateRoot } = RD;
4
+ export default { createRoot: RD.createRoot, hydrateRoot: RD.hydrateRoot };
@@ -0,0 +1,4 @@
1
+
2
+ const RD = window.ReactDOM;
3
+ export default RD;
4
+ export const { createPortal, flushSync, hydrate, render, unmountComponentAtNode, findDOMNode, unstable_batchedUpdates } = RD;
@@ -0,0 +1,4 @@
1
+
2
+ const R = window.React;
3
+ export default R;
4
+ export const { useState, useEffect, useRef, useCallback, useContext, useMemo, useReducer, createContext, createElement, Fragment, Children, cloneElement, isValidElement, memo, forwardRef, lazy, Suspense, startTransition, useTransition, useDeferredValue, useId, useSyncExternalStore, useInsertionEffect, useImperativeHandle, useLayoutEffect, useDebugValue, Component, PureComponent } = R;
@@ -0,0 +1 @@
1
+ import{createContext as z,useContext as I,useEffect as J}from"react";import{createContext as G}from"react";var H=G("");var K=z(null);function L(){return I(K)}function Q(h,q,j){let{setFeedbackEntry:B,removeFeedbackEntry:D}=L();J(()=>{return B(h,{id:h,markdown:q,label:j?.label,required:j?.required}),()=>D(h)},[h,q,j?.label,j?.required])}var R=z({setActiveView:()=>{}});export{Q as useFeedback,L as useAnnotations,H as SessionContext,K as AnnotationCtx,R as ActiveViewCtx};
@@ -0,0 +1,66 @@
1
+ @theme {
2
+ /* Colors — initial values, overridden by [data-theme] in styles.css */
3
+ --color-bg-base: #1a1a1a;
4
+ --color-bg-surface: #1e1e1e;
5
+ --color-bg-elevated: #242424;
6
+ --color-bg-code: #1c1c1c;
7
+ --color-bg-input: rgba(255, 248, 240, 0.03);
8
+ --color-bg-elevated-half: rgba(36, 36, 36, 0.5);
9
+
10
+ --color-text-primary: #e8e4df;
11
+ --color-text-secondary: #a09a92;
12
+ --color-text-tertiary: #847d75;
13
+ --color-text-code: #c4beb7;
14
+ --color-text-disabled: #3a3530;
15
+ --color-text-inverse: #1a1a1a;
16
+
17
+ --color-border-subtle: rgba(255, 248, 240, 0.06);
18
+ --color-border-medium: rgba(255, 248, 240, 0.08);
19
+ --color-border-hover: rgba(255, 248, 240, 0.12);
20
+ --color-border-strong: rgba(255, 248, 240, 0.15);
21
+
22
+ --color-accent-green: #4a9e6d;
23
+ --color-accent-green-muted: rgba(74, 158, 109, 0.12);
24
+ --color-accent-amber: #c49a3a;
25
+ --color-accent-amber-muted: rgba(196, 154, 58, 0.12);
26
+ --color-accent-red: #c45a5a;
27
+ --color-accent-red-muted: rgba(196, 90, 90, 0.12);
28
+ --color-accent-blue: #5a8ec4;
29
+ --color-accent-blue-muted: rgba(90, 142, 196, 0.10);
30
+
31
+ --color-highlight-bg: rgba(255, 220, 100, 0.15);
32
+ --color-highlight-border: rgba(255, 220, 100, 0.35);
33
+ --color-highlight-annotation: rgba(255, 220, 100, 0.08);
34
+ --color-highlight-active: rgba(255, 220, 100, 0.25);
35
+ --color-highlight-selected: rgba(255, 220, 100, 0.10);
36
+
37
+ --color-badge-bg: rgba(196, 154, 58, 0.20);
38
+ --color-btn-primary: #e8e4df;
39
+ --color-btn-primary-text: #1a1a1a;
40
+
41
+ /* Fonts */
42
+ --font-heading: 'Instrument Serif', Georgia, 'Times New Roman', serif;
43
+ --font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
44
+ --font-mono: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
45
+
46
+ /* Font sizes */
47
+ --text-section: 1.75rem;
48
+ --text-section--line-height: 1.25;
49
+ --text-section--letter-spacing: -0.01em;
50
+ --text-task-label: 0.9375rem;
51
+ --text-task-label--line-height: 1.4;
52
+ --text-body: 0.875rem;
53
+ --text-body--line-height: 1.65;
54
+ --text-code: 0.8125rem;
55
+ --text-code--line-height: 1.6;
56
+ --text-meta: 0.75rem;
57
+ --text-meta--line-height: 1.4;
58
+ --text-meta--letter-spacing: 0.02em;
59
+ --text-tiny: 0.6875rem;
60
+ --text-tiny--line-height: 1.4;
61
+
62
+ /* Shadows */
63
+ --shadow-sm: 0 1px 2px var(--color-shadow);
64
+ --shadow-md: 0 4px 12px var(--color-shadow);
65
+ --shadow-lg: 0 8px 24px var(--color-shadow);
66
+ }
@@ -0,0 +1,92 @@
1
+ import { writeFileSync, unlinkSync, readFileSync } from "fs";
2
+ import { join } from "path";
3
+ import { randomUUID } from "crypto";
4
+ import { COMPILE_TEMP_DIR } from "./paths";
5
+
6
+ type CompileResult =
7
+ | { ok: true; js: string }
8
+ | { ok: false; error: string };
9
+
10
+ const COMPONENT_IMPORTS = `import React from 'react';
11
+ import * as C from '#canvas/components';
12
+ const { Section, Item, Task, FilePreview, CodeBlock, Callout,
13
+ Mermaid, Table, Priority, Checklist, Note, Diff,
14
+ Choice, MultiChoice, UserInput, RangeInput, ImageView,
15
+ useFeedback, useAnnotations } = C;
16
+ `;
17
+
18
+ export async function compilePlan(jsx: string, projectRoot?: string): Promise<CompileResult> {
19
+ // Resolve FilePreview contents at compile time
20
+ if (projectRoot) {
21
+ jsx = resolveFilePreviews(jsx, projectRoot);
22
+ }
23
+
24
+ const hasDefaultExport = /export\s+default\b/.test(jsx);
25
+
26
+ const source = hasDefaultExport
27
+ ? `${COMPONENT_IMPORTS}\n${jsx}`
28
+ : `${COMPONENT_IMPORTS}\nexport default function Plan() {\n return (<>${jsx}</>);\n}\n`;
29
+
30
+ const tmpFile = join(COMPILE_TEMP_DIR, `plan-${randomUUID()}.jsx`);
31
+
32
+ try {
33
+ writeFileSync(tmpFile, source);
34
+
35
+ const result = await Bun.build({
36
+ entrypoints: [tmpFile],
37
+ format: "esm",
38
+ external: ["react", "react-dom", "#canvas/components", "#canvas/runtime"],
39
+ });
40
+
41
+ if (!result.success) {
42
+ const errors = result.logs
43
+ .filter((l) => l.level === "error")
44
+ .map((l) => l.message)
45
+ .join("\n");
46
+ return { ok: false, error: errors || "Compilation failed" };
47
+ }
48
+
49
+ const js = await result.outputs[0].text();
50
+ return { ok: true, js };
51
+ } catch (e: any) {
52
+ return { ok: false, error: e.message };
53
+ } finally {
54
+ try { unlinkSync(tmpFile); } catch {}
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Find <FilePreview path="..." /> tags and inject __content with the file contents
60
+ * resolved at compile time so the browser doesn't need to fetch.
61
+ */
62
+ function resolveFilePreviews(jsx: string, projectRoot: string): string {
63
+ // Match <FilePreview ... /> (self-closing)
64
+ return jsx.replace(
65
+ /<FilePreview\b([^>]*?)\/>/g,
66
+ (match, attrs: string) => {
67
+ const pathMatch = attrs.match(/path=["']([^"']+)["']/);
68
+ if (!pathMatch) return match;
69
+
70
+ const filePath = pathMatch[1];
71
+ const absPath = join(projectRoot, filePath);
72
+
73
+ try {
74
+ let content = readFileSync(absPath, "utf-8");
75
+
76
+ // Apply lines filter if present
77
+ const linesMatch = attrs.match(/lines=\{?\[(\d+)\s*,\s*(\d+)\]\}?/);
78
+ if (linesMatch) {
79
+ const start = parseInt(linesMatch[1], 10);
80
+ const end = parseInt(linesMatch[2], 10);
81
+ content = content.split("\n").slice(start - 1, end).join("\n");
82
+ }
83
+
84
+ // Escape for embedding in JSX string
85
+ const escaped = JSON.stringify(content);
86
+ return `<FilePreview${attrs} __content={${escaped}} />`;
87
+ } catch {
88
+ return match;
89
+ }
90
+ }
91
+ );
92
+ }
@@ -0,0 +1,22 @@
1
+ import { join } from "path";
2
+ import { homedir, tmpdir } from "os";
3
+ import { mkdirSync } from "fs";
4
+
5
+ /** Persistent data — survives reboots */
6
+ export const DATA_DIR = join(homedir(), ".claude", "agent-canvas");
7
+
8
+ /** Session storage */
9
+ export const SESSIONS_DIR = join(DATA_DIR, "sessions");
10
+
11
+ /** Ephemeral — can be lost on reboot */
12
+ export const TEMP_DIR = join(tmpdir(), "agent-canvas");
13
+
14
+ /** PID file for daemon process */
15
+ export const PID_FILE = join(TEMP_DIR, "daemon.pid");
16
+
17
+ /** Temp dir for JSX compilation */
18
+ export const COMPILE_TEMP_DIR = join(TEMP_DIR, "compile");
19
+
20
+ // Ensure dirs exist
21
+ mkdirSync(SESSIONS_DIR, { recursive: true });
22
+ mkdirSync(COMPILE_TEMP_DIR, { recursive: true });