procedure-cli 1.0.2 → 1.0.4
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 +3 -2
- package/config/stacks/go-bubbletea.json +2 -0
- package/config/stacks/ink-tui.json +16 -0
- package/dist/lib/template.d.ts +6 -1
- package/dist/lib/template.js +20 -2
- package/dist/lib/template.js.map +1 -1
- package/dist/steps/generation.js +7 -3
- package/dist/steps/generation.js.map +1 -1
- package/dist/steps/product-context.js +6 -1
- package/dist/steps/product-context.js.map +1 -1
- package/dist/steps/stack-style.js +31 -1
- package/dist/steps/stack-style.js.map +1 -1
- package/package.json +1 -1
- package/templates/AGENTS.md.hbs +4 -0
- package/templates/CLAUDE.md.hbs +50 -0
- package/templates/README.md.hbs +10 -0
- package/templates/ink-tui/INK-TUI-TECH-SPECS.md +375 -0
- package/templates/ink-tui/INK-TUI-UI-UX-GUIDELINE.md +1070 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# Technical Specifications
|
|
2
|
+
|
|
3
|
+
Technical stack, dependencies, configuration, and architecture for building CLI applications with React + Ink.js.
|
|
4
|
+
|
|
5
|
+
Reference source: [Procedure CLI](https://github.com/ducban/procedure-cli.git)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Dev Stack Summary
|
|
10
|
+
|
|
11
|
+
| Layer | Technology | Version |
|
|
12
|
+
|-------|-----------|---------|
|
|
13
|
+
| **Runtime** | Node.js | 22+ (ES2022 target) |
|
|
14
|
+
| **Language** | TypeScript | ^5.8 (strict mode) |
|
|
15
|
+
| **UI Framework** | React + Ink.js | React ^19.1 + Ink ^6.0 |
|
|
16
|
+
| **UI Components** | @inkjs/ui | ^2.0 |
|
|
17
|
+
| **Database** | SQLite (better-sqlite3) | ^11.8 |
|
|
18
|
+
| **ID Generation** | nanoid | ^5.1 |
|
|
19
|
+
| **Module System** | ESM (`"type": "module"`) | Node16 resolution |
|
|
20
|
+
| **Build** | tsc (no bundler) | Native ES modules |
|
|
21
|
+
| **Dev Server** | tsx (watch mode) | ^4.19 |
|
|
22
|
+
| **Linting** | ESLint + @typescript-eslint | ^8.x |
|
|
23
|
+
| **Testing** | Node.js built-in test runner | `node --test` |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Core Dependencies
|
|
28
|
+
|
|
29
|
+
### Production
|
|
30
|
+
|
|
31
|
+
| Package | Version | Purpose |
|
|
32
|
+
|---------|---------|---------|
|
|
33
|
+
| `react` | ^19.1.0 | Component model for UI rendering |
|
|
34
|
+
| `ink` | ^6.0.0 | Terminal UI rendering engine — renders React components as terminal output |
|
|
35
|
+
| `@inkjs/ui` | ^2.0.0 | Pre-built Ink components (TextInput, Spinner, etc.) |
|
|
36
|
+
| `ink-spinner` | ^5.0.0 | Loading spinner animations |
|
|
37
|
+
| `better-sqlite3` | ^11.8.1 | Synchronous SQLite database driver (no async overhead) |
|
|
38
|
+
| `nanoid` | ^5.1.5 | Compact, URL-safe unique ID generation |
|
|
39
|
+
|
|
40
|
+
### Development
|
|
41
|
+
|
|
42
|
+
| Package | Version | Purpose |
|
|
43
|
+
|---------|---------|---------|
|
|
44
|
+
| `typescript` | ^5.8.3 | TypeScript compiler |
|
|
45
|
+
| `tsx` | ^4.19.4 | TypeScript execution with hot-reload for dev |
|
|
46
|
+
| `@types/node` | ^22.15.0 | Node.js type definitions |
|
|
47
|
+
| `@types/react` | ^19.1.2 | React type definitions |
|
|
48
|
+
| `@types/better-sqlite3` | ^7.6.13 | better-sqlite3 type definitions |
|
|
49
|
+
| `eslint` | ^8.57.1 | JavaScript/TypeScript linter |
|
|
50
|
+
| `@typescript-eslint/parser` | ^8.56.1 | TypeScript parser for ESLint |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## TypeScript Configuration
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"compilerOptions": {
|
|
59
|
+
"target": "ES2022",
|
|
60
|
+
"module": "Node16",
|
|
61
|
+
"moduleResolution": "Node16",
|
|
62
|
+
"strict": true,
|
|
63
|
+
"esModuleInterop": true,
|
|
64
|
+
"skipLibCheck": true,
|
|
65
|
+
"outDir": "dist",
|
|
66
|
+
"rootDir": "src",
|
|
67
|
+
"jsx": "react-jsx",
|
|
68
|
+
"declaration": true,
|
|
69
|
+
"sourceMap": true
|
|
70
|
+
},
|
|
71
|
+
"include": ["src"],
|
|
72
|
+
"exclude": ["node_modules", "dist"]
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Key settings:**
|
|
77
|
+
- `"jsx": "react-jsx"` — uses the default React JSX runtime (no manual `import React` needed, though explicitly imported for clarity)
|
|
78
|
+
- `"module": "Node16"` — ESM with `.js` extensions in imports
|
|
79
|
+
- `"strict": true` — full strict mode (noImplicitAny, strictNullChecks, etc.)
|
|
80
|
+
- `"target": "ES2022"` — modern JS features (top-level await, Array.at, etc.)
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Package.json Template
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"name": "your-cli-app",
|
|
89
|
+
"version": "0.1.0",
|
|
90
|
+
"description": "Your CLI app description",
|
|
91
|
+
"type": "module",
|
|
92
|
+
"files": ["dist", "config"],
|
|
93
|
+
"bin": {
|
|
94
|
+
"your-cli": "./dist/cli.js"
|
|
95
|
+
},
|
|
96
|
+
"scripts": {
|
|
97
|
+
"dev": "tsx src/cli.tsx",
|
|
98
|
+
"dev:watch": "tsx watch src/cli.tsx",
|
|
99
|
+
"build": "tsc",
|
|
100
|
+
"typecheck": "tsc --noEmit",
|
|
101
|
+
"test": "node --import tsx --test \"src/**/*.test.ts\"",
|
|
102
|
+
"lint": "eslint src/ --ext .ts,.tsx",
|
|
103
|
+
"start": "tsx src/cli.tsx"
|
|
104
|
+
},
|
|
105
|
+
"dependencies": {
|
|
106
|
+
"@inkjs/ui": "^2.0.0",
|
|
107
|
+
"better-sqlite3": "^11.8.1",
|
|
108
|
+
"ink": "^6.0.0",
|
|
109
|
+
"ink-spinner": "^5.0.0",
|
|
110
|
+
"nanoid": "^5.1.5",
|
|
111
|
+
"react": "^19.1.0"
|
|
112
|
+
},
|
|
113
|
+
"devDependencies": {
|
|
114
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
115
|
+
"@types/node": "^22.15.0",
|
|
116
|
+
"@types/react": "^19.1.2",
|
|
117
|
+
"@typescript-eslint/parser": "^8.56.1",
|
|
118
|
+
"eslint": "^8.57.1",
|
|
119
|
+
"tsx": "^4.19.4",
|
|
120
|
+
"typescript": "^5.8.3"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Build System
|
|
128
|
+
|
|
129
|
+
| Command | Purpose |
|
|
130
|
+
|---------|---------|
|
|
131
|
+
| `npm run dev` | Run in development mode (tsx, single run) |
|
|
132
|
+
| `npm run dev:watch` | Run with hot-reload (tsx watch) |
|
|
133
|
+
| `npm run build` | Compile TypeScript → `dist/` (tsc) |
|
|
134
|
+
| `npm run typecheck` | Type-check without emitting files (fast) |
|
|
135
|
+
| `npm run test` | Run tests with Node.js built-in test runner |
|
|
136
|
+
| `npm run lint` | Lint all `.ts` and `.tsx` files |
|
|
137
|
+
| `npm run start` | Run the app via tsx |
|
|
138
|
+
|
|
139
|
+
**No bundler required** — uses native ES modules with Node.js. TypeScript compiles to `.js` files in `dist/`, imports use `.js` extensions throughout.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Project Structure
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
project-root/
|
|
147
|
+
├── src/
|
|
148
|
+
│ ├── cli.tsx # Entry point — render(<App />) via Ink
|
|
149
|
+
│ ├── app.tsx # Main app shell — menu-driven screen router
|
|
150
|
+
│ ├── components/
|
|
151
|
+
│ │ ├── banner.tsx # ASCII art header with brand identity
|
|
152
|
+
│ │ ├── timeline.tsx # Vertical wizard with step indicators
|
|
153
|
+
│ │ ├── step-indicator.tsx # Animated step status (◆/◇/○)
|
|
154
|
+
│ │ ├── gutter-line.tsx # │ prefix component for content lines
|
|
155
|
+
│ │ └── guttered-select.tsx # Select + MultiSelect with gutter integration
|
|
156
|
+
│ ├── screens/
|
|
157
|
+
│ │ ├── [feature].tsx # Feature screens (one file per screen)
|
|
158
|
+
│ │ └── steps/
|
|
159
|
+
│ │ └── [step].tsx # Wizard step components (self-contained)
|
|
160
|
+
│ └── lib/
|
|
161
|
+
│ ├── theme.ts # Color tokens — exports `C` object
|
|
162
|
+
│ ├── themes/ # Theme definitions (future: multiple themes)
|
|
163
|
+
│ │ ├── index.ts # Theme registry and loader
|
|
164
|
+
│ │ ├── catppuccin-mocha.ts
|
|
165
|
+
│ │ ├── catppuccin-latte.ts
|
|
166
|
+
│ │ ├── tokyonight-dark.ts
|
|
167
|
+
│ │ └── tokyonight-light.ts
|
|
168
|
+
│ ├── types.ts # All TypeScript types and interfaces
|
|
169
|
+
│ ├── config.ts # JSON config loader with validation
|
|
170
|
+
│ ├── db.ts # SQLite CRUD (better-sqlite3, nanoid IDs)
|
|
171
|
+
│ ├── csv.ts # CSV export utilities
|
|
172
|
+
│ ├── validation.ts # Input validation helpers
|
|
173
|
+
│ └── input/
|
|
174
|
+
│ └── normalize-keys.ts # ANSI input normalization for deterministic key handling
|
|
175
|
+
├── config/ # JSON configuration files
|
|
176
|
+
├── data/ # SQLite database, backups, exports
|
|
177
|
+
├── docs/
|
|
178
|
+
│ ├── B3AWESOME-CLI-APP-UI-UX-GUIDELINE.md # UI/UX design system
|
|
179
|
+
│ ├── TECH-SPECS.md # This file
|
|
180
|
+
│ ├── PRD.md # Product requirements
|
|
181
|
+
│ └── USER-STORIES.md # User stories with acceptance criteria
|
|
182
|
+
└── dist/ # Compiled output (git-ignored)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Styling System
|
|
188
|
+
|
|
189
|
+
### Color Token Architecture
|
|
190
|
+
|
|
191
|
+
All colors are accessed through semantic tokens in `src/lib/theme.ts`:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
export const C = {
|
|
195
|
+
mauve: '#...', // brand, active focus, cursor highlight
|
|
196
|
+
green: '#...', // success, completed steps, confirmed values
|
|
197
|
+
yellow: '#...', // in-progress input highlight
|
|
198
|
+
sapphire: '#...', // headings, section headers
|
|
199
|
+
teal: '#...', // secondary accent
|
|
200
|
+
red: '#...', // errors, destructive warnings
|
|
201
|
+
peach: '#...', // warnings
|
|
202
|
+
overlay1: '#...', // gutter lines, dim text, hints, instructions
|
|
203
|
+
overlay0: '#...', // pending steps, very dim elements
|
|
204
|
+
inputing: '#...', // currently typing value
|
|
205
|
+
inputed: '#...', // confirmed/completed value
|
|
206
|
+
label: '#...', // general text labels, content
|
|
207
|
+
} as const;
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Components import `C` — never hardcode hex values.** This enables theme switching.
|
|
211
|
+
|
|
212
|
+
### Available Themes
|
|
213
|
+
|
|
214
|
+
| Theme | Type | Token Source |
|
|
215
|
+
|-------|------|-------------|
|
|
216
|
+
| Catppuccin Mocha | Dark | [catppuccin.com](https://catppuccin.com) |
|
|
217
|
+
| Catppuccin Latte | Light | [catppuccin.com](https://catppuccin.com) |
|
|
218
|
+
| Tokyo Night | Dark | [github.com/enkia/tokyo-night-vscode-theme](https://github.com/enkia/tokyo-night-vscode-theme) |
|
|
219
|
+
| Tokyo Night Light | Light | Same source |
|
|
220
|
+
|
|
221
|
+
See `B3AWESOME-CLI-APP-UI-UX-GUIDELINE.md` §2 for complete hex values for all themes.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## UI Framework: React + Ink.js
|
|
226
|
+
|
|
227
|
+
### How It Works
|
|
228
|
+
|
|
229
|
+
Ink.js uses React's component model to render terminal output:
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
// cli.tsx — Entry point
|
|
233
|
+
import { render } from 'ink';
|
|
234
|
+
import App from './app.js';
|
|
235
|
+
|
|
236
|
+
render(<App />);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
- React components render to **terminal characters** instead of DOM elements
|
|
240
|
+
- `<Box>` = flex container (like `<div>` with flexbox)
|
|
241
|
+
- `<Text>` = styled text (like `<span>` with color/bold)
|
|
242
|
+
- All Ink components support `color`, `bold`, `dimColor`, etc.
|
|
243
|
+
- Layout uses `flexDirection="column"` (vertical) or `flexDirection="row"` (horizontal)
|
|
244
|
+
|
|
245
|
+
### Key Ink.js APIs
|
|
246
|
+
|
|
247
|
+
| API | Purpose |
|
|
248
|
+
|-----|---------|
|
|
249
|
+
| `<Box>` | Flex container — `flexDirection`, `padding`, `margin`, `gap` |
|
|
250
|
+
| `<Text>` | Styled text — `color`, `bold`, `dimColor`, `wrap` |
|
|
251
|
+
| `useInput(handler)` | Keyboard input listener — receives `(input, key)` |
|
|
252
|
+
| `useApp()` | App lifecycle — `exit()` to quit |
|
|
253
|
+
| `render(<Component />)` | Mount React tree to terminal |
|
|
254
|
+
|
|
255
|
+
### Key @inkjs/ui APIs
|
|
256
|
+
|
|
257
|
+
| Component | Purpose |
|
|
258
|
+
|-----------|---------|
|
|
259
|
+
| `<TextInput>` | Text input field — `placeholder`, `defaultValue`, `onSubmit` |
|
|
260
|
+
| `<Spinner>` | Loading spinner animation |
|
|
261
|
+
| `<Select>` | Selection list (we use custom GutteredSelect instead) |
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Database Pattern
|
|
266
|
+
|
|
267
|
+
### SQLite with better-sqlite3
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import Database from 'better-sqlite3';
|
|
271
|
+
|
|
272
|
+
const db = new Database('data/assessments.db');
|
|
273
|
+
db.pragma('journal_mode = WAL'); // Write-ahead logging
|
|
274
|
+
db.pragma('foreign_keys = ON'); // Enforce FK constraints
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Key patterns:**
|
|
278
|
+
- **Synchronous API** — no async/await needed, simpler code
|
|
279
|
+
- **nanoid for IDs** — `nanoid()` generates compact unique IDs
|
|
280
|
+
- **Prepared statements** — `db.prepare(sql).run(params)` for all queries
|
|
281
|
+
- **Transactions** — `db.transaction(() => { ... })()` for multi-step operations
|
|
282
|
+
- **Migrations** — detect columns via `PRAGMA table_info(tablename)`, alter as needed
|
|
283
|
+
- **Result pattern** — all DB functions return `{ ok: true, data } | { ok: false, error: string }`
|
|
284
|
+
|
|
285
|
+
### Migration Pattern
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
function migrateIfNeeded() {
|
|
289
|
+
const columns = db.pragma('table_info(tablename)') as { name: string }[];
|
|
290
|
+
const hasNewColumn = columns.some((c) => c.name === 'new_column');
|
|
291
|
+
if (!hasNewColumn) {
|
|
292
|
+
db.exec('ALTER TABLE tablename ADD COLUMN new_column TEXT');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Input Handling
|
|
300
|
+
|
|
301
|
+
### ANSI Input Normalization
|
|
302
|
+
|
|
303
|
+
Terminal input can arrive as batched ANSI sequences in a single stdin chunk. The normalizer handles:
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
// CSI arrow sequences: \x1b[A (up), \x1b[B (down)
|
|
307
|
+
// SS3 arrow sequences: \x1bOA (up), \x1bOB (down)
|
|
308
|
+
// Enter/Return: \r, \n, \r\n
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
This prevents the "ghost selection" bug where rapid arrow key presses get misinterpreted.
|
|
312
|
+
|
|
313
|
+
### Keyboard Shortcut Reference
|
|
314
|
+
|
|
315
|
+
| Key | Action |
|
|
316
|
+
|-----|--------|
|
|
317
|
+
| `↑` / `↓` | Move selection in lists |
|
|
318
|
+
| `Enter` | Confirm/submit |
|
|
319
|
+
| `Space` | Toggle in multi-select |
|
|
320
|
+
| `Esc` | Return to main menu |
|
|
321
|
+
| `Shift+Tab` | Go back (wizard steps, form fields) |
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Code Style
|
|
326
|
+
|
|
327
|
+
| Rule | Convention |
|
|
328
|
+
|------|-----------|
|
|
329
|
+
| **Variables** | camelCase |
|
|
330
|
+
| **Types/Interfaces** | PascalCase |
|
|
331
|
+
| **File names** | kebab-case (`gutter-line.tsx`, `employee-step.tsx`) |
|
|
332
|
+
| **Import order** | stdlib → external → internal |
|
|
333
|
+
| **Error handling** | Return `Result<T>` objects, never throw |
|
|
334
|
+
| **JSX runtime** | `react-jsx` (default React runtime) |
|
|
335
|
+
| **Module imports** | Always use `.js` extension (ESM) |
|
|
336
|
+
| **Strict mode** | TypeScript strict: true |
|
|
337
|
+
|
|
338
|
+
### Result Pattern
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
type Result<T> = { ok: true; data: T } | { ok: false; error: string };
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
All functions that can fail return this pattern. No try/catch at call sites — check `.ok` instead.
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Testing
|
|
349
|
+
|
|
350
|
+
Uses Node.js built-in test runner:
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
# Run all tests
|
|
354
|
+
node --import tsx --test "src/**/*.test.ts"
|
|
355
|
+
|
|
356
|
+
# Run specific test
|
|
357
|
+
node --import tsx --test -t "test name" "src/**/*.test.ts"
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Test files are co-located with source: `src/lib/validation.test.ts` alongside `src/lib/validation.ts`.
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Verification Checklist
|
|
365
|
+
|
|
366
|
+
Before every commit or PR:
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
npm run typecheck # TypeScript type-checking (fast)
|
|
370
|
+
npm run build # Full compilation
|
|
371
|
+
npm run lint # ESLint
|
|
372
|
+
npm run test # Test suite
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
All four must pass clean.
|