wao 0.40.0 → 0.40.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.
Files changed (33) hide show
  1. package/cjs/create.js +32 -27
  2. package/cjs/workspace/.claude/agents/builder.md +4 -2
  3. package/cjs/workspace/.claude/skills/build/SKILL.md +34 -12
  4. package/cjs/workspace/.claude/skills/build-aos/SKILL.md +10 -1
  5. package/cjs/workspace/.claude/skills/build-device/SKILL.md +37 -23
  6. package/cjs/workspace/.claude/skills/build-frontend/SKILL.md +17 -14
  7. package/cjs/workspace/.claude/skills/build-module/SKILL.md +10 -1
  8. package/cjs/workspace/.claude/skills/plan/SKILL.md +12 -4
  9. package/cjs/workspace/.claude/skills/readme/SKILL.md +151 -45
  10. package/cjs/workspace/.claude/skills/report/SKILL.md +2 -1
  11. package/cjs/workspace/dashboard/index.html +154 -3
  12. package/cjs/workspace/dashboard/public/favicon.ico +0 -0
  13. package/cjs/workspace/dashboard/public/favicon.png +0 -0
  14. package/cjs/workspace/dashboard/server.js +93 -12
  15. package/cjs/workspace/dashboard/src/App.jsx +2297 -372
  16. package/cjs/workspace/package.json +1 -1
  17. package/esm/create.js +3 -0
  18. package/esm/workspace/.claude/agents/builder.md +4 -2
  19. package/esm/workspace/.claude/skills/build/SKILL.md +34 -12
  20. package/esm/workspace/.claude/skills/build-aos/SKILL.md +10 -1
  21. package/esm/workspace/.claude/skills/build-device/SKILL.md +37 -23
  22. package/esm/workspace/.claude/skills/build-frontend/SKILL.md +17 -14
  23. package/esm/workspace/.claude/skills/build-module/SKILL.md +10 -1
  24. package/esm/workspace/.claude/skills/plan/SKILL.md +12 -4
  25. package/esm/workspace/.claude/skills/readme/SKILL.md +151 -45
  26. package/esm/workspace/.claude/skills/report/SKILL.md +2 -1
  27. package/esm/workspace/dashboard/index.html +154 -3
  28. package/esm/workspace/dashboard/public/favicon.ico +0 -0
  29. package/esm/workspace/dashboard/public/favicon.png +0 -0
  30. package/esm/workspace/dashboard/server.js +93 -12
  31. package/esm/workspace/dashboard/src/App.jsx +2297 -372
  32. package/esm/workspace/package.json +1 -1
  33. package/package.json +1 -1
@@ -5,76 +5,182 @@ disable-model-invocation: true
5
5
  allowed-tools: Read, Write, Glob, Grep
6
6
  ---
7
7
 
8
- Generate a comprehensive README.md from the project's plan, code, and test files.
8
+ Generate a comprehensive README.md that explains the project thoroughly — what it does, why it exists, how it works, and complete API references for every script, device, and module.
9
+
10
+ ## Performance Notes
11
+ - Read every source file before writing — the README must be accurate, not guessed
12
+ - Include real code examples extracted from test files
13
+ - Document every handler/action, not just the main ones
9
14
 
10
15
  ## Steps
11
16
 
12
- 1. Read `plan.md` for the feature overview, components, and architecture.
17
+ 1. Read `plan.md` for the feature overview, architecture decisions, and edge cases.
18
+
19
+ 2. Read **every** AOS script in `src/*.lua`. For each script, extract:
20
+ - Purpose and what problem it solves
21
+ - Every `Handlers.add()` — action name, expected tags, return data, error cases
22
+ - State shape (what globals/tables the script maintains)
23
+ - Interactions with other scripts (cross-process messages)
24
+
25
+ 3. Read **every** test file in `test/*.test.js`. Extract:
26
+ - Real usage examples (deploy, message, dry-run patterns)
27
+ - Edge cases being tested (what fails, what's validated)
28
+ - Multi-user scenarios
29
+
30
+ 4. If `custom-lua/` or `custom-wasm/` exist, read the module source:
31
+ - What the module does (compute function, state handling)
32
+ - Input/output format
33
+ - How it differs from standard AOS
34
+
35
+ 5. If `HyperBEAM/src/dev_*.erl` files exist, read each device:
36
+ - Device purpose and when to use it
37
+ - Exported functions and their signatures
38
+ - State structure and lifecycle
39
+ - HTTP endpoints it exposes
40
+
41
+ 6. If `frontend/src/` exists, read key components and hooks:
42
+ - What the app does from a user perspective
43
+ - How it connects to AOS (which hooks, which actions)
44
+ - Wallet integration (ArConnect)
45
+
46
+ 7. Read `package.json` for available scripts and dependencies.
47
+
48
+ 8. Read `scripts/deploy.js` for deployment details.
49
+
50
+ 9. **Write `README.md`** at the project root with ALL of these sections:
51
+
52
+ ```markdown
53
+ # {Project Name}
54
+
55
+ {2-3 sentence description: what it does, what platform it runs on, who it's for}
56
+
57
+ ## Overview
58
+
59
+ {Expanded explanation — what problem does this solve? What are the key features?
60
+ List each major capability as a bullet point with a brief explanation.}
61
+
62
+ ## How It Works
63
+
64
+ {Explain the architecture in plain language:
65
+ - What happens when a user interacts with the app
66
+ - How AOS scripts process messages
67
+ - How state is maintained
68
+ - If HyperBEAM: how devices fit in the pipeline
69
+ - If frontend: how the browser connects to AOS}
70
+
71
+ ## Project Structure
72
+
73
+ ```
74
+ {file tree with inline comments for every important file}
75
+ ```
76
+
77
+ ## Prerequisites
78
+
79
+ - Node.js 20+
80
+ - Arweave wallet (`yarn keygen` to generate)
81
+ - {Erlang/OTP 27+ if devices}
82
+ - {ArConnect browser extension if frontend}
83
+
84
+ ## Setup
85
+
86
+ ```bash
87
+ yarn install
88
+ yarn keygen
89
+ ```
90
+
91
+ ## AOS Script Reference
92
+
93
+ ### {script_name}.lua
94
+
95
+ {What this script does and why it exists}
96
+
97
+ **State**: {describe the tables/globals maintained}
98
+
99
+ | Action | Tags | Returns | Description |
100
+ |--------|------|---------|-------------|
101
+ | {action} | {required tags} | {return data} | {what it does} |
102
+
103
+ **Example** (from tests):
104
+ ```js
105
+ {real code example extracted from test file}
106
+ ```
107
+
108
+ **Edge Cases**:
109
+ - {what happens on invalid input}
110
+ - {what happens on insufficient balance, etc.}
13
111
 
14
- 2. Read all AOS scripts in `src/*.lua` — extract script names, actions, input/output.
112
+ {repeat for every script}
15
113
 
16
- 3. Read test files in `test/*.test.js` — extract usage examples and test scenarios.
114
+ ## Device Reference (if applicable)
17
115
 
18
- 4. Read `package.json` for available scripts and dependencies.
116
+ ### dev_{name}.erl
19
117
 
20
- 5. If `HyperBEAM/src/dev_*.erl` files exist, read them for device reference.
118
+ {What this device does}
21
119
 
22
- 6. If `frontend/src/` exists, read key components for frontend usage.
120
+ | Function | Input | Output | Description |
121
+ |----------|-------|--------|-------------|
122
+ | {function} | {params} | {return} | {what it does} |
23
123
 
24
- 7. **Write `README.md`** at the project root with these sections:
124
+ {repeat for every device}
25
125
 
26
- ```markdown
27
- # {Project Name}
126
+ ## Custom Module Reference (if applicable)
28
127
 
29
- {1-2 sentence description from plan.md}
128
+ ### {module_name}
30
129
 
31
- ## Architecture
130
+ {What this module does, how it differs from standard AOS}
32
131
 
33
- {Diagram or description of which tracks are included: AOS / Device / Frontend}
34
- {How they connect: AOS scripts ↔ HyperBEAM devices ↔ Frontend}
132
+ ## Frontend (if applicable)
35
133
 
36
- ## Prerequisites
134
+ {What the app looks like, what users can do}
37
135
 
38
- - Node.js 20+
39
- - Arweave wallet (auto-generated at `.wallet.json`)
40
- - Erlang/OTP 27+ (if using HyperBEAM devices)
136
+ ### Components
137
+ - {component} {what it renders}
41
138
 
42
- ## Setup
139
+ ### Hooks
140
+ - {hook} — {what state it manages}
43
141
 
44
- ```bash
45
- yarn install
46
- ```
142
+ ## Testing
47
143
 
48
- ## AOS Scripts
144
+ ```bash
145
+ yarn test test/aos.test.js # in-memory AOS (fast)
146
+ yarn test test/{script}.test.js # specific script tests
147
+ yarn test test/hyperbeam.test.js # HyperBEAM integration
148
+ cd frontend && npm run test:unit # frontend vitest
149
+ cd frontend && npm run test:e2e # Playwright E2E
150
+ ```
49
151
 
50
- | Script | Action | Input Tags | Output |
51
- |---------|--------|------------|--------|
52
- {table from Lua source}
152
+ ## Deploy
53
153
 
54
- ## Devices (if applicable)
154
+ ```bash
155
+ yarn keygen # generate wallet (first time only)
156
+ yarn deploy # all scripts to AO testnet
157
+ yarn deploy src/{script}.lua # single script
158
+ yarn deploy --local-hb # local HyperBEAM
159
+ yarn deploy --mainnet # remote HyperBEAM (production)
160
+ ```
55
161
 
56
- | Device | Module | Actions | Description |
57
- |--------|--------|---------|-------------|
58
- {table from Erlang source}
162
+ ### How Deploy Works
59
163
 
60
- ## Frontend (if applicable)
164
+ {Explain: reads src/*.lua, spawns a process per file.
165
+ On testnet: Eval message. On HyperBEAM: ao.deploy().
166
+ Each script gets its own process ID printed to console.}
61
167
 
62
- {Component overview, how to run dev server}
168
+ ### Verify
63
169
 
64
- ## Testing
170
+ - [aolink](https://aolink.ar.io/#/entity/{PROCESS_ID}) — inspect AOS process
171
+ - [lunar](https://lunar.ar.io/#/process/{PROCESS_ID}) — inspect HyperBEAM process
65
172
 
66
- ```bash
67
- yarn test # in-memory AOS tests
68
- yarn test test/hyperbeam.test.js # HyperBEAM integration
69
- cd frontend && npm run test:unit # frontend vitest
70
- cd frontend && npm run test:e2e # Playwright E2E
71
- ```
173
+ ## Built With
72
174
 
73
- ## Deployment
175
+ - [WAO](https://docs.wao.eco) — SDK for AO and HyperBEAM
176
+ - [AOS](https://ao.arweave.dev) — Lua processes on the AO computer
177
+ {add others as relevant}
178
+ ```
74
179
 
75
- ```bash
76
- yarn deploy src/{script}.lua
77
- ```
78
- ```
180
+ 10. After writing, re-read the README and verify:
181
+ - Every AOS action from every script is documented
182
+ - Every device function is documented
183
+ - Code examples are real (from test files), not made up
184
+ - Edge cases are mentioned
79
185
 
80
- 8. Update the task status to `"done"` in `tasks.json`.
186
+ 11. Update the task status to `"done"` in `tasks.json`.
@@ -41,7 +41,8 @@ Components:
41
41
 
42
42
  5. If any task is `in_progress`, run a quick check on its "done when" condition:
43
43
  - aos-test: run `yarn test` and report pass/fail count
44
- - device-test: run `rebar3 eunit` and report
44
+ - device: run `rebar3 eunit --module=dev_{name}` and report (eunit tests are inline)
45
+ - module-test: run `yarn test test/{name}-module.test.js` and report
45
46
  - frontend-test: run `cd frontend && npm run test:unit` and report
46
47
  - For other types, just report the status
47
48
 
@@ -1,11 +1,162 @@
1
1
  <!doctype html>
2
- <html lang="en">
2
+ <html lang="en" data-color-mode="light" data-light-theme="light" data-dark-theme="dark">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>WAO Dashboard</title>
6
+ <title>HyperADD Dashboard</title>
7
+ <link rel="icon" type="image/png" href="/favicon.png" />
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@primer/css@21/dist/primer.css">
9
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/github-markdown-css@5/github-markdown.min.css">
10
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css" id="hljs-light">
11
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css" id="hljs-dark" disabled>
12
+ <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
13
+ <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/languages/erlang.min.js"></script>
14
+ <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/languages/lua.min.js"></script>
15
+ <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/languages/rust.min.js"></script>
16
+ <style>
17
+ /* Only custom CSS for things Primer doesn't provide */
18
+ @keyframes spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }
19
+ .anim-spin { animation: spin 1s linear infinite; }
20
+ .anim-spin-slow { animation: spin 1.5s linear infinite; }
21
+ [data-color-mode="dark"] .logo-invert { filter: invert(1); }
22
+
23
+ /* Dark toggle — GitHub-style icon button */
24
+ .dark-toggle {
25
+ display: flex; align-items: center; justify-content: center;
26
+ width: 32px; height: 32px; border-radius: 6px;
27
+ border: 1px solid var(--color-border-default, #d0d7de);
28
+ background: var(--color-canvas-default, #fff); cursor: pointer;
29
+ color: var(--color-fg-muted, #656d76);
30
+ transition: background 0.15s, border-color 0.15s; appearance: none; padding: 0;
31
+ }
32
+ .dark-toggle:hover { background-color: var(--color-canvas-subtle, #f6f8fa); color: var(--color-fg-default); border-color: var(--color-border-default, #d0d7de); }
33
+ .dark-toggle:focus { outline: 2px solid var(--color-accent-fg); outline-offset: -2px; }
34
+ /* Override Primer Header to match modern GitHub (light gray, not black) */
35
+ .Header { background-color: var(--color-canvas-subtle, #f6f8fa) !important; color: var(--color-fg-default) !important; border-bottom: 1px solid var(--color-border-muted, #d0d7de); }
36
+ .Header .Header-link { color: var(--color-fg-default) !important; }
37
+ [data-color-mode="dark"] .Header { background-color: var(--color-canvas-subtle, #161b22) !important; border-bottom-color: var(--color-border-default, #30363d); }
38
+ /* Dark toggle inside Header */
39
+ .Header .dark-toggle { background: transparent; border-color: var(--color-border-default, #d0d7de); }
40
+ .Header .dark-toggle:hover { background-color: var(--color-neutral-muted); }
41
+
42
+ /* UnderlineNav button — Primer needs this for <button> elements */
43
+ button.UnderlineNav-item {
44
+ background: transparent; border: 0;
45
+ border-bottom: 2px solid transparent; border-radius: 0;
46
+ appearance: none; cursor: pointer;
47
+ padding: 8px 16px; font-size: 14px; line-height: 30px;
48
+ color: var(--color-fg-default);
49
+ margin-bottom: -1px;
50
+ }
51
+ button.UnderlineNav-item:hover {
52
+ border-bottom-color: var(--color-neutral-muted);
53
+ text-decoration: none;
54
+ }
55
+ button.UnderlineNav-item[aria-selected="true"] {
56
+ font-weight: 600;
57
+ border-bottom-color: var(--color-accent-fg);
58
+ color: var(--color-fg-default);
59
+ }
60
+
61
+ /* Progress bar override — ensure correct height and color */
62
+ .Progress { height: 8px; }
63
+ .Progress-item { background-color: var(--color-success-emphasis, #1f883d) !important; }
64
+
65
+ /* Status icon circles */
66
+ .icon-circle { width: 22px; height: 22px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
67
+ .icon-circle-success { background-color: var(--color-success-emphasis, #1f883d); }
68
+ .icon-circle-attention { background-color: var(--color-attention-emphasis, #bf8700); }
69
+ .icon-circle-empty { border: 2px solid var(--color-border-default, #d0d7de); }
70
+
71
+ /* Clickable file rows */
72
+ .file-row { cursor: pointer; transition: background 0.1s; }
73
+ .file-row:hover { background-color: var(--color-neutral-subtle) !important; }
74
+
75
+ /* Code viewer — GitHub blob-style */
76
+ .code-view { overflow-x: auto; }
77
+ .code-view table { width: 100%; border-collapse: collapse; font-size: 12px; line-height: 20px; tab-size: 4; }
78
+ .code-view td { padding: 0; vertical-align: top; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; }
79
+ .code-view .ln { width: 1px; min-width: 50px; padding: 0 10px; text-align: right; user-select: none; white-space: nowrap; color: var(--color-fg-subtle); }
80
+ .code-view .lc { padding: 0 16px 0 10px; white-space: pre; }
81
+ .code-view tr:hover { background: var(--color-neutral-subtle); }
82
+ .code-view .hljs { background: transparent !important; }
83
+
84
+ /* Back button */
85
+ .back-btn {
86
+ display: inline-flex; align-items: center; gap: 4px;
87
+ padding: 4px 12px 4px 8px; border: 1px solid var(--color-border-default);
88
+ border-radius: 6px; background: transparent; cursor: pointer;
89
+ font-size: 12px; color: var(--color-fg-muted); font-family: inherit;
90
+ }
91
+ .back-btn:hover { background: var(--color-neutral-subtle); color: var(--color-fg-default); }
92
+
93
+ /* Skill badge — Primer Label doesn't have filled-color style */
94
+ .skill-badge {
95
+ display: inline-block; padding: 0 6px; font-size: 11px; font-weight: 600; line-height: 18px;
96
+ border-radius: 4px; color: #fff; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
97
+ }
98
+
99
+ /* Markdown body — explicit light/dark overrides (github-markdown-css ignores data-color-mode) */
100
+ .markdown-body { background-color: transparent !important; color: inherit !important; }
101
+ .markdown-body pre { background-color: #f6f8fa !important; }
102
+ .markdown-body code { background-color: rgba(175,184,193,0.2) !important; }
103
+ .markdown-body pre code { background-color: transparent !important; }
104
+ .markdown-body table { border-color: #d0d7de !important; }
105
+ .markdown-body table th, .markdown-body table td { border-color: #d0d7de !important; }
106
+ .markdown-body table tr { background-color: transparent !important; }
107
+ .markdown-body table tr:nth-child(2n) { background-color: #f6f8fa !important; }
108
+ .markdown-body hr { background-color: #d0d7de !important; }
109
+ [data-color-mode="dark"] .markdown-body pre { background-color: #161b22 !important; }
110
+ [data-color-mode="dark"] .markdown-body code { background-color: rgba(110,118,129,0.4) !important; }
111
+ [data-color-mode="dark"] .markdown-body pre code { background-color: transparent !important; }
112
+ [data-color-mode="dark"] .markdown-body table { border-color: #30363d !important; }
113
+ [data-color-mode="dark"] .markdown-body table th, [data-color-mode="dark"] .markdown-body table td { border-color: #30363d !important; }
114
+ [data-color-mode="dark"] .markdown-body table tr:nth-child(2n) { background-color: #161b22 !important; }
115
+ [data-color-mode="dark"] .markdown-body hr { background-color: #30363d !important; }
116
+
117
+ /* Compact markdown */
118
+ .markdown-compact { font-size: 14px; }
119
+ .markdown-compact h1 { font-size: 20px; margin: 16px 0 8px; padding-bottom: 6px; border-bottom: 1px solid var(--color-border-muted); }
120
+ .markdown-compact h2 { font-size: 16px; margin: 14px 0 6px; padding-bottom: 4px; border-bottom: 1px solid var(--color-border-muted); }
121
+ .markdown-compact h3 { font-size: 14px; font-weight: 600; margin: 10px 0 4px; }
122
+ .markdown-compact p { margin: 4px 0; }
123
+ .markdown-compact li { margin: 2px 0; }
124
+ .markdown-compact pre {
125
+ margin: 8px 0; padding: 16px; font-size: 12px; line-height: 1.45;
126
+ overflow: auto; border-radius: 6px;
127
+ background-color: #f6f8fa !important;
128
+ }
129
+ [data-color-mode="dark"] .markdown-compact pre { background-color: #161b22 !important; }
130
+ .markdown-compact code {
131
+ font-size: 85%; padding: 0.2em 0.4em; border-radius: 6px;
132
+ background-color: rgba(175,184,193,0.2);
133
+ }
134
+ [data-color-mode="dark"] .markdown-compact code { background-color: rgba(110,118,129,0.4); }
135
+ .markdown-compact pre code {
136
+ padding: 0; background: none !important; font-size: 100%;
137
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
138
+ }
139
+ .markdown-compact .hljs,
140
+ .markdown-compact pre code.hljs { background: transparent !important; }
141
+ .markdown-compact ul, .markdown-compact ol { padding-left: 24px; margin: 4px 0; }
142
+
143
+ /* Code block copy button */
144
+ .code-block-wrap { position: relative; }
145
+ .code-copy-btn {
146
+ position: absolute; top: 8px; right: 8px; z-index: 1;
147
+ display: flex; align-items: center; justify-content: center;
148
+ width: 32px; height: 32px; border-radius: 6px;
149
+ border: 1px solid var(--color-border-default, #d0d7de);
150
+ background: var(--color-canvas-default, #fff); color: var(--color-fg-muted, #656d76);
151
+ cursor: pointer; opacity: 0; transition: opacity 0.15s;
152
+ }
153
+ .code-block-wrap:hover .code-copy-btn { opacity: 1; }
154
+ .code-copy-btn:hover { background: var(--color-canvas-subtle, #f6f8fa); color: var(--color-fg-default, #1f2328); }
155
+ [data-color-mode="dark"] .code-copy-btn { background: #161b22; border-color: #30363d; color: #8b949e; }
156
+ [data-color-mode="dark"] .code-copy-btn:hover { background: #30363d; color: #e6edf3; }
157
+ </style>
7
158
  </head>
8
- <body>
159
+ <body class="color-bg-default color-fg-default">
9
160
  <div id="root"></div>
10
161
  <script type="module" src="/src/main.jsx"></script>
11
162
  </body>
@@ -1,6 +1,6 @@
1
1
  import { createServer } from "node:http"
2
- import { readFileSync, watch, existsSync } from "node:fs"
3
- import { join, extname } from "node:path"
2
+ import { readFileSync, readdirSync, statSync, watch, existsSync } from "node:fs"
3
+ import { join, extname, relative } from "node:path"
4
4
 
5
5
  const PORT = 3333
6
6
  const ROOT = join(import.meta.dirname, "..")
@@ -52,12 +52,10 @@ function watchFile(filename) {
52
52
  try {
53
53
  const w = watch(filepath, { persistent: false }, onFileChange)
54
54
  w.on("error", () => {
55
- // File may have been deleted; retry after a delay
56
55
  setTimeout(() => watchFile(filename), 2000)
57
56
  })
58
57
  return w
59
58
  } catch {
60
- // File doesn't exist yet; retry
61
59
  setTimeout(() => watchFile(filename), 2000)
62
60
  return null
63
61
  }
@@ -66,6 +64,29 @@ function watchFile(filename) {
66
64
  watchFile("tasks.json")
67
65
  watchFile("plan.md")
68
66
 
67
+ // --- File scanner ---
68
+ const IGNORE_DIRS = new Set(["node_modules", ".git", "dashboard", "HyperBEAM", ".claude", "docs", "target", "dist", "_build"])
69
+
70
+ function scanFiles(dir, files = []) {
71
+ try {
72
+ const entries = readdirSync(dir, { withFileTypes: true })
73
+ for (const e of entries) {
74
+ if (e.name.startsWith(".") && e.name !== ".mcp.json") continue
75
+ const full = join(dir, e.name)
76
+ if (e.isDirectory()) {
77
+ if (IGNORE_DIRS.has(e.name)) continue
78
+ scanFiles(full, files)
79
+ } else {
80
+ try {
81
+ const s = statSync(full)
82
+ files.push({ path: relative(ROOT, full), size: s.size })
83
+ } catch {}
84
+ }
85
+ }
86
+ } catch {}
87
+ return files
88
+ }
89
+
69
90
  // --- Static file serving (production dist/) ---
70
91
  function serveStatic(req, res) {
71
92
  const url = req.url === "/" ? "/index.html" : req.url.split("?")[0]
@@ -76,7 +97,6 @@ function serveStatic(req, res) {
76
97
  res.writeHead(200, { "Content-Type": MIME[ext] || "application/octet-stream" })
77
98
  res.end(data)
78
99
  } catch {
79
- // SPA fallback: serve index.html for non-file routes
80
100
  try {
81
101
  const index = readFileSync(join(DIST, "index.html"))
82
102
  res.writeHead(200, { "Content-Type": "text/html" })
@@ -90,7 +110,6 @@ function serveStatic(req, res) {
90
110
 
91
111
  // --- HTTP server ---
92
112
  const server = createServer((req, res) => {
93
- // CORS headers
94
113
  res.setHeader("Access-Control-Allow-Origin", "*")
95
114
  res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS")
96
115
  res.setHeader("Access-Control-Allow-Headers", "Content-Type")
@@ -102,7 +121,7 @@ const server = createServer((req, res) => {
102
121
 
103
122
  const url = req.url.split("?")[0]
104
123
 
105
- // GET /api/progress — return tasks.json content
124
+ // GET /api/progress
106
125
  if (url === "/api/progress") {
107
126
  const data = readProgress()
108
127
  if (!data) {
@@ -113,7 +132,73 @@ const server = createServer((req, res) => {
113
132
  return res.end(JSON.stringify(data))
114
133
  }
115
134
 
116
- // GET /api/events — SSE endpoint
135
+ // GET /api/plan
136
+ if (url === "/api/plan") {
137
+ try {
138
+ const planPath = join(ROOT, "plan.md")
139
+ if (!existsSync(planPath)) {
140
+ res.writeHead(200, { "Content-Type": "application/json" })
141
+ return res.end(JSON.stringify({ content: null }))
142
+ }
143
+ const content = readFileSync(planPath, "utf8")
144
+ res.writeHead(200, { "Content-Type": "application/json" })
145
+ return res.end(JSON.stringify({ content }))
146
+ } catch {
147
+ res.writeHead(200, { "Content-Type": "application/json" })
148
+ return res.end(JSON.stringify({ content: null }))
149
+ }
150
+ }
151
+
152
+ // GET /api/files
153
+ if (url === "/api/files") {
154
+ const files = scanFiles(ROOT)
155
+ res.writeHead(200, { "Content-Type": "application/json" })
156
+ return res.end(JSON.stringify({ files }))
157
+ }
158
+
159
+ // GET /api/file?path=...
160
+ if (url === "/api/file") {
161
+ const params = new URL(req.url, "http://localhost").searchParams
162
+ const filePath = params.get("path")
163
+ if (!filePath) {
164
+ res.writeHead(400, { "Content-Type": "application/json" })
165
+ return res.end(JSON.stringify({ error: "Missing path parameter" }))
166
+ }
167
+ const fullPath = join(ROOT, filePath)
168
+ if (!fullPath.startsWith(ROOT + "/")) {
169
+ res.writeHead(403, { "Content-Type": "application/json" })
170
+ return res.end(JSON.stringify({ error: "Access denied" }))
171
+ }
172
+ try {
173
+ const content = readFileSync(fullPath, "utf8")
174
+ res.writeHead(200, { "Content-Type": "application/json" })
175
+ return res.end(JSON.stringify({ path: filePath, content }))
176
+ } catch {
177
+ res.writeHead(404, { "Content-Type": "application/json" })
178
+ return res.end(JSON.stringify({ error: "File not found" }))
179
+ }
180
+ }
181
+
182
+ // GET /api/deploy
183
+ if (url === "/api/deploy") {
184
+ const info = { scripts: {}, wallet: false, hyperbeam: null }
185
+ try {
186
+ const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8"))
187
+ info.scripts = pkg.scripts || {}
188
+ } catch {}
189
+ info.wallet = existsSync(join(ROOT, ".wallet.json"))
190
+ try {
191
+ const env = readFileSync(join(ROOT, ".env.hyperbeam"), "utf8")
192
+ const portMatch = env.match(/PORT=(\d+)/)
193
+ info.hyperbeam = { port: portMatch ? portMatch[1] : "10001", configured: true }
194
+ } catch {
195
+ info.hyperbeam = { configured: false }
196
+ }
197
+ res.writeHead(200, { "Content-Type": "application/json" })
198
+ return res.end(JSON.stringify(info))
199
+ }
200
+
201
+ // GET /api/events — SSE
117
202
  if (url === "/api/events") {
118
203
  res.writeHead(200, {
119
204
  "Content-Type": "text/event-stream",
@@ -121,19 +206,15 @@ const server = createServer((req, res) => {
121
206
  Connection: "keep-alive",
122
207
  })
123
208
  res.write("\n")
124
-
125
- // Send current state immediately
126
209
  const data = readProgress()
127
210
  if (data) {
128
211
  res.write(`event: progress\ndata: ${JSON.stringify(data)}\n\n`)
129
212
  }
130
-
131
213
  clients.add(res)
132
214
  req.on("close", () => clients.delete(res))
133
215
  return
134
216
  }
135
217
 
136
- // Static files (production build)
137
218
  serveStatic(req, res)
138
219
  })
139
220