wao 0.40.0 → 0.40.2
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/cjs/ao.js +4 -4
- package/cjs/create.js +32 -27
- package/cjs/workspace/.claude/agents/builder.md +4 -2
- package/cjs/workspace/.claude/skills/build/SKILL.md +34 -12
- package/cjs/workspace/.claude/skills/build-aos/SKILL.md +10 -1
- package/cjs/workspace/.claude/skills/build-device/SKILL.md +37 -23
- package/cjs/workspace/.claude/skills/build-frontend/SKILL.md +17 -14
- package/cjs/workspace/.claude/skills/build-module/SKILL.md +10 -1
- package/cjs/workspace/.claude/skills/plan/SKILL.md +12 -4
- package/cjs/workspace/.claude/skills/readme/SKILL.md +151 -45
- package/cjs/workspace/.claude/skills/report/SKILL.md +2 -1
- package/cjs/workspace/dashboard/index.html +154 -3
- package/cjs/workspace/dashboard/public/favicon.ico +0 -0
- package/cjs/workspace/dashboard/public/favicon.png +0 -0
- package/cjs/workspace/dashboard/server.js +93 -12
- package/cjs/workspace/dashboard/src/App.jsx +2297 -372
- package/cjs/workspace/docs/wao-sdk.md +1 -1
- package/cjs/workspace/package.json +1 -1
- package/esm/ao.js +3 -3
- package/esm/create.js +3 -0
- package/esm/workspace/.claude/agents/builder.md +4 -2
- package/esm/workspace/.claude/skills/build/SKILL.md +34 -12
- package/esm/workspace/.claude/skills/build-aos/SKILL.md +10 -1
- package/esm/workspace/.claude/skills/build-device/SKILL.md +37 -23
- package/esm/workspace/.claude/skills/build-frontend/SKILL.md +17 -14
- package/esm/workspace/.claude/skills/build-module/SKILL.md +10 -1
- package/esm/workspace/.claude/skills/plan/SKILL.md +12 -4
- package/esm/workspace/.claude/skills/readme/SKILL.md +151 -45
- package/esm/workspace/.claude/skills/report/SKILL.md +2 -1
- package/esm/workspace/dashboard/index.html +154 -3
- package/esm/workspace/dashboard/public/favicon.ico +0 -0
- package/esm/workspace/dashboard/public/favicon.png +0 -0
- package/esm/workspace/dashboard/server.js +93 -12
- package/esm/workspace/dashboard/src/App.jsx +2297 -372
- package/esm/workspace/docs/wao-sdk.md +1 -1
- package/esm/workspace/package.json +1 -1
- 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
|
|
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,
|
|
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
|
-
|
|
112
|
+
{repeat for every script}
|
|
15
113
|
|
|
16
|
-
|
|
114
|
+
## Device Reference (if applicable)
|
|
17
115
|
|
|
18
|
-
|
|
116
|
+
### dev_{name}.erl
|
|
19
117
|
|
|
20
|
-
|
|
118
|
+
{What this device does}
|
|
21
119
|
|
|
22
|
-
|
|
120
|
+
| Function | Input | Output | Description |
|
|
121
|
+
|----------|-------|--------|-------------|
|
|
122
|
+
| {function} | {params} | {return} | {what it does} |
|
|
23
123
|
|
|
24
|
-
|
|
124
|
+
{repeat for every device}
|
|
25
125
|
|
|
26
|
-
|
|
27
|
-
# {Project Name}
|
|
126
|
+
## Custom Module Reference (if applicable)
|
|
28
127
|
|
|
29
|
-
|
|
128
|
+
### {module_name}
|
|
30
129
|
|
|
31
|
-
|
|
130
|
+
{What this module does, how it differs from standard AOS}
|
|
32
131
|
|
|
33
|
-
|
|
34
|
-
{How they connect: AOS scripts ↔ HyperBEAM devices ↔ Frontend}
|
|
132
|
+
## Frontend (if applicable)
|
|
35
133
|
|
|
36
|
-
|
|
134
|
+
{What the app looks like, what users can do}
|
|
37
135
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
- Erlang/OTP 27+ (if using HyperBEAM devices)
|
|
136
|
+
### Components
|
|
137
|
+
- {component} — {what it renders}
|
|
41
138
|
|
|
42
|
-
|
|
139
|
+
### Hooks
|
|
140
|
+
- {hook} — {what state it manages}
|
|
43
141
|
|
|
44
|
-
|
|
45
|
-
yarn install
|
|
46
|
-
```
|
|
142
|
+
## Testing
|
|
47
143
|
|
|
48
|
-
|
|
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
|
-
|
|
51
|
-
|---------|--------|------------|--------|
|
|
52
|
-
{table from Lua source}
|
|
152
|
+
## Deploy
|
|
53
153
|
|
|
54
|
-
|
|
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
|
-
|
|
57
|
-
|--------|--------|---------|-------------|
|
|
58
|
-
{table from Erlang source}
|
|
162
|
+
### How Deploy Works
|
|
59
163
|
|
|
60
|
-
|
|
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
|
-
|
|
168
|
+
### Verify
|
|
63
169
|
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
|
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>
|
|
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>
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
|
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/
|
|
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
|
|