@powerhousedao/ph-clint-cli 0.1.0-dev.8 → 0.1.0-dev.81

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 (99) hide show
  1. package/dist/cli.js +52 -10
  2. package/dist/codegen/actions.d.ts +46 -0
  3. package/dist/codegen/actions.js +424 -0
  4. package/dist/codegen/builders/agent-ts.d.ts +4 -4
  5. package/dist/codegen/builders/agent-ts.js +92 -2
  6. package/dist/codegen/builders/app-index-ts.d.ts +3 -10
  7. package/dist/codegen/builders/app-index-ts.js +30 -20
  8. package/dist/codegen/builders/cli-package-json.js +46 -14
  9. package/dist/codegen/builders/cli-ts.js +87 -49
  10. package/dist/codegen/builders/config-ts.d.ts +1 -1
  11. package/dist/codegen/builders/config-ts.js +2 -8
  12. package/dist/codegen/builders/demo-agent-ts.d.ts +8 -0
  13. package/dist/codegen/builders/demo-agent-ts.js +50 -0
  14. package/dist/codegen/builders/eslint-config-js.d.ts +1 -1
  15. package/dist/codegen/builders/eslint-config-js.js +7 -4
  16. package/dist/codegen/builders/framework-gen-ts.d.ts +11 -6
  17. package/dist/codegen/builders/framework-gen-ts.js +73 -21
  18. package/dist/codegen/builders/framework-ts.js +33 -7
  19. package/dist/codegen/builders/index.d.ts +10 -2
  20. package/dist/codegen/builders/index.js +30 -6
  21. package/dist/codegen/builders/manifest-json.d.ts +6 -0
  22. package/dist/codegen/builders/manifest-json.js +31 -0
  23. package/dist/codegen/builders/mastra-index-ts.d.ts +3 -0
  24. package/dist/codegen/builders/mastra-index-ts.js +47 -5
  25. package/dist/codegen/builders/publish-config-js.d.ts +8 -0
  26. package/dist/codegen/builders/publish-config-js.js +33 -0
  27. package/dist/codegen/builders/readme-md.js +7 -6
  28. package/dist/codegen/builders/root-package-json.js +11 -12
  29. package/dist/codegen/builders/smoke-test-ts.d.ts +2 -0
  30. package/dist/codegen/builders/smoke-test-ts.js +15 -0
  31. package/dist/codegen/exec.d.ts +0 -18
  32. package/dist/codegen/exec.js +0 -27
  33. package/dist/codegen/generated.d.ts +17 -0
  34. package/dist/codegen/generated.js +49 -0
  35. package/dist/codegen/index.d.ts +8 -36
  36. package/dist/codegen/index.js +6 -293
  37. package/dist/codegen/scaffold.d.ts +41 -3
  38. package/dist/codegen/scaffold.js +106 -16
  39. package/dist/commands/clint-project-build.d.ts +77 -1
  40. package/dist/commands/clint-project-build.js +11 -28
  41. package/dist/commands/clint-project-init.d.ts +128 -10
  42. package/dist/commands/clint-project-init.js +72 -47
  43. package/dist/commands/clint-project-publish.d.ts +78 -1
  44. package/dist/commands/clint-project-publish.js +6 -4
  45. package/dist/commands/clint-project-regen.d.ts +77 -1
  46. package/dist/commands/clint-project-regen.js +26 -2
  47. package/dist/commands/clint-skills-sync.d.ts +86 -0
  48. package/dist/commands/clint-skills-sync.js +56 -0
  49. package/dist/framework.d.ts +322 -60
  50. package/dist/framework.gen.d.ts +50 -10
  51. package/dist/framework.js +1 -5
  52. package/{prompts/agent-profiles/AgentBase.md → dist/gen/agent-profiles/PhClintDevAgent.md} +1 -1
  53. package/dist/gen/skills/cli-setup/.cli-docs.md +135 -0
  54. package/dist/gen/skills/cli-setup/SKILL.md +125 -0
  55. package/dist/gen/skills/cli-setup/references/00.assess-cli-needs.md +53 -0
  56. package/dist/gen/skills/cli-setup/references/01.define-config-schema.md +78 -0
  57. package/dist/gen/skills/cli-setup/references/02.wire-cli.md +141 -0
  58. package/dist/gen/skills/cli-setup/references/03.add-entrypoint.md +128 -0
  59. package/dist/gen/skills/cli-setup/references/expected-outcome.md +3 -0
  60. package/dist/gen/skills/command-definition/.cli-docs.md +84 -0
  61. package/dist/gen/skills/command-definition/SKILL.md +99 -0
  62. package/dist/gen/skills/command-definition/references/00.assess-requirements.md +58 -0
  63. package/dist/gen/skills/command-definition/references/01.define-schema.md +72 -0
  64. package/dist/gen/skills/command-definition/references/02.implement-execute.md +118 -0
  65. package/dist/gen/skills/command-definition/references/03.add-prompting.md +54 -0
  66. package/dist/gen/skills/command-definition/references/expected-outcome.md +3 -0
  67. package/dist/gen/skills/service-definition/.cli-docs.md +214 -0
  68. package/dist/gen/skills/service-definition/SKILL.md +148 -0
  69. package/dist/gen/skills/service-definition/references/00.assess-service.md +69 -0
  70. package/dist/gen/skills/service-definition/references/01.define-service.md +166 -0
  71. package/dist/gen/skills/service-definition/references/02.add-preflight.md +132 -0
  72. package/dist/gen/skills/service-definition/references/03.add-project-scanner.md +82 -0
  73. package/dist/gen/skills/service-definition/references/04.wire-events.md +120 -0
  74. package/dist/gen/skills/service-definition/references/expected-outcome.md +3 -0
  75. package/dist/main.js +0 -0
  76. package/dist/services/clint-project.js +21 -0
  77. package/dist/skills/sync.d.ts +40 -0
  78. package/dist/skills/sync.js +119 -0
  79. package/dist/spec/ensure-document.d.ts +17 -0
  80. package/dist/spec/ensure-document.js +56 -0
  81. package/dist/spec/file.d.ts +1 -4
  82. package/dist/spec/file.js +1 -34
  83. package/dist/spec/from-document.d.ts +4 -1
  84. package/dist/spec/from-document.js +4 -16
  85. package/dist/spec/types.d.ts +1 -62
  86. package/dist/spec/types.js +1 -112
  87. package/dist/triggers/publish-trigger.d.ts +81 -0
  88. package/dist/triggers/publish-trigger.js +137 -0
  89. package/dist/triggers/spec-change.d.ts +60 -18
  90. package/dist/triggers/spec-change.js +154 -101
  91. package/package.json +13 -18
  92. package/dist/codegen/builders/publish-config-ts.d.ts +0 -6
  93. package/dist/codegen/builders/publish-config-ts.js +0 -18
  94. package/dist/commands/init.d.ts +0 -57
  95. package/dist/commands/init.js +0 -168
  96. package/dist/commands/regen.d.ts +0 -18
  97. package/dist/commands/regen.js +0 -68
  98. package/prompts/skills-ext/.gitkeep +0 -0
  99. package/prompts/skills-tpl/.gitkeep +0 -0
@@ -0,0 +1,128 @@
1
+ # Phase 3: Create the Entry Point
2
+
3
+ Set up the CLI entry point and package.json configuration.
4
+
5
+ ## Steps
6
+
7
+ ### 1. Create the entry point file
8
+
9
+ Create `src/cli.ts` (or `src/main.ts`) with the Node.js shebang:
10
+
11
+ ```typescript
12
+ #!/usr/bin/env node
13
+ import { defineCli } from '@powerhousedao/ph-clint';
14
+ import { configSchema } from './config.js';
15
+ import { myCommand } from './commands/my-command.js';
16
+
17
+ const cli = defineCli({
18
+ name: 'my-app',
19
+ version: '1.0.0',
20
+ description: 'My application',
21
+ commands: [myCommand],
22
+ configSchema,
23
+ interactive: {
24
+ welcome: 'My App — type /help for commands',
25
+ },
26
+ });
27
+
28
+ cli.run(process.argv);
29
+ ```
30
+
31
+ If the CLI definition is complex, keep it in a separate file (`src/cli-definition.ts`) and import it:
32
+
33
+ ```typescript
34
+ #!/usr/bin/env node
35
+ import { cli } from './cli-definition.js';
36
+ cli.run(process.argv);
37
+ ```
38
+
39
+ ### 2. Configure package.json
40
+
41
+ Add these fields:
42
+
43
+ ```json
44
+ {
45
+ "type": "module",
46
+ "bin": {
47
+ "my-app": "./dist/cli.js"
48
+ },
49
+ "scripts": {
50
+ "build": "tsc",
51
+ "start": "node dist/cli.js",
52
+ "dev": "tsx src/cli.ts"
53
+ }
54
+ }
55
+ ```
56
+
57
+ - `"type": "module"` — required for ESM imports
58
+ - `"bin"` — maps the CLI name to the compiled entry point
59
+ - `"dev"` script uses `tsx` for development without compilation
60
+
61
+ ### 3. Configure tsconfig.json
62
+
63
+ Ensure these compiler options:
64
+
65
+ ```json
66
+ {
67
+ "compilerOptions": {
68
+ "target": "ES2022",
69
+ "module": "Node16",
70
+ "moduleResolution": "Node16",
71
+ "outDir": "./dist",
72
+ "rootDir": "./src",
73
+ "declaration": true,
74
+ "strict": true,
75
+ "esModuleInterop": true
76
+ },
77
+ "include": ["src"]
78
+ }
79
+ ```
80
+
81
+ ### 4. Add ph-clint dependency
82
+
83
+ ```sh
84
+ pnpm add @powerhousedao/ph-clint zod
85
+ ```
86
+
87
+ Or for local development with the library:
88
+
89
+ ```json
90
+ {
91
+ "dependencies": {
92
+ "ph-clint": "file:../../packages/ph-clint"
93
+ }
94
+ }
95
+ ```
96
+
97
+ ### 5. Codegen projects already have entry points
98
+
99
+ In codegen-managed projects (created by `clint-project-init`), the scaffold already includes:
100
+ - `src/main.ts` — shebang entry point with `cli.run(process.argv)`
101
+ - `src/cli.ts` — `defineCli()` with marker regions
102
+ - `src/framework.ts` — user-owned config schemas and `createTypes()`
103
+ - `package.json` — pre-configured with `bin`, `type: "module"`, and scripts
104
+
105
+ Skip steps 1-4 if these already exist. Proceed directly to verification.
106
+
107
+ ### 6. Verify the CLI works
108
+
109
+ Run these checks:
110
+
111
+ ```sh
112
+ # Build
113
+ pnpm build
114
+
115
+ # Help output (should show all commands)
116
+ node dist/cli.js --help
117
+
118
+ # Version
119
+ node dist/cli.js --version
120
+
121
+ # A specific command
122
+ node dist/cli.js my-command --help
123
+
124
+ # Interactive mode (if configured)
125
+ node dist/cli.js -i
126
+ ```
127
+
128
+ Expected: `--help` shows the description, version, all registered commands (including auto-injected ones like `config` and `cli-docs`), and global options (`-i`, `--workdir`, `--config`, `--verbose`).
@@ -0,0 +1,3 @@
1
+ ## Expected Outcome
2
+
3
+ CLI is defined with all required features wired in (config, secrets, services, interactive mode as needed). The entry point is set up with a shebang and `cli.run(process.argv)`. The `package.json` has `"bin"` and `"type": "module"` fields. The CLI responds correctly to `--help`, `--version`, `-i`, and all registered commands. If `configureAgent()` or `configureReactor()` are needed, they are called after `defineCli()`.
@@ -0,0 +1,84 @@
1
+ ## API Reference: Command Definition
2
+
3
+ ### `defineCommand<TInput, TOutput, TConfig>(options)`
4
+
5
+ Identity function that returns the options object with full type inference. Accepts three generic parameters:
6
+
7
+ - `TInput extends z.ZodType` — the input schema type
8
+ - `TOutput` — the return type of `execute`
9
+ - `TConfig` — the config type from `defineCli`
10
+
11
+ ### `Command<TInput, TOutput, TConfig>` Interface
12
+
13
+ ```typescript
14
+ interface Command<TInput, TOutput, TConfig> {
15
+ id: string; // Kebab-case identifier (e.g. 'add-task')
16
+ description: string; // One-line description for help text
17
+ inputSchema: TInput; // Zod object schema — each field = CLI flag
18
+ outputSchema?: z.ZodType; // Optional output validation schema
19
+ prompt?: PromptConfig; // Interactive prompting configuration
20
+ execute: (
21
+ input: z.output<TInput>,
22
+ context: CommandContext<TConfig>
23
+ ) => Promise<TOutput>;
24
+ }
25
+ ```
26
+
27
+ ### `CommandContext<TConfig>` Interface
28
+
29
+ Available properties on the context object:
30
+
31
+ | Property | Type | Description |
32
+ |----------|------|-------------|
33
+ | `workdir` | `string` | Resolved working directory |
34
+ | `workspace` | `WorkdirStore` | Persistent file-based state (`.ph/{cliName}/`) |
35
+ | `config` | `TConfig` | Typed configuration object |
36
+ | `stdout` | `(text: string) => void` | Output function for streaming text |
37
+ | `log` | `Logger?` | Structured logger (debug/info/warn/error) |
38
+ | `routine` | `Routine?` | Routine loop control (if triggers configured) |
39
+ | `processes` | `ProcessManager?` | Run shell commands with timeout and labeling |
40
+ | `services` | `ServiceManager?` | Manage background services (if configured) |
41
+ | `emit` | `EmitFn?` | Event bus emitter |
42
+ | `reactor` | `() => Promise<ReactorContext?>` | Lazy Powerhouse reactor access |
43
+ | `agent` | `() => Promise<AgentProvider?>` | Lazy Mastra agent access |
44
+
45
+ ### `WorkdirStore` Methods
46
+
47
+ ```typescript
48
+ workspace.loadJsonObject<T>(filename, fallback) // Load JSON from .ph/ store
49
+ workspace.storeJsonObject(filename, value) // Save JSON to .ph/ store
50
+ workspace.loadLocalConfig() // Load local config
51
+ workspace.storeLocalConfig(config) // Save local config
52
+ workspace.getWorkdir() // Get workdir path
53
+ workspace.getStoreFolder(subpath?) // Get .ph/ store folder path
54
+ ```
55
+
56
+ ### `PromptConfig` Interface
57
+
58
+ ```typescript
59
+ interface PromptConfig {
60
+ promptForDefaults?: boolean; // Prompt for fields with .default()? (default: true)
61
+ promptOptional?: string[]; // List of optional field IDs to prompt for
62
+ }
63
+ ```
64
+
65
+ ### Return Value Handling
66
+
67
+ The framework processes return values as follows:
68
+
69
+ 1. `undefined` or `null` — nothing is printed
70
+ 2. `string` — displayed directly as output
71
+ 3. Object with `.text` property — the `text` string is displayed; `.data` is available for programmatic/agent consumption
72
+ 4. Any other value — converted via `String(value)`
73
+
74
+ ### Registration in `defineCli`
75
+
76
+ Commands are registered via the `commands` array:
77
+
78
+ ```typescript
79
+ const cli = defineCli({
80
+ name: 'mycli',
81
+ commands: [greet, addTask, listTasks],
82
+ // ...
83
+ });
84
+ ```
@@ -0,0 +1,99 @@
1
+ ---
2
+ name: command-definition
3
+ description: "Define commands with Zod schemas, execute functions, return values, and parameter prompting"
4
+ metadata:
5
+ author: Powerhouse
6
+ version: "1.0.0"
7
+ ---
8
+
9
+ === BEGIN SKILL BRIEFING ===
10
+
11
+ IMPORTANT: Don't take any action yet. You will be guided through your tasks after the briefing(s). Just process and confirm your understanding.
12
+
13
+ # Command Definition - Skill Preamble
14
+
15
+ With this skill, you can define ph-clint commands — the atomic building blocks of every CLI. A single command definition produces a CLI subcommand (`mycli greet --name World`), a REPL command (`/greet --name World`), an MCP tool, and an agent tool — all from the same code.
16
+
17
+ ## Core Principles
18
+
19
+ ### One Command = One User Intent
20
+
21
+ Each command does one thing. Don't create CRUD bundles — instead define `add-task`, `list-tasks`, `remove-task` as separate commands. This keeps schemas focused and help text clear.
22
+
23
+ ### inputSchema Is the Single Source of Truth
24
+
25
+ The Zod `inputSchema` drives everything: CLI flags, help text, validation, agent tool descriptions, and interactive prompting. Design the schema carefully — it's the command's public API.
26
+
27
+ ### `.describe()` on Every Field
28
+
29
+ Every Zod field MUST have `.describe('...')`. This text appears in:
30
+ - CLI `--help` output
31
+ - REPL auto-completion hints
32
+ - Agent tool descriptions
33
+ - Parameter prompting labels
34
+
35
+ Without `.describe()`, the user sees empty descriptions everywhere.
36
+
37
+ ### Return `{ text, data }` for Agent-Compatible Commands
38
+
39
+ - Return a **string** for simple display-only commands
40
+ - Return `{ text: 'human-readable', data: structuredResult }` when the command will also be used as an agent tool — `text` is shown to users, `data` is consumed by the agent
41
+
42
+ ### Execute Receives Validated Input and Injected Context
43
+
44
+ The `execute(input, context)` function receives:
45
+ 1. `input` — already validated and parsed by Zod. Destructure directly: `({ name, loud })`
46
+ 2. `context` — injected by the framework. Destructure only what you need: `{ workspace, config }`
47
+
48
+ Never construct context yourself. Never call `process.exit()` or `console.log()` — use the injected `context.stdout()` for streaming output, and throw errors for failures.
49
+
50
+ ## Common Pitfalls
51
+
52
+ - **Forgetting `.describe()`** — help text and tool descriptions show empty fields
53
+ - **Using `.optional()` when you mean `.default('value')`** — optional means the flag can be omitted entirely with no fallback; default provides a sensible fallback value
54
+ - **Returning void** — the CLI has nothing to display; always return a string or `{ text, data }`
55
+ - **Accessing `context.services` without null check** — it's `undefined` when no services are configured
56
+ - **Using `console.log`** — use `context.stdout(text)` instead so output is capturable in tests
57
+ - **Calling `process.exit()`** — throw an error instead; the framework handles exit codes
58
+
59
+ ## When to Use Typed Generics
60
+
61
+ For simple commands, `defineCommand({ ... })` with inline schemas infers types automatically. Use explicit generics when you need to:
62
+ - Share the input schema across commands (extract to a const)
63
+ - Enforce a specific output type
64
+ - Bind to a project-specific config type via `createTypes()`
65
+
66
+ ```typescript
67
+ // Project-level typed factory (from framework.ts)
68
+ const { defineCommand } = createTypes({ configSchema });
69
+
70
+ // Now context.config is typed automatically
71
+ const myCmd = defineCommand({
72
+ id: 'my-cmd',
73
+ inputSchema: z.object({ name: z.string().describe('Name') }),
74
+ execute: async ({ name }, { config }) => {
75
+ // config is fully typed here
76
+ },
77
+ });
78
+ ```
79
+
80
+ ## Working in a Codegen-Managed Project
81
+
82
+ ph-clint-cli projects are spec-driven. When defining commands:
83
+
84
+ - **Import `defineCommand` from `../framework.js`**, not from `@powerhousedao/ph-clint`. The typed factory from `framework.ts` gives automatic config typing via `createTypes()`.
85
+ - **Never hand-edit `cli.ts` marker regions.** The `@clint:begin`/`@clint:end` regions are codegen-owned. Instead:
86
+ 1. Write the command file in `src/commands/`
87
+ 2. Update `project-spec.json` to declare the new command
88
+ 3. Run `clint-project-regen` to regenerate `cli.ts` with the correct imports and registration
89
+ - **Build** after regen with `clint-project-build` to verify TypeScript compiles
90
+
91
+ === END OF SKILL BRIEFING ===
92
+
93
+ ## Specific tasks
94
+
95
+ * **Assess Requirements** [references/00.assess-requirements.md](references/00.assess-requirements.md)
96
+ * **Define Schema** [references/01.define-schema.md](references/01.define-schema.md)
97
+ * **Implement Execute** [references/02.implement-execute.md](references/02.implement-execute.md)
98
+ * **Add Prompting** [references/03.add-prompting.md](references/03.add-prompting.md)
99
+ * **Expected Outcome** [references/expected-outcome.md](references/expected-outcome.md)
@@ -0,0 +1,58 @@
1
+ # Phase 0: Assess Requirements
2
+
3
+ Before writing any code, clarify what the command should do.
4
+
5
+ ## Steps
6
+
7
+ ### 1. Name the command
8
+
9
+ Choose a kebab-case, verb-noun name that describes the action:
10
+ - `add-task`, `list-images`, `run-build`, `show-status`
11
+ - The `id` becomes the CLI subcommand and REPL `/command` name
12
+
13
+ ### 2. Identify all inputs
14
+
15
+ For each parameter, determine:
16
+ - **Type**: `z.string()`, `z.number()`, `z.boolean()`, `z.enum([...])`, `z.array(z.string())`
17
+ - **Required vs optional**: Is this mandatory or can it be omitted?
18
+ - **Default value**: What's a sensible default? Use `.default(value)` instead of `.optional()` when there is one
19
+ - **Validation**: Regex patterns, min/max ranges, URL format, etc.
20
+ - **Description**: One-line user-facing text for `--help`
21
+
22
+ ### 3. Determine context needs
23
+
24
+ Which `CommandContext` properties does the command need?
25
+
26
+ - [ ] `workspace` — persistent state across invocations
27
+ - [ ] `config` — typed CLI configuration
28
+ - [ ] `processes` — spawn shell commands
29
+ - [ ] `services` — manage background services
30
+ - [ ] `stdout` — streaming output (for long-running operations)
31
+ - [ ] `log` — debug/info/warn/error logging
32
+ - [ ] `reactor()` — Powerhouse document operations
33
+ - [ ] `agent()` — AI agent access
34
+
35
+ ### 4. Determine return shape
36
+
37
+ - **Display-only**: return a string
38
+ - **Agent-compatible**: return `{ text: 'human message', data: structuredResult }`
39
+ - **Silent**: return `undefined` (rare — most commands should confirm what they did)
40
+
41
+ ### 5. Determine prompting needs
42
+
43
+ If the command will be used in interactive/REPL mode:
44
+ - Which optional fields should be prompted for?
45
+ - Should fields with defaults be re-prompted? (Usually no — set `promptForDefaults: false`)
46
+ - Enum fields get selection prompts automatically
47
+
48
+ ### 6. Detect codegen project
49
+
50
+ Check if `cli.ts` contains `@clint:begin` markers. If it does, this is a codegen-managed project:
51
+ - Import `defineCommand` from `../framework.js`, not from `@powerhousedao/ph-clint`
52
+ - Do NOT manually edit marker regions in `cli.ts` — update `project-spec.json` instead and run `clint-project-regen` to regenerate
53
+
54
+ ### 7. Check for existing commands
55
+
56
+ - Are there existing commands that overlap with this one?
57
+ - Does this command depend on another command's output?
58
+ - Could this be an extension of an existing command instead?
@@ -0,0 +1,72 @@
1
+ # Phase 1: Define the Input Schema
2
+
3
+ Design and write the Zod `inputSchema` that defines the command's parameters.
4
+
5
+ ## Steps
6
+
7
+ ### 1. Choose field types
8
+
9
+ Select the appropriate Zod type for each parameter:
10
+
11
+ ```typescript
12
+ z.string() // Free text
13
+ z.string().url() // URL validation
14
+ z.string().regex(/^[a-z-]+$/) // Pattern validation
15
+ z.number() // Numeric input
16
+ z.number().min(1).max(100) // Bounded number
17
+ z.boolean() // Flag (--verbose, --dry-run)
18
+ z.enum(['low', 'medium', 'high']) // Fixed choices (auto-completion in REPL)
19
+ z.array(z.string()) // Multiple values (--tag a --tag b)
20
+ ```
21
+
22
+ ### 2. Add `.describe()` to every field
23
+
24
+ This is mandatory. The description text appears in help output, agent tool descriptions, and prompting labels:
25
+
26
+ ```typescript
27
+ z.string().describe('Name of the project to initialize')
28
+ z.boolean().default(false).describe('Run without making changes')
29
+ ```
30
+
31
+ ### 3. Add `.default()` for sensible defaults
32
+
33
+ Fields with defaults become optional CLI flags. Without a default, the flag is mandatory:
34
+
35
+ ```typescript
36
+ z.number().default(3000).describe('Port number') // --port is optional, defaults to 3000
37
+ z.enum(['json', 'table']).default('table').describe('Output format')
38
+ ```
39
+
40
+ ### 4. Mark truly optional fields
41
+
42
+ Use `.optional()` only when there is genuinely no default value and the field can be absent:
43
+
44
+ ```typescript
45
+ z.string().optional().describe('Due date (YYYY-MM-DD)') // --due can be omitted entirely
46
+ ```
47
+
48
+ ### 5. Add validation constraints
49
+
50
+ ```typescript
51
+ z.string().regex(/^[a-zA-Z0-9-]+$/).describe('Project name (alphanumeric + hyphens)')
52
+ z.number().min(1).max(65535).describe('Port number')
53
+ z.string().url().describe('API endpoint URL')
54
+ ```
55
+
56
+ ### 6. Assemble the schema
57
+
58
+ ```typescript
59
+ const inputSchema = z.object({
60
+ name: z.string().describe('Project name'),
61
+ port: z.number().default(3000).describe('Dev server port'),
62
+ template: z.enum(['minimal', 'full']).default('minimal').describe('Project template'),
63
+ dryRun: z.boolean().default(false).describe('Preview changes without writing files'),
64
+ });
65
+ ```
66
+
67
+ ### 7. Verify
68
+
69
+ - Every required field without a default will be a mandatory `--flag`
70
+ - Every field has `.describe()`
71
+ - Field names are camelCase (they become `--camel-case` CLI flags automatically)
72
+ - Enum types list all valid options
@@ -0,0 +1,118 @@
1
+ # Phase 2: Implement the Execute Function
2
+
3
+ Write the command's logic inside the `execute` function.
4
+
5
+ ## Steps
6
+
7
+ ### 1. Destructure input and context
8
+
9
+ Only destructure what you need:
10
+
11
+ ```typescript
12
+ execute: async ({ name, port }, { workspace, config }) => {
13
+ // `name` and `port` are validated by Zod
14
+ // `workspace` and `config` are injected by the framework
15
+ }
16
+ ```
17
+
18
+ ### 2. Implement the command logic
19
+
20
+ The body is a regular async function. Use the context resources:
21
+
22
+ **Workspace (persistent state)**:
23
+ ```typescript
24
+ const tasks = await workspace.loadJsonObject<Task[]>('tasks.json', []);
25
+ tasks.push(newTask);
26
+ await workspace.storeJsonObject('tasks.json', tasks);
27
+ ```
28
+
29
+ **Processes (shell commands)**:
30
+ ```typescript
31
+ const result = await processes!.run('npm install', {
32
+ label: 'install',
33
+ timeout: 60_000,
34
+ });
35
+ ```
36
+
37
+ **Streaming output (long operations)**:
38
+ ```typescript
39
+ context.stdout('Building project...\n');
40
+ // ... work ...
41
+ context.stdout('Done.\n');
42
+ ```
43
+
44
+ ### 3. Choose the return shape
45
+
46
+ **Simple display** — return a string:
47
+ ```typescript
48
+ return `Created project "${name}" at port ${port}`;
49
+ ```
50
+
51
+ **Agent-compatible** — return `{ text, data }`:
52
+ ```typescript
53
+ const project = { name, port, path: projectDir };
54
+ return {
55
+ text: `Created project "${name}" at ${projectDir}`,
56
+ data: project,
57
+ };
58
+ ```
59
+
60
+ ### 4. Handle errors
61
+
62
+ Throw errors — the framework catches them and formats the output:
63
+
64
+ ```typescript
65
+ if (!fs.existsSync(targetDir)) {
66
+ throw new Error(`Directory not found: ${targetDir}`);
67
+ }
68
+ ```
69
+
70
+ Do NOT:
71
+ - Return error strings (the exit code will be 0)
72
+ - Call `process.exit(1)` (breaks testability)
73
+ - Use `console.error()` (not captured by RunOptions)
74
+
75
+ ### 5. Assemble the full command
76
+
77
+ ```typescript
78
+ import { defineCommand } from '../framework.js';
79
+ import { z } from 'zod';
80
+
81
+ export const myCommand = defineCommand({
82
+ id: 'my-command',
83
+ description: 'Does something useful',
84
+ inputSchema: z.object({
85
+ name: z.string().describe('The name'),
86
+ verbose: z.boolean().default(false).describe('Show detailed output'),
87
+ }),
88
+ execute: async ({ name, verbose }, { workspace, config }) => {
89
+ // Implementation here
90
+ return { text: `Done: ${name}`, data: { name } };
91
+ },
92
+ });
93
+ ```
94
+
95
+ ### 6. Export and register
96
+
97
+ Export the command from its file, then register it in the CLI.
98
+
99
+ **In a codegen-managed project** (cli.ts has `@clint:begin` markers):
100
+
101
+ Do NOT manually edit `cli.ts`. Instead:
102
+
103
+ 1. Add the command to `project-spec.json`
104
+ 2. Run `clint-project-regen` — this regenerates the marker regions with the correct import and registration
105
+ 3. Run `clint-project-build` to verify TypeScript compiles
106
+
107
+ **In a manually-managed project** (no markers):
108
+
109
+ ```typescript
110
+ import { myCommand } from './commands/my-command.js';
111
+
112
+ const cli = defineCli({
113
+ commands: [...existingCommands, myCommand],
114
+ // ...
115
+ });
116
+ ```
117
+
118
+ In both cases, import `defineCommand` from `../framework.js` in your command file (not from `@powerhousedao/ph-clint`) to get automatic config typing.
@@ -0,0 +1,54 @@
1
+ # Phase 3: Configure Interactive Prompting (Optional)
2
+
3
+ Set up parameter prompting for REPL/interactive mode.
4
+
5
+ ## When to Add Prompting
6
+
7
+ Add prompting when the command is frequently used in interactive mode and has optional parameters that benefit from guided input. Skip this phase if the command is primarily used in command mode or all parameters are required.
8
+
9
+ ## Steps
10
+
11
+ ### 1. Add `prompt` config to `defineCommand`
12
+
13
+ ```typescript
14
+ export const addTask = defineCommand({
15
+ id: 'add-task',
16
+ description: 'Add a new task',
17
+ inputSchema: z.object({
18
+ title: z.string().describe('Task title'),
19
+ priority: z.enum(['low', 'medium', 'high']).optional().describe('Task priority'),
20
+ due: z.string().optional().describe('Due date (YYYY-MM-DD)'),
21
+ }),
22
+ prompt: {
23
+ promptForDefaults: false,
24
+ promptOptional: ['priority'],
25
+ },
26
+ execute: async ({ title, priority, due }, { workspace }) => {
27
+ // ...
28
+ },
29
+ });
30
+ ```
31
+
32
+ ### 2. Understand the prompting options
33
+
34
+ **`promptForDefaults: false`** (recommended):
35
+ - Fields with `.default()` values will NOT be prompted for
36
+ - The default value is used silently
37
+ - Set to `true` only if users frequently need to override defaults
38
+
39
+ **`promptOptional: ['field1', 'field2']`**:
40
+ - These `.optional()` fields WILL be prompted for in REPL mode
41
+ - Omitted optional fields are NOT prompted (they stay `undefined`)
42
+ - Use this for fields that are useful but not required
43
+
44
+ ### 3. How prompting works at runtime
45
+
46
+ - **CLI mode** (`mycli add-task --title "Buy milk"`): No prompting. Missing required fields are errors. Optional fields default to `undefined`.
47
+ - **REPL mode** (`/add-task`): Missing required fields trigger text prompts. Fields in `promptOptional` trigger prompts. Enum fields show selection menus.
48
+
49
+ ### 4. Test the prompting behavior
50
+
51
+ In the REPL:
52
+ 1. Type `/add-task` with no arguments — required fields should be prompted
53
+ 2. Type `/add-task --title "test"` — only `promptOptional` fields should be prompted
54
+ 3. Enum fields should show a selection list, not free text input
@@ -0,0 +1,3 @@
1
+ ## Expected Outcome
2
+
3
+ Command is defined with typed inputSchema, exported, and registered in `defineCli`. Help text is complete (every field has `.describe()`). If intended as an agent tool, returns `{ text, data }`. The command handles all input combinations correctly and follows ph-clint conventions (no `console.log`, no `process.exit`, errors are thrown).