@powerhousedao/ph-clint-cli 0.1.0-dev.9 → 0.1.0-dev.90
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/dist/cli.js +52 -10
- package/dist/codegen/actions.d.ts +46 -0
- package/dist/codegen/actions.js +424 -0
- package/dist/codegen/builders/agent-ts.d.ts +4 -4
- package/dist/codegen/builders/agent-ts.js +92 -2
- package/dist/codegen/builders/app-index-ts.d.ts +3 -10
- package/dist/codegen/builders/app-index-ts.js +30 -20
- package/dist/codegen/builders/cli-package-json.js +46 -14
- package/dist/codegen/builders/cli-ts.js +87 -49
- package/dist/codegen/builders/config-ts.d.ts +1 -1
- package/dist/codegen/builders/config-ts.js +2 -8
- package/dist/codegen/builders/demo-agent-ts.d.ts +8 -0
- package/dist/codegen/builders/demo-agent-ts.js +50 -0
- package/dist/codegen/builders/eslint-config-js.d.ts +1 -1
- package/dist/codegen/builders/eslint-config-js.js +7 -4
- package/dist/codegen/builders/framework-gen-ts.d.ts +11 -6
- package/dist/codegen/builders/framework-gen-ts.js +73 -21
- package/dist/codegen/builders/framework-ts.js +33 -7
- package/dist/codegen/builders/index.d.ts +10 -2
- package/dist/codegen/builders/index.js +30 -6
- package/dist/codegen/builders/manifest-json.d.ts +6 -0
- package/dist/codegen/builders/manifest-json.js +31 -0
- package/dist/codegen/builders/mastra-index-ts.d.ts +3 -0
- package/dist/codegen/builders/mastra-index-ts.js +47 -5
- package/dist/codegen/builders/publish-config-js.d.ts +8 -0
- package/dist/codegen/builders/publish-config-js.js +33 -0
- package/dist/codegen/builders/readme-md.js +7 -6
- package/dist/codegen/builders/root-package-json.js +11 -12
- package/dist/codegen/builders/smoke-test-ts.d.ts +2 -0
- package/dist/codegen/builders/smoke-test-ts.js +15 -0
- package/dist/codegen/exec.d.ts +0 -18
- package/dist/codegen/exec.js +0 -27
- package/dist/codegen/generated.d.ts +17 -0
- package/dist/codegen/generated.js +49 -0
- package/dist/codegen/index.d.ts +8 -36
- package/dist/codegen/index.js +6 -293
- package/dist/codegen/scaffold.d.ts +41 -3
- package/dist/codegen/scaffold.js +106 -16
- package/dist/commands/clint-project-build.d.ts +77 -1
- package/dist/commands/clint-project-build.js +11 -28
- package/dist/commands/clint-project-init.d.ts +130 -10
- package/dist/commands/clint-project-init.js +72 -47
- package/dist/commands/clint-project-publish.d.ts +78 -1
- package/dist/commands/clint-project-publish.js +6 -4
- package/dist/commands/clint-project-regen.d.ts +77 -1
- package/dist/commands/clint-project-regen.js +26 -2
- package/dist/commands/clint-skills-sync.d.ts +86 -0
- package/dist/commands/clint-skills-sync.js +56 -0
- package/dist/framework.d.ts +322 -60
- package/dist/framework.gen.d.ts +50 -10
- package/dist/framework.js +1 -5
- package/{prompts/agent-profiles/AgentBase.md → dist/gen/agent-profiles/PhClintDevAgent.md} +1 -1
- package/dist/gen/skills/cli-setup/.cli-docs.md +135 -0
- package/dist/gen/skills/cli-setup/SKILL.md +125 -0
- package/dist/gen/skills/cli-setup/references/00.assess-cli-needs.md +53 -0
- package/dist/gen/skills/cli-setup/references/01.define-config-schema.md +78 -0
- package/dist/gen/skills/cli-setup/references/02.wire-cli.md +141 -0
- package/dist/gen/skills/cli-setup/references/03.add-entrypoint.md +128 -0
- package/dist/gen/skills/cli-setup/references/expected-outcome.md +3 -0
- package/dist/gen/skills/command-definition/.cli-docs.md +84 -0
- package/dist/gen/skills/command-definition/SKILL.md +99 -0
- package/dist/gen/skills/command-definition/references/00.assess-requirements.md +58 -0
- package/dist/gen/skills/command-definition/references/01.define-schema.md +72 -0
- package/dist/gen/skills/command-definition/references/02.implement-execute.md +118 -0
- package/dist/gen/skills/command-definition/references/03.add-prompting.md +54 -0
- package/dist/gen/skills/command-definition/references/expected-outcome.md +3 -0
- package/dist/gen/skills/service-definition/.cli-docs.md +214 -0
- package/dist/gen/skills/service-definition/SKILL.md +148 -0
- package/dist/gen/skills/service-definition/references/00.assess-service.md +69 -0
- package/dist/gen/skills/service-definition/references/01.define-service.md +166 -0
- package/dist/gen/skills/service-definition/references/02.add-preflight.md +132 -0
- package/dist/gen/skills/service-definition/references/03.add-project-scanner.md +82 -0
- package/dist/gen/skills/service-definition/references/04.wire-events.md +120 -0
- package/dist/gen/skills/service-definition/references/expected-outcome.md +3 -0
- package/dist/main.js +0 -0
- package/dist/services/clint-project.js +21 -0
- package/dist/skills/sync.d.ts +40 -0
- package/dist/skills/sync.js +119 -0
- package/dist/spec/ensure-document.d.ts +17 -0
- package/dist/spec/ensure-document.js +56 -0
- package/dist/spec/file.d.ts +1 -4
- package/dist/spec/file.js +1 -34
- package/dist/spec/from-document.d.ts +4 -1
- package/dist/spec/from-document.js +4 -16
- package/dist/spec/types.d.ts +1 -62
- package/dist/spec/types.js +1 -112
- package/dist/triggers/publish-trigger.d.ts +81 -0
- package/dist/triggers/publish-trigger.js +137 -0
- package/dist/triggers/spec-change.d.ts +60 -18
- package/dist/triggers/spec-change.js +154 -101
- package/package.json +15 -20
- package/dist/codegen/builders/publish-config-ts.d.ts +0 -6
- package/dist/codegen/builders/publish-config-ts.js +0 -18
- package/dist/commands/init.d.ts +0 -57
- package/dist/commands/init.js +0 -168
- package/dist/commands/regen.d.ts +0 -18
- package/dist/commands/regen.js +0 -68
- package/prompts/skills-ext/.gitkeep +0 -0
- 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).
|