oh-my-harness 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +343 -0
  3. package/dist/bin/oh-my-harness.d.ts +2 -0
  4. package/dist/bin/oh-my-harness.js +4 -0
  5. package/dist/cli/commands/add.d.ts +5 -0
  6. package/dist/cli/commands/add.js +41 -0
  7. package/dist/cli/commands/doctor.d.ts +14 -0
  8. package/dist/cli/commands/doctor.js +84 -0
  9. package/dist/cli/commands/init.d.ts +21 -0
  10. package/dist/cli/commands/init.js +188 -0
  11. package/dist/cli/commands/remove.d.ts +5 -0
  12. package/dist/cli/commands/remove.js +89 -0
  13. package/dist/cli/deps-checker.d.ts +10 -0
  14. package/dist/cli/deps-checker.js +79 -0
  15. package/dist/cli/index.d.ts +2 -0
  16. package/dist/cli/index.js +39 -0
  17. package/dist/cli/tool-checker.d.ts +15 -0
  18. package/dist/cli/tool-checker.js +71 -0
  19. package/dist/cli/tui/init-flow.d.ts +8 -0
  20. package/dist/cli/tui/init-flow.js +380 -0
  21. package/dist/core/config-merger.d.ts +2 -0
  22. package/dist/core/config-merger.js +59 -0
  23. package/dist/core/generator.d.ts +9 -0
  24. package/dist/core/generator.js +21 -0
  25. package/dist/core/harness-converter.d.ts +3 -0
  26. package/dist/core/harness-converter.js +140 -0
  27. package/dist/core/harness-schema.d.ts +176 -0
  28. package/dist/core/harness-schema.js +39 -0
  29. package/dist/core/preset-loader.d.ts +3 -0
  30. package/dist/core/preset-loader.js +18 -0
  31. package/dist/core/preset-registry.d.ts +14 -0
  32. package/dist/core/preset-registry.js +39 -0
  33. package/dist/core/preset-types.d.ts +388 -0
  34. package/dist/core/preset-types.js +51 -0
  35. package/dist/generators/claude-md.d.ts +6 -0
  36. package/dist/generators/claude-md.js +30 -0
  37. package/dist/generators/gitignore.d.ts +1 -0
  38. package/dist/generators/gitignore.js +36 -0
  39. package/dist/generators/hooks.d.ts +17 -0
  40. package/dist/generators/hooks.js +43 -0
  41. package/dist/generators/settings.d.ts +8 -0
  42. package/dist/generators/settings.js +35 -0
  43. package/dist/nl/parse-intent.d.ts +11 -0
  44. package/dist/nl/parse-intent.js +122 -0
  45. package/dist/nl/prompt-templates.d.ts +8 -0
  46. package/dist/nl/prompt-templates.js +109 -0
  47. package/dist/utils/markdown.d.ts +8 -0
  48. package/dist/utils/markdown.js +32 -0
  49. package/dist/utils/yaml.d.ts +3 -0
  50. package/dist/utils/yaml.js +12 -0
  51. package/package.json +56 -0
  52. package/presets/_base/preset.yaml +103 -0
  53. package/presets/fastapi/preset.yaml +122 -0
  54. package/presets/nextjs/preset.yaml +109 -0
  55. package/presets/nextjs-fastapi/preset.yaml +54 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kyu1204
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,343 @@
1
+ <div align="center">
2
+
3
+ # 🐴 oh-my-harness
4
+
5
+ **Tame your AI coding agents with natural language.**
6
+
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.7+-blue.svg)](https://www.typescriptlang.org/)
9
+ [![Node.js](https://img.shields.io/badge/Node.js-20+-green.svg)](https://nodejs.org/)
10
+ [![Tests](https://img.shields.io/badge/Tests-131%20passing-brightgreen.svg)](#)
11
+
12
+ > Stop hand-writing CLAUDE.md files. Describe your project, get enforced guardrails.
13
+
14
+ </div>
15
+
16
+ ---
17
+
18
+ ## The Problem
19
+
20
+ Every AI code agent needs configuration files. Claude Code needs `CLAUDE.md` + hooks. Cursor needs `.cursorrules`. Codex needs `AGENTS.md`. You end up:
21
+
22
+ - Copy-pasting config files between projects
23
+ - Forgetting to set up TDD enforcement hooks
24
+ - Agents committing code without running tests
25
+ - Inconsistent behavior across projects
26
+
27
+ ## The Solution
28
+
29
+ ```bash
30
+ oh-my-harness init "React + FastAPI fullstack, TDD enforced, lint on save"
31
+ ```
32
+
33
+ That's it. oh-my-harness generates **enforced guardrails** — not just instructions, but hooks that actually **block** bad behavior:
34
+
35
+ - ❌ Commit without tests passing? **Blocked.**
36
+ - ❌ Write to `node_modules/` or `.next/`? **Blocked.**
37
+ - ❌ Run `rm -rf /`? **Blocked.**
38
+ - ✅ Auto-lint on every file save? **Done.**
39
+ - ✅ TDD workflow enforced in every plan? **Done.**
40
+
41
+ ---
42
+
43
+ ## Quick Start
44
+
45
+ ```bash
46
+ # Clone and install
47
+ git clone https://github.com/your-username/oh-my-harness.git
48
+ cd oh-my-harness && npm install
49
+
50
+ # NL-first: describe your project
51
+ npx tsx bin/oh-my-harness.ts init "TypeScript Next.js frontend with Python FastAPI backend"
52
+
53
+ # Or use built-in presets (offline, instant)
54
+ npx tsx bin/oh-my-harness.ts init --preset nextjs fastapi
55
+ ```
56
+
57
+ ### What Gets Generated
58
+
59
+ ```
60
+ your-project/
61
+ ├── CLAUDE.md # TDD rules, coding standards, architecture guide
62
+ ├── harness.yaml # Your harness config (editable, git-trackable)
63
+ └── .claude/
64
+ ├── settings.json # Hook configs, permissions (allow/deny)
65
+ ├── hooks/
66
+ │ ├── base-command-guard.sh # Blocks dangerous commands
67
+ │ ├── base-test-before-commit.sh # Tests must pass before commit
68
+ │ ├── nextjs-file-guard.sh # Protects build outputs
69
+ │ └── fastapi-lint-on-save.sh # Auto-formats Python on save
70
+ └── oh-my-harness.json # Active preset tracking
71
+ ```
72
+
73
+ ---
74
+
75
+ ## How It Works
76
+
77
+ ```
78
+ ┌─────────────────────┐
79
+ "React + FastAPI │ │
80
+ TDD enforced" │ claude -p (NL) │
81
+ ─────────────────▶│ or --preset flag │
82
+ │ │
83
+ └────────┬────────────┘
84
+
85
+
86
+ ┌─────────────────────┐
87
+ │ harness.yaml │ ← Intermediate representation
88
+ │ (editable, git │ (source of truth)
89
+ │ trackable) │
90
+ └────────┬────────────┘
91
+
92
+
93
+ ┌──────────────┼──────────────┐
94
+ ▼ ▼ ▼
95
+ ┌──────────┐ ┌──────────┐ ┌──────────────┐
96
+ │ CLAUDE.md│ │ Hooks │ │ settings.json│
97
+ │ (rules) │ │ (enforce)│ │ (permissions)│
98
+ └──────────┘ └──────────┘ └──────────────┘
99
+ ```
100
+
101
+ ### Two Modes
102
+
103
+ | Mode | Command | Speed | Requires |
104
+ |------|---------|-------|----------|
105
+ | **NL-first** | `init "description"` | ~5s | Claude CLI installed |
106
+ | **Preset** | `init --preset nextjs` | Instant | Nothing |
107
+
108
+ ---
109
+
110
+ ## Built-in Presets
111
+
112
+ ### `_base` — Always Applied
113
+ - TDD enforcement (mandatory test-first workflow)
114
+ - Dangerous command blocking
115
+ - Pre-commit test gate
116
+ - Branch management rules
117
+ - File safety rules
118
+
119
+ ### `nextjs` — Next.js + TypeScript
120
+ - App Router conventions (Server Components default)
121
+ - Component test enforcement
122
+ - ESLint auto-fix on save
123
+ - Build output protection (`.next/`, `out/`)
124
+ - pnpm permissions
125
+
126
+ ### `fastapi` — FastAPI + Python
127
+ - Async-first, Pydantic v2 patterns
128
+ - pytest + real DB testing (no mocks)
129
+ - Ruff auto-format on save
130
+ - Virtual env protection
131
+ - uv/pytest permissions
132
+
133
+ ### `nextjs-fastapi` — Full Stack
134
+ - Composes both presets
135
+ - Cross-cutting rules (CORS, API boundaries)
136
+ - Dual test suite enforcement (both must pass before commit)
137
+
138
+ ---
139
+
140
+ ## Commands
141
+
142
+ ```bash
143
+ # Initialize with natural language
144
+ oh-my-harness init "your project description"
145
+
146
+ # Initialize with presets
147
+ oh-my-harness init --preset nextjs fastapi
148
+
149
+ # Add a preset to existing config
150
+ oh-my-harness add nextjs
151
+
152
+ # Remove a preset
153
+ oh-my-harness remove fastapi
154
+
155
+ # Health check
156
+ oh-my-harness doctor
157
+ ```
158
+
159
+ ### `doctor` Output
160
+
161
+ ```
162
+ oh-my-harness: running health checks...
163
+ ✓ .claude/oh-my-harness.json found (presets: _base, nextjs)
164
+ ✓ CLAUDE.md exists with intact markers
165
+ ✓ .claude/settings.json is valid
166
+ ✓ All hook scripts are executable
167
+ oh-my-harness: all checks passed
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Enforcement in Action
173
+
174
+ ### TDD Gate (Pre-commit Hook)
175
+
176
+ When Claude Code tries to `git commit`, oh-my-harness intercepts:
177
+
178
+ ```bash
179
+ # Agent attempts: git commit -m "add login page"
180
+ oh-my-harness: Running tests before commit...
181
+ # pnpm test runs...
182
+ # If tests fail:
183
+ {"decision": "block", "reason": "oh-my-harness: tests failed, commit blocked"}
184
+ # Agent must fix tests before committing
185
+ ```
186
+
187
+ ### File Guard (Pre-write Hook)
188
+
189
+ ```bash
190
+ # Agent attempts: Write to .next/cache/something.js
191
+ {"decision": "block", "reason": "oh-my-harness: protected path .next/"}
192
+ # Agent cannot write to build outputs
193
+ ```
194
+
195
+ ### Command Guard
196
+
197
+ ```bash
198
+ # Agent attempts: rm -rf /
199
+ {"decision": "block", "reason": "oh-my-harness: dangerous command blocked"}
200
+ ```
201
+
202
+ ---
203
+
204
+ ## The `harness.yaml` File
205
+
206
+ After init, a `harness.yaml` is saved to your project root. This is the **source of truth** — edit it directly, then re-run init to regenerate:
207
+
208
+ ```yaml
209
+ version: "1.0"
210
+ project:
211
+ description: "E-commerce platform"
212
+ stacks:
213
+ - name: frontend
214
+ framework: nextjs
215
+ language: typescript
216
+ testRunner: vitest
217
+ linter: eslint
218
+
219
+ rules:
220
+ - id: tdd-rules
221
+ title: TDD Workflow
222
+ content: |
223
+ ## TDD Workflow (MANDATORY)
224
+ 1. Write failing test FIRST
225
+ 2. Implement minimal code to pass
226
+ 3. Refactor while green
227
+ priority: 10
228
+
229
+ enforcement:
230
+ preCommit: ["test", "lint", "build"]
231
+ blockedPaths: [".next/", "node_modules/"]
232
+ blockedCommands: ["rm -rf", "sudo"]
233
+ postSave:
234
+ - pattern: "*.ts"
235
+ command: "eslint --fix"
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Architecture
241
+
242
+ ```
243
+ oh-my-harness/
244
+ ├── bin/ # CLI entry point
245
+ ├── src/
246
+ │ ├── cli/commands/ # init, add, remove, doctor
247
+ │ ├── core/
248
+ │ │ ├── preset-types.ts # Zod schemas
249
+ │ │ ├── preset-loader.ts # YAML → typed config
250
+ │ │ ├── preset-registry.ts # Discover & search presets
251
+ │ │ ├── config-merger.ts # Merge multiple presets
252
+ │ │ ├── harness-schema.ts # harness.yaml schema
253
+ │ │ ├── harness-converter.ts # harness.yaml → MergedConfig
254
+ │ │ └── generator.ts # Orchestrates all generators
255
+ │ ├── generators/
256
+ │ │ ├── claude-md.ts # CLAUDE.md with idempotent markers
257
+ │ │ ├── hooks.ts # Executable hook scripts
258
+ │ │ ├── settings.ts # .claude/settings.json
259
+ │ │ └── gitignore.ts # .gitignore updater
260
+ │ ├── nl/
261
+ │ │ ├── parse-intent.ts # claude -p integration
262
+ │ │ └── prompt-templates.ts # LLM prompt construction
263
+ │ └── utils/
264
+ │ ├── markdown.ts # Marker-based section management
265
+ │ └── yaml.ts # YAML helpers
266
+ ├── presets/ # Built-in preset definitions
267
+ │ ├── _base/
268
+ │ ├── nextjs/
269
+ │ ├── fastapi/
270
+ │ └── nextjs-fastapi/
271
+ └── tests/ # 131 tests (unit + integration)
272
+ ```
273
+
274
+ ---
275
+
276
+ ## Adding Custom Presets
277
+
278
+ Create a directory in `presets/` with a `preset.yaml`:
279
+
280
+ ```yaml
281
+ name: my-preset
282
+ displayName: "My Custom Preset"
283
+ description: "Custom rules for my team"
284
+ version: "1.0.0"
285
+ extends: ["_base"]
286
+ tags: ["custom"]
287
+
288
+ claudeMd:
289
+ sections:
290
+ - id: "my-rules"
291
+ title: "My Rules"
292
+ content: |
293
+ ## My Team Rules
294
+ - Always write JSDoc comments
295
+ - Use barrel exports
296
+ priority: 30
297
+
298
+ hooks:
299
+ preToolUse:
300
+ - id: "my-guard"
301
+ matcher: "Bash"
302
+ inline: |
303
+ #!/bin/bash
304
+ # Your custom enforcement logic
305
+ exit 0
306
+ ```
307
+
308
+ No code changes required. The registry auto-discovers it.
309
+
310
+ ---
311
+
312
+ ## Requirements
313
+
314
+ - **Node.js** >= 20
315
+ - **Claude CLI** (optional, for NL mode) — [Install guide](https://docs.anthropic.com/en/docs/claude-code)
316
+
317
+ ---
318
+
319
+ ## Roadmap
320
+
321
+ - [ ] Cursor (`.cursor/rules/`) emitter
322
+ - [ ] Codex (`AGENTS.md`) emitter
323
+ - [ ] GitHub Copilot emitter
324
+ - [ ] `oh-my-harness sync` — drift detection
325
+ - [ ] Community preset registry
326
+ - [ ] `npx oh-my-harness` — zero-install usage
327
+ - [ ] `oh-my-harness modify "change X"` — NL config editing
328
+
329
+ ---
330
+
331
+ ## License
332
+
333
+ MIT
334
+
335
+ ---
336
+
337
+ <div align="center">
338
+
339
+ **Your agents are only as good as their guardrails.**
340
+
341
+ Built with frustration from hand-writing CLAUDE.md files.
342
+
343
+ </div>
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { createCli } from "../cli/index.js";
3
+ const cli = createCli();
4
+ cli.parse(process.argv);
@@ -0,0 +1,5 @@
1
+ export interface AddOptions {
2
+ projectDir?: string;
3
+ presetsDir?: string;
4
+ }
5
+ export declare function addCommand(presetName: string, options?: AddOptions): Promise<void>;
@@ -0,0 +1,41 @@
1
+ import path from "node:path";
2
+ import { PresetRegistry } from "../../core/preset-registry.js";
3
+ import { mergePresets } from "../../core/config-merger.js";
4
+ import { generate } from "../../core/generator.js";
5
+ import { readHarnessState, writeHarnessState, loadAndMergePresets } from "./init.js";
6
+ function getDefaultPresetsDir() {
7
+ return path.resolve(import.meta.dirname, "../../../presets");
8
+ }
9
+ export async function addCommand(presetName, options = {}) {
10
+ const projectDir = options.projectDir ?? process.cwd();
11
+ const presetsDir = options.presetsDir ?? getDefaultPresetsDir();
12
+ // Read existing state (throws if not initialized)
13
+ const state = await readHarnessState(projectDir).catch(() => {
14
+ throw new Error("Harness not initialized. Run `oh-my-harness init` first.");
15
+ });
16
+ // Discover presets
17
+ const registry = new PresetRegistry();
18
+ await registry.discover(presetsDir);
19
+ // Verify preset exists
20
+ if (!registry.has(presetName)) {
21
+ throw new Error(`Preset not found: ${presetName}`);
22
+ }
23
+ // Add preset (deduplicate)
24
+ const updatedPresets = Array.from(new Set([...state.presets, presetName]));
25
+ // Load, resolve, merge
26
+ const presetConfigs = await loadAndMergePresets(updatedPresets, registry);
27
+ const config = mergePresets(presetConfigs);
28
+ // Regenerate
29
+ const result = await generate({ projectDir, config });
30
+ // Save updated state
31
+ await writeHarnessState(projectDir, {
32
+ presets: updatedPresets,
33
+ generatedAt: new Date().toISOString(),
34
+ });
35
+ console.log(`\noh-my-harness: added preset "${presetName}"`);
36
+ console.log(`Active presets: ${updatedPresets.join(", ")}`);
37
+ console.log("Regenerated files:");
38
+ for (const f of result.files) {
39
+ console.log(` ${f}`);
40
+ }
41
+ }
@@ -0,0 +1,14 @@
1
+ export interface DoctorOptions {
2
+ projectDir?: string;
3
+ }
4
+ export interface DoctorResult {
5
+ healthy: boolean;
6
+ checks: {
7
+ stateFile: boolean;
8
+ claudeMd: boolean;
9
+ settingsJson: boolean;
10
+ hooksExecutable: boolean;
11
+ };
12
+ messages: string[];
13
+ }
14
+ export declare function doctorCommand(options?: DoctorOptions): Promise<DoctorResult>;
@@ -0,0 +1,84 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ export async function doctorCommand(options = {}) {
4
+ const projectDir = options.projectDir ?? process.cwd();
5
+ const messages = [];
6
+ const checks = {
7
+ stateFile: false,
8
+ claudeMd: false,
9
+ settingsJson: false,
10
+ hooksExecutable: false,
11
+ };
12
+ // 1. Check .claude/oh-my-harness.json exists
13
+ const stateFile = path.join(projectDir, ".claude", "oh-my-harness.json");
14
+ try {
15
+ await fs.access(stateFile);
16
+ checks.stateFile = true;
17
+ }
18
+ catch {
19
+ messages.push("FAIL: .claude/oh-my-harness.json not found. Run `oh-my-harness init` first.");
20
+ }
21
+ // 2. Check CLAUDE.md exists and has managed markers
22
+ const claudeMdPath = path.join(projectDir, "CLAUDE.md");
23
+ try {
24
+ const content = await fs.readFile(claudeMdPath, "utf-8");
25
+ if (content.includes("<!-- oh-my-harness:")) {
26
+ checks.claudeMd = true;
27
+ }
28
+ else {
29
+ messages.push("WARN: CLAUDE.md exists but has no oh-my-harness managed sections.");
30
+ checks.claudeMd = true; // File exists, just no markers — acceptable
31
+ }
32
+ }
33
+ catch {
34
+ messages.push("FAIL: CLAUDE.md not found.");
35
+ }
36
+ // 3. Check settings.json exists and is valid JSON
37
+ const settingsPath = path.join(projectDir, ".claude", "settings.json");
38
+ try {
39
+ const raw = await fs.readFile(settingsPath, "utf-8");
40
+ JSON.parse(raw);
41
+ checks.settingsJson = true;
42
+ }
43
+ catch (err) {
44
+ if (err.code === "ENOENT") {
45
+ messages.push("FAIL: .claude/settings.json not found.");
46
+ }
47
+ else {
48
+ messages.push("FAIL: .claude/settings.json is not valid JSON.");
49
+ }
50
+ }
51
+ // 4. Check hook scripts exist and are executable
52
+ const hooksDir = path.join(projectDir, ".claude", "hooks");
53
+ try {
54
+ const files = await fs.readdir(hooksDir);
55
+ const scripts = files.filter((f) => f.endsWith(".sh"));
56
+ let allExecutable = true;
57
+ for (const script of scripts) {
58
+ const scriptPath = path.join(hooksDir, script);
59
+ try {
60
+ await fs.access(scriptPath, fs.constants.X_OK);
61
+ }
62
+ catch {
63
+ messages.push(`FAIL: Hook script not executable: ${script}`);
64
+ allExecutable = false;
65
+ }
66
+ }
67
+ checks.hooksExecutable = allExecutable;
68
+ }
69
+ catch {
70
+ // No hooks dir — acceptable if no hooks defined
71
+ checks.hooksExecutable = true;
72
+ }
73
+ const healthy = Object.values(checks).every(Boolean);
74
+ if (healthy) {
75
+ console.log("oh-my-harness: all checks passed");
76
+ }
77
+ else {
78
+ console.log("oh-my-harness: some checks failed:");
79
+ for (const msg of messages) {
80
+ console.log(` ${msg}`);
81
+ }
82
+ }
83
+ return { healthy, checks, messages };
84
+ }
@@ -0,0 +1,21 @@
1
+ import { PresetRegistry } from "../../core/preset-registry.js";
2
+ import type { PresetConfig } from "../../core/preset-types.js";
3
+ import type { ClaudeRunner } from "../../nl/parse-intent.js";
4
+ export interface InitOptions {
5
+ yes?: boolean;
6
+ projectDir?: string;
7
+ presetsDir?: string;
8
+ preset?: string[];
9
+ nlRunner?: ClaudeRunner;
10
+ _nlDescription?: string;
11
+ }
12
+ export interface HarnessState {
13
+ presets: string[];
14
+ generatedAt: string;
15
+ }
16
+ export declare function readHarnessState(projectDir: string): Promise<HarnessState>;
17
+ export declare function writeHarnessState(projectDir: string, state: HarnessState): Promise<void>;
18
+ export declare function loadAndMergePresets(presetNames: string[], registry: PresetRegistry): Promise<PresetConfig[]>;
19
+ export declare function initCommand(presetNames: string[], options?: InitOptions): Promise<void>;
20
+ /** Headless init for CI/automation — bypasses TUI */
21
+ export declare const initCommandHeadless: typeof initCommand;