stoa-mcp 0.1.1 → 0.1.3
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/README.md +240 -74
- package/dist/cli/moodboard-edit.d.ts +1 -0
- package/dist/cli/moodboard-edit.js +245 -0
- package/dist/cli/moodboard-picker.d.ts +2 -0
- package/dist/cli/moodboard-picker.js +125 -0
- package/dist/cli.js +299 -24
- package/dist/index.js +0 -0
- package/dist/storage/moodboard-presets.d.ts +30 -0
- package/dist/storage/moodboard-presets.js +160 -0
- package/dist/storage/project.js +23 -11
- package/package.json +1 -1
- package/templates/moodboard-presets/bold.json +18 -0
- package/templates/moodboard-presets/clean.json +18 -0
- package/templates/moodboard-presets/dark.json +18 -0
- package/templates/moodboard-presets/warm.json +18 -0
package/README.md
CHANGED
|
@@ -32,48 +32,60 @@ This creates a `.stoa/` folder with everything Stoa needs:
|
|
|
32
32
|
```
|
|
33
33
|
.stoa/
|
|
34
34
|
moodboard/notes.md ← your design direction (colors, layout, style)
|
|
35
|
+
moodboard/tokens.json ← auto-generated machine-readable design tokens
|
|
35
36
|
context.md ← dependencies, conventions, brand voice
|
|
36
37
|
lessons.md ← project memory (grows automatically)
|
|
37
38
|
guardrails/ ← rules the AI must follow
|
|
38
39
|
roles/ ← AI personas (Builder, Fixer, Planner)
|
|
40
|
+
presets/ ← your saved custom style presets
|
|
39
41
|
specs/ ← saved specifications
|
|
40
42
|
```
|
|
41
43
|
|
|
44
|
+
Every new project starts with the **Clean** style preset (white, minimal, Linear-style). Every spec you generate will include these design tokens automatically.
|
|
45
|
+
|
|
42
46
|
### 2. Set up your moodboard (optional but powerful)
|
|
43
47
|
|
|
44
|
-
|
|
48
|
+
Your project already has a working design system. Check it:
|
|
45
49
|
|
|
46
50
|
```bash
|
|
47
|
-
stoa
|
|
51
|
+
stoa moodboard
|
|
48
52
|
```
|
|
49
53
|
|
|
50
|
-
|
|
54
|
+
**Want a different style?** Pick a preset:
|
|
51
55
|
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
```bash
|
|
57
|
+
stoa moodboard preset
|
|
58
|
+
```
|
|
55
59
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
Use arrow keys to browse 4 built-in presets with live previews:
|
|
61
|
+
- **Clean** — White, minimal, Linear/Vercel style
|
|
62
|
+
- **Dark** — Dark background, muted accents, GitHub/Raycast style
|
|
63
|
+
- **Warm** — Cream tones, friendly SaaS feel
|
|
64
|
+
- **Bold** — High contrast, sharp corners, brutalist
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
Sidebar navigation left, main content right
|
|
66
|
+
Or apply directly: `stoa moodboard preset dark`
|
|
63
67
|
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
**Want to customize?** Edit interactively in the terminal:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
stoa moodboard edit
|
|
66
72
|
```
|
|
67
73
|
|
|
68
|
-
|
|
74
|
+
Walks you through each field (colors, typography, layout) one by one. Press Enter to keep, type a new value to change, or `q` to quit anytime.
|
|
69
75
|
|
|
70
|
-
**Have a screenshot of a design you like?**
|
|
76
|
+
**Have a screenshot of a design you like?** Run:
|
|
71
77
|
|
|
72
78
|
```bash
|
|
73
79
|
stoa moodboard describe
|
|
74
80
|
```
|
|
75
81
|
|
|
76
|
-
If you have an Anthropic API key, Stoa
|
|
82
|
+
This opens the moodboard folder in Finder — drop your screenshots in, press Enter. If you have an Anthropic API key, Stoa analyzes the images and writes the design system for you. If not, it prints a prompt you can paste into any Claude chat.
|
|
83
|
+
|
|
84
|
+
**Save your custom style** for reuse across projects:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
stoa moodboard save-preset my-brand
|
|
88
|
+
```
|
|
77
89
|
|
|
78
90
|
### 3. Refine your idea
|
|
79
91
|
|
|
@@ -92,34 +104,46 @@ Stoa runs 5 stages:
|
|
|
92
104
|
After refining, Stoa:
|
|
93
105
|
- Saves the spec as readable markdown files in `.stoa/specs/`
|
|
94
106
|
- Copies Stage 1 to your clipboard automatically
|
|
95
|
-
-
|
|
107
|
+
- Shows an interactive menu:
|
|
96
108
|
|
|
97
109
|
```
|
|
98
110
|
Spec saved to .stoa/specs/personal-finance-tracker/
|
|
99
|
-
Score: 5/5
|
|
111
|
+
Spec Score: 5 / 5
|
|
100
112
|
|
|
101
113
|
→ Stage 1 description copied to clipboard
|
|
102
114
|
Paste into Lovable, Bolt, v0, or any AI tool
|
|
103
115
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
What next?
|
|
117
|
+
[b] Build with Claude Code
|
|
118
|
+
[c] Copy spec to clipboard
|
|
119
|
+
[e] Export as single markdown
|
|
120
|
+
[v] View spec files
|
|
121
|
+
[q] Done
|
|
108
122
|
```
|
|
109
123
|
|
|
124
|
+
- **[b] Build** — launches Claude Code with the spec pre-loaded
|
|
125
|
+
- **[c] Copy** — re-copies the full spec to clipboard (useful if you copied something else)
|
|
126
|
+
- **[e] Export** — writes all 5 stages as a single markdown to `specs/<slug>/spec.md` in your project root (visible, not hidden inside `.stoa/`)
|
|
127
|
+
- **[v] View** — opens the spec directory in Finder
|
|
128
|
+
- **[q] Done** — exits
|
|
129
|
+
|
|
110
130
|
### 4. Build
|
|
111
131
|
|
|
112
|
-
**Option A —
|
|
132
|
+
**Option A — Press [b] after refine:**
|
|
133
|
+
|
|
134
|
+
The fastest path. Press `b` in the post-refine menu and Claude Code starts building immediately.
|
|
135
|
+
|
|
136
|
+
**Option B — Paste into any AI tool:**
|
|
113
137
|
|
|
114
138
|
Open Lovable, Bolt, v0, or any AI coding tool. Press Cmd+V. The spec is already on your clipboard. The AI builds exactly what you specified.
|
|
115
139
|
|
|
116
|
-
**Option
|
|
140
|
+
**Option C — Use Claude Code manually:**
|
|
117
141
|
|
|
118
142
|
```bash
|
|
119
143
|
claude "Read the spec in .stoa/specs/personal-finance-tracker/ and build it. Follow all constraints and subtasks."
|
|
120
144
|
```
|
|
121
145
|
|
|
122
|
-
**Option
|
|
146
|
+
**Option D — Use Cursor with Claude Code extension:**
|
|
123
147
|
|
|
124
148
|
Open the project folder in Cursor. Open Claude Code from the sidebar. Paste the build prompt.
|
|
125
149
|
|
|
@@ -173,12 +197,11 @@ Stage 1 will reference your existing files, components, and design system. The s
|
|
|
173
197
|
|
|
174
198
|
## Changing the Design
|
|
175
199
|
|
|
176
|
-
|
|
200
|
+
Switch preset, edit interactively, or both:
|
|
177
201
|
|
|
178
202
|
```bash
|
|
179
|
-
stoa
|
|
180
|
-
#
|
|
181
|
-
|
|
203
|
+
stoa moodboard preset dark # switch to dark theme
|
|
204
|
+
stoa moodboard edit # tweak individual values
|
|
182
205
|
stoa refine "Redesign the app to match the updated design system"
|
|
183
206
|
```
|
|
184
207
|
|
|
@@ -194,6 +217,7 @@ All files in `.stoa/` are optional. Use what you need, ignore what you don't.
|
|
|
194
217
|
|------|-------------|----------|
|
|
195
218
|
| `moodboard/notes.md` | Design direction: colors, layout, typography | Web apps, UI projects |
|
|
196
219
|
| `moodboard/tokens.json` | Auto-generated machine-readable design values | Generated by `stoa moodboard sync` |
|
|
220
|
+
| `presets/*.json` | Custom saved style presets | Reuse across projects |
|
|
197
221
|
| `context.md` | Dependencies, conventions, brand voice | All projects |
|
|
198
222
|
| `lessons.md` | Past mistakes — auto-grows, prevents repeats | All projects (grows over time) |
|
|
199
223
|
| `guardrails/*.md` | Rules the AI must follow (e.g. "don't delete code") | All projects |
|
|
@@ -241,60 +265,190 @@ Every future refine includes past lessons as failure modes to avoid. Your projec
|
|
|
241
265
|
|
|
242
266
|
## CLI Reference
|
|
243
267
|
|
|
268
|
+
### Setup
|
|
269
|
+
|
|
244
270
|
```bash
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
stoa
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
#
|
|
252
|
-
stoa
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
stoa
|
|
271
|
+
stoa init
|
|
272
|
+
```
|
|
273
|
+
Creates `.stoa/` in the current directory with the Clean style preset, 5 guardrails, and 3 roles. Run this once per project.
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
stoa edit moodboard # open moodboard in VS Code/Cursor
|
|
277
|
+
stoa edit context # open context.md
|
|
278
|
+
stoa edit lessons # open lessons.md
|
|
279
|
+
```
|
|
280
|
+
Opens files in the best available editor. Detection order: Cursor → VS Code → `$EDITOR` → macOS default → nano.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
### Moodboard
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
stoa moodboard
|
|
288
|
+
```
|
|
289
|
+
Shows current moodboard status: active style, color count, image count, and available commands.
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
stoa moodboard preset
|
|
293
|
+
```
|
|
294
|
+
**Interactive.** Browse 4 built-in presets (Clean, Dark, Warm, Bold) + any custom presets with arrow keys. Shows a live preview with colors, typography, and references. Press **Enter** to apply, **q** to cancel.
|
|
295
|
+
|
|
296
|
+
You can also apply directly without the picker:
|
|
297
|
+
```bash
|
|
298
|
+
stoa moodboard preset dark
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
stoa moodboard edit
|
|
303
|
+
```
|
|
304
|
+
**Interactive.** Walks through each field one by one: Design Direction → Colors (each individually) → Typography → Layout → Component Style → References. Press **Enter** to keep current value. Type a new value to replace. Type **q** to quit at any point.
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
stoa moodboard describe
|
|
308
|
+
```
|
|
309
|
+
**Interactive.** Opens the `.stoa/moodboard/` folder in Finder so you can drag screenshots in. Press **Enter** when ready. If you have an API key, Stoa analyzes the images with AI and writes the design system automatically. Without an API key, it prints a prompt you can paste into any Claude chat alongside your screenshot.
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
stoa moodboard sync
|
|
313
|
+
```
|
|
314
|
+
Regenerates `tokens.json` from `notes.md`. Usually happens automatically after preset or edit, but run this if you edited `notes.md` by hand.
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
stoa moodboard save-preset my-brand
|
|
318
|
+
```
|
|
319
|
+
Saves the current moodboard as `.stoa/presets/my-brand.json`. Reusable across projects — shows up in `stoa moodboard preset` picker.
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
### Refine
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
stoa refine "Build a waitlist page with email signup and referral system"
|
|
327
|
+
```
|
|
328
|
+
Runs the 5-stage pipeline. After completion, shows an interactive menu:
|
|
329
|
+
|
|
330
|
+
| Key | Action | Notes |
|
|
331
|
+
|-----|--------|-------|
|
|
332
|
+
| **b** | Build with Claude Code | Launches Claude Code with the spec |
|
|
333
|
+
| **c** | Copy spec to clipboard | Re-copy (Stage 1 is auto-copied on finish) |
|
|
334
|
+
| **e** | Export as markdown | Writes to `specs/<slug>/spec.md` in project root |
|
|
335
|
+
| **v** | View spec files | Opens spec directory in Finder |
|
|
336
|
+
| **q** | Done | Exits |
|
|
337
|
+
|
|
338
|
+
**Options:**
|
|
339
|
+
```bash
|
|
340
|
+
stoa refine "task" --mode api # Force Anthropic API (needs key)
|
|
341
|
+
stoa refine "task" --mode claude-code # Force Claude Code CLI
|
|
342
|
+
stoa refine "task" --mode clipboard # Get prompts without AI calls (free)
|
|
343
|
+
stoa refine "task" --role planner # Use a specific role
|
|
344
|
+
stoa refine "task" --stages clarify,structure # Run specific stages only
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
### Specs
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
stoa specs list # List all saved specs with dates and stage count
|
|
353
|
+
stoa specs show <name> # Print a spec's contents to terminal
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Specs are saved in `.stoa/specs/<slug>/` with one markdown file per stage. The `[e]` export writes a combined `spec.md` to the visible `specs/` folder in your project root.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
### Scenarios
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
stoa scenarios list # List scenarios for the latest spec
|
|
364
|
+
stoa scenarios list <name> # List scenarios for a specific spec
|
|
267
365
|
stoa scenarios run # Walk through scenarios interactively
|
|
268
366
|
stoa scenarios run <name> # Run scenarios for a specific spec
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**Interactive.** Each scenario shows GIVEN (what to set up) and EXPECTED (what to check). Press **y** for pass, **n** for fail, **s** to skip. Shows a summary at the end.
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
### Review
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
stoa review # Review the latest spec
|
|
377
|
+
stoa review <name> # Review a specific spec
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
**Interactive.** Opens each stage for review. Accept, edit, or skip. After editing, optionally re-runs affected pipeline stages.
|
|
381
|
+
|
|
382
|
+
---
|
|
269
383
|
|
|
270
|
-
|
|
271
|
-
stoa guardrails list # List active guardrails
|
|
272
|
-
stoa guardrails show <name> # View a guardrail
|
|
273
|
-
stoa roles list # List available roles
|
|
274
|
-
stoa roles show <name> # View a role
|
|
384
|
+
### Build & Verify
|
|
275
385
|
|
|
276
|
-
|
|
277
|
-
stoa
|
|
386
|
+
```bash
|
|
387
|
+
stoa build # Build the latest spec with Claude Code
|
|
388
|
+
stoa build <name> # Build a specific spec
|
|
389
|
+
stoa verify # Run blind test verification
|
|
390
|
+
stoa verify <name> # Verify a specific spec
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Build gives you a choice: build all at once or subtask by subtask. Verify runs the scenarios interactively after the build.
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
### Guardrails & Roles
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
stoa guardrails list # List active guardrails
|
|
401
|
+
stoa guardrails show <name> # View a guardrail's content
|
|
402
|
+
stoa guardrails add <name> # Add a new guardrail
|
|
403
|
+
stoa guardrails remove <name> # Remove a guardrail
|
|
404
|
+
stoa roles list # List available roles
|
|
405
|
+
stoa roles show <name> # View a role's content
|
|
406
|
+
stoa roles add <name> # Add a new role
|
|
407
|
+
stoa roles remove <name> # Remove a role
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Guardrails are rules injected into every refine (e.g. "don't delete existing code"). Roles are AI personas used via `--role` flag.
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
### Config
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
stoa config # View current settings
|
|
278
418
|
stoa config set apiKey <key> # Set Anthropic API key
|
|
279
|
-
stoa config set model <model> # Set model (default: claude-sonnet-4-
|
|
280
|
-
stoa config set mode <mode> # Set mode: api, claude-code, clipboard
|
|
419
|
+
stoa config set model <model> # Set model (default: claude-sonnet-4-6)
|
|
420
|
+
stoa config set mode <mode> # Set default mode: api, claude-code, clipboard
|
|
281
421
|
```
|
|
282
422
|
|
|
283
423
|
---
|
|
284
424
|
|
|
285
425
|
## Execution Modes
|
|
286
426
|
|
|
287
|
-
|
|
427
|
+
| Mode | How it works | You need | Cost |
|
|
428
|
+
|------|-------------|----------|------|
|
|
429
|
+
| `api` | Direct Anthropic API call | API key (`stoa config set apiKey`) | ~$0.05/refine |
|
|
430
|
+
| `claude-code` | Pipes to Claude Code CLI | Claude Code installed + subscription | Included in subscription |
|
|
431
|
+
| `clipboard` | Returns prompts — no AI calls | Nothing | Free |
|
|
288
432
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
433
|
+
Stoa auto-detects: API key → `api`, Claude Code in PATH → `claude-code`, otherwise → `clipboard`.
|
|
434
|
+
|
|
435
|
+
In clipboard mode, Stoa prints each stage's prompt. Paste into any AI chat (Claude, ChatGPT, Cursor) and copy the response back. Same pipeline, just manual.
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## Keyboard Shortcuts
|
|
294
440
|
|
|
295
|
-
|
|
441
|
+
All interactive commands support these:
|
|
296
442
|
|
|
297
|
-
|
|
443
|
+
| Context | Key | Action |
|
|
444
|
+
|---------|-----|--------|
|
|
445
|
+
| Any prompt | `q` / `quit` / `exit` | Cancel and go back |
|
|
446
|
+
| Arrow key menus | `↑` `↓` or `k` `j` | Navigate |
|
|
447
|
+
| Arrow key menus | `Enter` | Select |
|
|
448
|
+
| Arrow key menus | `q` | Cancel |
|
|
449
|
+
| Post-refine menu | `b` `c` `e` `v` `q` | See table above |
|
|
450
|
+
| Scenario runner | `y` `n` `s` | Pass / Fail / Skip |
|
|
451
|
+
| Ctrl+C | Always | Force quit |
|
|
298
452
|
|
|
299
453
|
---
|
|
300
454
|
|
|
@@ -313,18 +467,24 @@ Add Stoa as an MCP server in Cursor. Create or edit `.cursor/mcp.json` in your p
|
|
|
313
467
|
}
|
|
314
468
|
```
|
|
315
469
|
|
|
316
|
-
Find the global path
|
|
317
|
-
|
|
470
|
+
Find the global path:
|
|
318
471
|
```bash
|
|
319
|
-
npm root -g
|
|
472
|
+
echo "$(npm root -g)/stoa-mcp/dist/index.js"
|
|
320
473
|
```
|
|
321
474
|
|
|
322
475
|
Then in Cursor's Agent chat:
|
|
323
|
-
|
|
324
476
|
```
|
|
325
477
|
Use the refine_task tool with title: "My App" and description: "description of what I want"
|
|
326
478
|
```
|
|
327
479
|
|
|
480
|
+
## Use with Claude Code
|
|
481
|
+
|
|
482
|
+
Stoa works directly with Claude Code. After refining:
|
|
483
|
+
|
|
484
|
+
1. Press `[b]` in the post-refine menu — launches Claude Code automatically
|
|
485
|
+
2. Or copy the spec and paste it: `claude "Read the spec in .stoa/specs/<name>/ and build it"`
|
|
486
|
+
3. Or use `stoa build` for the full guided experience
|
|
487
|
+
|
|
328
488
|
---
|
|
329
489
|
|
|
330
490
|
## How It Works
|
|
@@ -375,6 +535,12 @@ The spec references existing files by name and says "add to" instead of "rebuild
|
|
|
375
535
|
- `Fixer` — fixes bugs from failure context
|
|
376
536
|
- `Planner` — breaks down large tasks
|
|
377
537
|
|
|
538
|
+
**4 Style Presets:**
|
|
539
|
+
- `Clean` — white, minimal, Linear/Vercel (applied by default)
|
|
540
|
+
- `Dark` — dark background, violet accents, GitHub/Raycast
|
|
541
|
+
- `Warm` — cream tones, amber accents, Cal.com/Stripe
|
|
542
|
+
- `Bold` — high contrast, sharp corners, brutalist
|
|
543
|
+
|
|
378
544
|
---
|
|
379
545
|
|
|
380
546
|
## The Stoa Desktop App
|
|
@@ -388,7 +554,7 @@ The CLI is the free version. The full loop lives in the Stoa desktop app:
|
|
|
388
554
|
- Task hierarchy (parent → subtask → fix task)
|
|
389
555
|
- Dashboard with spec scores across all tasks
|
|
390
556
|
|
|
391
|
-
**Same pipeline, full GUI.** Coming soon at [stoafactory.com](https://stoafactory.
|
|
557
|
+
**Same pipeline, full GUI.** Coming soon at [stoafactory.com](https://stoafactory.dev).
|
|
392
558
|
|
|
393
559
|
---
|
|
394
560
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runMoodboardEdit(projectDir: string): Promise<void>;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { createInterface } from "node:readline";
|
|
5
|
+
import { syncMoodboard } from "../storage/moodboard-sync.js";
|
|
6
|
+
function writeln(text = "") {
|
|
7
|
+
process.stdout.write(text + "\n");
|
|
8
|
+
}
|
|
9
|
+
const HTML_COMMENT_RE = /<!--[\s\S]*?-->/g;
|
|
10
|
+
function parseMoodboard(notesPath) {
|
|
11
|
+
const defaults = {
|
|
12
|
+
designDirection: "",
|
|
13
|
+
colors: {},
|
|
14
|
+
layout: "",
|
|
15
|
+
typography: "",
|
|
16
|
+
componentStyle: "",
|
|
17
|
+
references: "",
|
|
18
|
+
};
|
|
19
|
+
if (!existsSync(notesPath))
|
|
20
|
+
return defaults;
|
|
21
|
+
const raw = readFileSync(notesPath, "utf-8");
|
|
22
|
+
const sections = {};
|
|
23
|
+
const parts = raw.split(/^# /m);
|
|
24
|
+
for (const part of parts) {
|
|
25
|
+
const trimmed = part.trim();
|
|
26
|
+
if (!trimmed)
|
|
27
|
+
continue;
|
|
28
|
+
const newlineIdx = trimmed.indexOf("\n");
|
|
29
|
+
if (newlineIdx === -1)
|
|
30
|
+
continue;
|
|
31
|
+
const heading = trimmed.slice(0, newlineIdx).trim();
|
|
32
|
+
const body = trimmed.slice(newlineIdx + 1).replace(HTML_COMMENT_RE, "").trim();
|
|
33
|
+
if (body)
|
|
34
|
+
sections[heading] = body;
|
|
35
|
+
}
|
|
36
|
+
// Parse colors
|
|
37
|
+
const colors = {};
|
|
38
|
+
if (sections["Colors"]) {
|
|
39
|
+
const lines = sections["Colors"].split("\n");
|
|
40
|
+
for (const line of lines) {
|
|
41
|
+
const match = line.match(/^([A-Za-z][A-Za-z0-9 /]*?)\s*:\s*(#[0-9A-Fa-f]{6})\b/);
|
|
42
|
+
if (match) {
|
|
43
|
+
colors[match[1].trim()] = match[2];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
designDirection: sections["Design Direction"] ?? "",
|
|
49
|
+
colors,
|
|
50
|
+
layout: sections["Layout"] ?? "",
|
|
51
|
+
typography: sections["Typography"] ?? "",
|
|
52
|
+
componentStyle: sections["Component Style"] ?? "",
|
|
53
|
+
references: sections["References"] ?? "",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const QUIT_COMMANDS = new Set(["q", "quit", "exit"]);
|
|
57
|
+
function prompt(rl, question) {
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
rl.question(question, (answer) => {
|
|
60
|
+
if (QUIT_COMMANDS.has(answer.trim().toLowerCase())) {
|
|
61
|
+
resolve(null);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
resolve(answer);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
export async function runMoodboardEdit(projectDir) {
|
|
70
|
+
const notesPath = join(projectDir, ".stoa", "moodboard", "notes.md");
|
|
71
|
+
const current = parseMoodboard(notesPath);
|
|
72
|
+
const rl = createInterface({
|
|
73
|
+
input: process.stdin,
|
|
74
|
+
output: process.stdout,
|
|
75
|
+
});
|
|
76
|
+
writeln();
|
|
77
|
+
writeln(chalk.bold("Edit Moodboard"));
|
|
78
|
+
writeln(chalk.dim("Enter to keep current value. Type new value to replace. Type 'q' to quit."));
|
|
79
|
+
writeln();
|
|
80
|
+
let changed = false;
|
|
81
|
+
// Helper to handle a text field
|
|
82
|
+
async function editField(label, currentValue) {
|
|
83
|
+
writeln(chalk.bold(label));
|
|
84
|
+
if (currentValue) {
|
|
85
|
+
writeln(chalk.dim(` Current: ${currentValue}`));
|
|
86
|
+
}
|
|
87
|
+
const answer = await prompt(rl, chalk.cyan(" New: "));
|
|
88
|
+
if (answer === null)
|
|
89
|
+
return "quit";
|
|
90
|
+
if (answer.trim()) {
|
|
91
|
+
writeln(chalk.green(" ✓ Updated"));
|
|
92
|
+
return answer.trim();
|
|
93
|
+
}
|
|
94
|
+
writeln(chalk.dim(" ✓ Kept"));
|
|
95
|
+
return currentValue;
|
|
96
|
+
}
|
|
97
|
+
// Design Direction
|
|
98
|
+
const dir = await editField("Design Direction", current.designDirection);
|
|
99
|
+
if (dir === "quit") {
|
|
100
|
+
rl.close();
|
|
101
|
+
writeln(chalk.dim("\nCancelled."));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (dir !== current.designDirection) {
|
|
105
|
+
current.designDirection = dir;
|
|
106
|
+
changed = true;
|
|
107
|
+
}
|
|
108
|
+
writeln();
|
|
109
|
+
// Colors
|
|
110
|
+
writeln(chalk.bold("Colors"));
|
|
111
|
+
const colorKeys = Object.keys(current.colors);
|
|
112
|
+
if (colorKeys.length > 0) {
|
|
113
|
+
for (const key of colorKeys) {
|
|
114
|
+
const hex = current.colors[key];
|
|
115
|
+
const swatch = chalk.hex(hex)("██");
|
|
116
|
+
writeln(` ${chalk.dim(key)}: ${swatch} ${chalk.dim(hex)}`);
|
|
117
|
+
const newColor = await prompt(rl, chalk.cyan(` New ${key}: `));
|
|
118
|
+
if (newColor === null) {
|
|
119
|
+
rl.close();
|
|
120
|
+
writeln(chalk.dim("\nCancelled."));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (newColor.trim()) {
|
|
124
|
+
if (/^#[0-9A-Fa-f]{6}$/.test(newColor.trim())) {
|
|
125
|
+
current.colors[key] = newColor.trim();
|
|
126
|
+
changed = true;
|
|
127
|
+
writeln(chalk.green(" ✓ Updated"));
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
writeln(chalk.yellow(" ✗ Invalid hex (use #RRGGBB). Kept original."));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
writeln(chalk.dim(" ✓ Kept"));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
writeln(chalk.dim(" No colors defined. Add them in format: Label: #HEXVAL"));
|
|
140
|
+
}
|
|
141
|
+
// Add new color?
|
|
142
|
+
const addColor = await prompt(rl, chalk.cyan(" Add new color? (name: #hex or Enter to skip): "));
|
|
143
|
+
if (addColor === null) {
|
|
144
|
+
rl.close();
|
|
145
|
+
writeln(chalk.dim("\nCancelled."));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (addColor.trim()) {
|
|
149
|
+
const match = addColor.match(/^([A-Za-z][A-Za-z0-9 ]*?)\s*:\s*(#[0-9A-Fa-f]{6})$/);
|
|
150
|
+
if (match) {
|
|
151
|
+
current.colors[match[1].trim()] = match[2];
|
|
152
|
+
changed = true;
|
|
153
|
+
writeln(chalk.green(` ✓ Added ${match[1].trim()}`));
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
writeln(chalk.yellow(" ✗ Format: Name: #HEXVAL"));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
writeln();
|
|
160
|
+
// Typography
|
|
161
|
+
const typo = await editField("Typography", current.typography);
|
|
162
|
+
if (typo === "quit") {
|
|
163
|
+
rl.close();
|
|
164
|
+
writeln(chalk.dim("\nCancelled."));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (typo !== current.typography) {
|
|
168
|
+
current.typography = typo;
|
|
169
|
+
changed = true;
|
|
170
|
+
}
|
|
171
|
+
writeln();
|
|
172
|
+
// Layout
|
|
173
|
+
const layout = await editField("Layout", current.layout);
|
|
174
|
+
if (layout === "quit") {
|
|
175
|
+
rl.close();
|
|
176
|
+
writeln(chalk.dim("\nCancelled."));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (layout !== current.layout) {
|
|
180
|
+
current.layout = layout;
|
|
181
|
+
changed = true;
|
|
182
|
+
}
|
|
183
|
+
writeln();
|
|
184
|
+
// Component Style
|
|
185
|
+
const style = await editField("Component Style", current.componentStyle);
|
|
186
|
+
if (style === "quit") {
|
|
187
|
+
rl.close();
|
|
188
|
+
writeln(chalk.dim("\nCancelled."));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (style !== current.componentStyle) {
|
|
192
|
+
current.componentStyle = style;
|
|
193
|
+
changed = true;
|
|
194
|
+
}
|
|
195
|
+
writeln();
|
|
196
|
+
// References
|
|
197
|
+
const refs = await editField("References", current.references);
|
|
198
|
+
if (refs === "quit") {
|
|
199
|
+
rl.close();
|
|
200
|
+
writeln(chalk.dim("\nCancelled."));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (refs !== current.references) {
|
|
204
|
+
current.references = refs;
|
|
205
|
+
changed = true;
|
|
206
|
+
}
|
|
207
|
+
rl.close();
|
|
208
|
+
writeln();
|
|
209
|
+
if (!changed) {
|
|
210
|
+
writeln(chalk.dim("No changes made."));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
// Write notes.md
|
|
214
|
+
const colorLines = Object.entries(current.colors)
|
|
215
|
+
.map(([key, value]) => `${key}: ${value}`)
|
|
216
|
+
.join("\n");
|
|
217
|
+
const markdown = `# Design Direction
|
|
218
|
+
${current.designDirection || ""}
|
|
219
|
+
|
|
220
|
+
# Colors
|
|
221
|
+
${colorLines || ""}
|
|
222
|
+
|
|
223
|
+
# Layout
|
|
224
|
+
${current.layout || ""}
|
|
225
|
+
|
|
226
|
+
# Typography
|
|
227
|
+
${current.typography || ""}
|
|
228
|
+
|
|
229
|
+
# Component Style
|
|
230
|
+
${current.componentStyle || ""}
|
|
231
|
+
|
|
232
|
+
# References
|
|
233
|
+
${current.references || ""}
|
|
234
|
+
`;
|
|
235
|
+
writeFileSync(notesPath, markdown, "utf-8");
|
|
236
|
+
writeln(chalk.green("✓ Saved to .stoa/moodboard/notes.md"));
|
|
237
|
+
// Auto-sync tokens
|
|
238
|
+
try {
|
|
239
|
+
syncMoodboard(projectDir);
|
|
240
|
+
writeln(chalk.green("✓ Synced tokens.json"));
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
// Non-critical
|
|
244
|
+
}
|
|
245
|
+
}
|