@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.
- 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 +128 -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 +13 -18
- 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,214 @@
|
|
|
1
|
+
## API Reference: Service Definition
|
|
2
|
+
|
|
3
|
+
### `defineService<TConfig>(options): ServiceDefinition<TConfig>`
|
|
4
|
+
|
|
5
|
+
Identity function that returns the options object with full type inference. One generic parameter:
|
|
6
|
+
|
|
7
|
+
- `TConfig` — the config type from `defineCli` (for typed `env()` function)
|
|
8
|
+
|
|
9
|
+
### `ServiceDefinition<TConfig>` Interface
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
interface ServiceDefinition<TConfig = Record<string, unknown>> {
|
|
13
|
+
id: string; // Kebab-case identifier (e.g. 'dev-server')
|
|
14
|
+
command: string | ((params?: Record<string, unknown>) => string);
|
|
15
|
+
name?: string; // Display name (e.g. 'Dev Server')
|
|
16
|
+
description?: string; // One-line description
|
|
17
|
+
env?: (config: TConfig, params?: Record<string, unknown>) => Record<string, string>;
|
|
18
|
+
paramsSchema?: ZodType; // Runtime parameters (become start command flags)
|
|
19
|
+
maxInstances?: number; // Concurrency limit (default: 1)
|
|
20
|
+
preflight?: PreflightCheck<TConfig>[];
|
|
21
|
+
readiness?: ReadinessConfig;
|
|
22
|
+
shutdown?: { signal: NodeJS.Signals; timeout: number };
|
|
23
|
+
restart?: { enabled: boolean; maxRetries: number; delay: number };
|
|
24
|
+
projectScanner?: ProjectScanner;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### `ReadinessConfig` — Single Pattern Form
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
{
|
|
32
|
+
pattern: RegExp;
|
|
33
|
+
captures?: Record<string, number | CaptureDefinition>;
|
|
34
|
+
timeout: number; // ms before marking failed
|
|
35
|
+
wait?: boolean; // true (default) = block until match; false = ready immediately
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### `ReadinessConfig` — Multiple Patterns Form
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
{
|
|
43
|
+
patterns: ReadinessPattern[];
|
|
44
|
+
timeout: number;
|
|
45
|
+
wait?: boolean;
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
ALL patterns must match before the service is considered ready.
|
|
50
|
+
|
|
51
|
+
### `ReadinessPattern`
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
interface ReadinessPattern {
|
|
55
|
+
name: string; // Identifier for progress reporting
|
|
56
|
+
pattern: RegExp; // Regex to match against stdout/stderr lines
|
|
57
|
+
captures?: Record<string, number | CaptureDefinition>;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### `CaptureDefinition`
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
interface CaptureDefinition {
|
|
65
|
+
group: number; // 1-indexed regex capture group
|
|
66
|
+
type?: EndpointType; // Enables downstream features (e.g. MCP discovery)
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### `EndpointType` Union
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
type EndpointType = 'other' | 'api-mcp' | 'api-rest' | 'api-graphql' | 'website';
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Preflight Checks
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
type PreflightCheck<TConfig> =
|
|
80
|
+
(ctx: PreflightContext<TConfig>) => PreflightResult | Promise<PreflightResult>;
|
|
81
|
+
|
|
82
|
+
interface PreflightContext<TConfig> {
|
|
83
|
+
cwd: string;
|
|
84
|
+
config: TConfig;
|
|
85
|
+
params?: Record<string, unknown>;
|
|
86
|
+
command: string; // Resolved command string
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
type PreflightResult =
|
|
90
|
+
| { ok: true }
|
|
91
|
+
| { ok: false; message: string; hint?: string };
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Built-in Preflight Factories
|
|
95
|
+
|
|
96
|
+
**`checkWorkdir(test, message, hint?)`**
|
|
97
|
+
```typescript
|
|
98
|
+
checkWorkdir(
|
|
99
|
+
test: (cwd: string) => boolean, // Return true if valid
|
|
100
|
+
message: string, // Error message on failure
|
|
101
|
+
hint?: string // Suggested fix
|
|
102
|
+
): PreflightCheck
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**`checkCommand(binary, options?)`**
|
|
106
|
+
```typescript
|
|
107
|
+
checkCommand(
|
|
108
|
+
binary: string,
|
|
109
|
+
options?: {
|
|
110
|
+
versionFlag?: string; // Default: '--version'
|
|
111
|
+
versionTest?: (version: string) => boolean; // Validate version output
|
|
112
|
+
hint?: string; // Install instructions
|
|
113
|
+
}
|
|
114
|
+
): PreflightCheck
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**`checkPort(port, label?)`**
|
|
118
|
+
```typescript
|
|
119
|
+
checkPort(
|
|
120
|
+
port: number | ((ctx: PreflightContext) => number | undefined),
|
|
121
|
+
label?: string // Display name (e.g. 'Dev Server')
|
|
122
|
+
): PreflightCheck
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**`isPortFree(port): Promise<boolean>`** — utility for custom checks.
|
|
126
|
+
|
|
127
|
+
### `ServiceManager` Interface
|
|
128
|
+
|
|
129
|
+
Methods available on `context.services`:
|
|
130
|
+
|
|
131
|
+
| Method | Signature | Description |
|
|
132
|
+
|--------|-----------|-------------|
|
|
133
|
+
| `start` | `(id, opts?: ServiceStartOptions) => Promise<string>` | Start service, returns instanceId |
|
|
134
|
+
| `stop` | `(id, instanceId?) => Promise<void>` | Stop service instance |
|
|
135
|
+
| `list` | `(serviceId?) => ServiceInstanceStatus[]` | List instance statuses |
|
|
136
|
+
| `getDefinition` | `(id) => ServiceDefinition \| undefined` | Look up a service definition |
|
|
137
|
+
| `logs` | `(id, instanceId?, lines?) => string` | Tail service log (default 50 lines) |
|
|
138
|
+
| `watchLogs` | `(id, instanceId, onLine) => () => void` | Stream log lines, returns unsubscribe |
|
|
139
|
+
| `scanProjects` | `(id, rootDir) => ProjectScanResult[]` | Discover projects for a service |
|
|
140
|
+
| `purgeStoppedInstances` | `(id) => void` | Clean up stopped instance state |
|
|
141
|
+
|
|
142
|
+
### `ServiceStartOptions`
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
interface ServiceStartOptions {
|
|
146
|
+
workdir?: string; // For instance identity (hashed into instanceId)
|
|
147
|
+
cwd?: string; // Spawn directory (defaults to process.cwd())
|
|
148
|
+
params?: Record<string, unknown>; // Runtime parameters
|
|
149
|
+
name?: string; // Named instance (for multi-instance services)
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### `ServiceInstanceStatus`
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
interface ServiceInstanceStatus {
|
|
157
|
+
serviceId: string;
|
|
158
|
+
instanceId: string;
|
|
159
|
+
name: string;
|
|
160
|
+
status: 'idle' | 'starting' | 'ready' | 'failed' | 'stopping' | 'stopped';
|
|
161
|
+
pid?: number;
|
|
162
|
+
endpoints?: Record<string, string>;
|
|
163
|
+
endpointTypes?: Record<string, EndpointType>;
|
|
164
|
+
error?: string;
|
|
165
|
+
restartAttempt?: number;
|
|
166
|
+
workdir?: string;
|
|
167
|
+
params?: Record<string, unknown>;
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Service Events
|
|
172
|
+
|
|
173
|
+
| Event | Payload | When |
|
|
174
|
+
|-------|---------|------|
|
|
175
|
+
| `service:ready` | `{ id, instanceId, name, endpoints?, endpointTypes? }` | All readiness patterns matched |
|
|
176
|
+
| `service:failed` | `{ id, instanceId, name, error }` | Readiness timeout or process crash |
|
|
177
|
+
| `service:stopped` | `{ id, instanceId, name }` | Explicit stop completed |
|
|
178
|
+
| `service:pattern-matched` | `{ id, instanceId, name, patternName, endpoints, endpointTypes, remaining }` | One readiness pattern matched |
|
|
179
|
+
| `service:restarting` | `{ id, instanceId, name, attempt, maxRetries }` | Auto-restart triggered |
|
|
180
|
+
|
|
181
|
+
### `ProjectScanner` Interface
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
interface ProjectScanner {
|
|
185
|
+
isProjectFolder(folderPath: string): boolean;
|
|
186
|
+
getProjectName?(folderPath: string): string;
|
|
187
|
+
getProjectConfig?(folderPath: string): Record<string, unknown>;
|
|
188
|
+
getDocumentLink?(folderPath: string): { documentId: string; documentType: string } | undefined;
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Auto-Generated Commands per Service
|
|
193
|
+
|
|
194
|
+
| Command ID | Description |
|
|
195
|
+
|-----------|-------------|
|
|
196
|
+
| `{id}-start` | Start the service (flags from paramsSchema + workdir + name) |
|
|
197
|
+
| `{id}-stop` | Stop a running instance |
|
|
198
|
+
| `{id}-restart` | Restart preserving workdir/params |
|
|
199
|
+
| `{id}-ps` | Show instance status |
|
|
200
|
+
| `{id}-logs` | Tail service logs (default 50 lines) |
|
|
201
|
+
| `{id}-manage` | Interactive management panel (REPL only) |
|
|
202
|
+
| `{id}-ls` | List discovered projects (only if projectScanner defined) |
|
|
203
|
+
|
|
204
|
+
### Registration in `defineCli`
|
|
205
|
+
|
|
206
|
+
Services are registered via the `services` array:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
const cli = defineCli({
|
|
210
|
+
name: 'mycli',
|
|
211
|
+
services: [devServer, workerPool],
|
|
212
|
+
// ...
|
|
213
|
+
});
|
|
214
|
+
```
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: service-definition
|
|
3
|
+
description: "Define background services with readiness detection, preflight checks, and project scanning"
|
|
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
|
+
# Service Definition - Skill Preamble
|
|
14
|
+
|
|
15
|
+
With this skill, you can define ph-clint services — long-running background processes with lifecycle management, readiness detection, preflight validation, and automatic restart. A single `defineService()` call produces auto-generated management commands (`{id}-start`, `{id}-stop`, `{id}-restart`, `{id}-ps`, `{id}-logs`, `{id}-manage`) and integrates with the event bus for cross-cutting concerns.
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
### Services vs Processes
|
|
20
|
+
|
|
21
|
+
Services are long-running with lifecycle management (start, stop, restart, health checks). One-shot shell commands use `context.processes.run()` inside a command's execute function instead. If it runs and exits, it's a process. If it stays alive and serves requests, it's a service.
|
|
22
|
+
|
|
23
|
+
### Readiness Is a Contract
|
|
24
|
+
|
|
25
|
+
The service declares what "ready" looks like via stdout regex patterns. The framework watches stdout AND stderr combined, matching each line against the patterns. ALL patterns must match before the service transitions to `ready`. If any pattern never matches, the service hits the readiness timeout and is marked `failed`.
|
|
26
|
+
|
|
27
|
+
### Endpoint Captures Extract URLs from Regex Groups
|
|
28
|
+
|
|
29
|
+
Captures turn regex groups into named endpoints. Two forms:
|
|
30
|
+
|
|
31
|
+
- **Simple**: `{ 'name': groupIndex }` — captures the regex group as a plain string
|
|
32
|
+
- **Typed**: `{ 'name': { group: index, type: 'api-mcp' } }` — adds an `EndpointType` that enables downstream features like MCP tool discovery
|
|
33
|
+
|
|
34
|
+
Capture groups are 1-indexed (group 0 is the full match). The `type` field accepts: `'other'`, `'api-mcp'`, `'api-rest'`, `'api-graphql'`, `'website'`.
|
|
35
|
+
|
|
36
|
+
### Preflight Checks Fail Fast
|
|
37
|
+
|
|
38
|
+
Preflight checks run before the process spawns. They validate preconditions (working directory, required binaries, port availability) and fail with clear messages rather than letting a process spawn and fail cryptically. Order checks from cheapest to most expensive: workdir first, then binaries, then ports (network I/O).
|
|
39
|
+
|
|
40
|
+
### Shutdown and Restart Policies
|
|
41
|
+
|
|
42
|
+
- **Shutdown**: `{ signal, timeout }` — sends `signal` (SIGTERM, SIGINT, or SIGKILL) then waits `timeout` ms. After timeout, SIGKILL is sent unconditionally.
|
|
43
|
+
- **Restart**: `{ enabled, maxRetries, delay }` — on crash, automatically restart up to `maxRetries` times with `delay` ms between attempts.
|
|
44
|
+
|
|
45
|
+
### maxInstances Controls Concurrency
|
|
46
|
+
|
|
47
|
+
Use `maxInstances: 1` (default) for exclusive services like dev servers. Use higher values for services that can run multiple instances in parallel (e.g., worker pools).
|
|
48
|
+
|
|
49
|
+
## Service State Machine
|
|
50
|
+
|
|
51
|
+
Services transition through these states:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
idle → starting → ready → stopping → stopped
|
|
55
|
+
↘ failed → (restarting → starting)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- **idle**: Defined but never started
|
|
59
|
+
- **starting**: Process spawned, waiting for readiness patterns to match
|
|
60
|
+
- **ready**: All readiness patterns matched (or no readiness config), service is accepting connections
|
|
61
|
+
- **failed**: Readiness timeout expired, or process crashed during startup
|
|
62
|
+
- **stopping**: Shutdown signal sent, waiting for process to exit
|
|
63
|
+
- **stopped**: Process has exited (clean shutdown or after force-kill)
|
|
64
|
+
- **restarting**: Failed service with restart policy, waiting for delay before retry
|
|
65
|
+
|
|
66
|
+
Each transition emits an event via the event bus (if configured).
|
|
67
|
+
|
|
68
|
+
## Instance Identity
|
|
69
|
+
|
|
70
|
+
Every running service has an `instanceId` derived from the start parameters:
|
|
71
|
+
|
|
72
|
+
- No name, no workdir → instanceId = `serviceId`
|
|
73
|
+
- Named instance → instanceId = `serviceId:name`
|
|
74
|
+
- Workdir-derived → instanceId = `serviceId:hash8` (first 8 chars of SHA256 of workdir)
|
|
75
|
+
|
|
76
|
+
This means starting the same service in different workdirs creates separate tracked instances.
|
|
77
|
+
|
|
78
|
+
## The ServiceManager in Commands
|
|
79
|
+
|
|
80
|
+
Commands access the service manager via `context.services`:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// In a command's execute function
|
|
84
|
+
const status = context.services!.list('my-service');
|
|
85
|
+
const instanceId = await context.services!.start('my-service', { workdir: '/path' });
|
|
86
|
+
const logs = context.services!.logs('my-service', instanceId, 100);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Always null-check `context.services` — it's `undefined` when no services are configured in `defineCli`.
|
|
90
|
+
|
|
91
|
+
## Auto-Generated Management Commands
|
|
92
|
+
|
|
93
|
+
When you register a service in `defineCli({ services: [...] })`, the framework auto-generates these commands:
|
|
94
|
+
|
|
95
|
+
- `{id}-start` — Start with optional params, workdir, and instance name
|
|
96
|
+
- `{id}-stop` — Stop by instance ID (or the default instance)
|
|
97
|
+
- `{id}-restart` — Restart preserving the original workdir and params
|
|
98
|
+
- `{id}-ps` — Show status of all instances
|
|
99
|
+
- `{id}-logs` — Tail the log file (default 50 lines)
|
|
100
|
+
- `{id}-manage` — Interactive management panel (REPL only)
|
|
101
|
+
- `{id}-ls` — List discovered projects (only if `projectScanner` is defined)
|
|
102
|
+
|
|
103
|
+
You don't need to write these commands yourself — they are injected automatically.
|
|
104
|
+
|
|
105
|
+
## Common Pitfalls
|
|
106
|
+
|
|
107
|
+
- **Patterns match stdout AND stderr** — some tools (Next.js, Vite) log to stderr; your patterns will still match
|
|
108
|
+
- **Regex must match a single line** — patterns don't span lines; design them to match one log line
|
|
109
|
+
- **Capture groups are 1-indexed** — group 0 is the full match, group 1 is the first capture
|
|
110
|
+
- **No readiness config = immediately ready** — if you omit `readiness`, the service is marked ready right after spawn; use `readiness: { pattern: /.../, timeout: 30_000 }` to wait for actual readiness
|
|
111
|
+
- **Dynamic ports need dynamic preflight** — use `checkPort((ctx) => ctx.params?.port as number ?? 3000)` when ports come from params
|
|
112
|
+
- **Forgetting `checkCommand`** — the service fails cryptically if the binary isn't installed; add a preflight check with a helpful install hint
|
|
113
|
+
- **Timeout too short** — services that compile on first start (Next.js, Webpack) need longer timeouts (60-90s)
|
|
114
|
+
- **Static port in preflight but dynamic in params** — if the port comes from `paramsSchema`, use a function in `checkPort`, not a literal number
|
|
115
|
+
|
|
116
|
+
## When to Use paramsSchema
|
|
117
|
+
|
|
118
|
+
Add `paramsSchema` (a Zod schema) when the service needs runtime parameters that vary per invocation — ports, feature flags, URLs. These become flags on the auto-generated `{id}-start` command and are passed to both the `command()` and `env()` functions. Use `z.coerce.number()` for port parameters since CLI flags are always strings.
|
|
119
|
+
|
|
120
|
+
## When to Use projectScanner
|
|
121
|
+
|
|
122
|
+
Add `projectScanner` when one service definition manages multiple project directories (e.g., a dev server that can run in any project folder). The scanner enables the `{id}-ls` command for project discovery and integrates with `services.scanProjects()`. The scanner performs breadth-first search and prunes at foreign project boundaries.
|
|
123
|
+
|
|
124
|
+
## Typed Captures and MCP Discovery
|
|
125
|
+
|
|
126
|
+
Typed captures with `type: 'api-mcp'` make the captured endpoint discoverable by the agent integration. When the agent boots, it can scan service endpoints for MCP servers and automatically load their tools. This is the bridge between services and agent capabilities — you define the service, capture the MCP URL with the right type, and the agent picks it up.
|
|
127
|
+
|
|
128
|
+
## Registration in Codegen-Managed Projects
|
|
129
|
+
|
|
130
|
+
In codegen-managed projects, do NOT manually edit `cli.ts` marker regions to register services. Instead:
|
|
131
|
+
|
|
132
|
+
1. Write the service definition file in `src/services/`
|
|
133
|
+
2. Add the service to `project-spec.json`
|
|
134
|
+
3. Run `clint-project-regen` to regenerate `cli.ts` (updates `@clint:begin services`, `@clint:begin imports`, and `@clint:begin events`)
|
|
135
|
+
4. Run `clint-project-build` to verify
|
|
136
|
+
|
|
137
|
+
Import `defineService` from `../framework.js` (not from `@powerhousedao/ph-clint`) for automatic config typing.
|
|
138
|
+
|
|
139
|
+
=== END OF SKILL BRIEFING ===
|
|
140
|
+
|
|
141
|
+
## Specific tasks
|
|
142
|
+
|
|
143
|
+
* **Assess Service** [references/00.assess-service.md](references/00.assess-service.md)
|
|
144
|
+
* **Define Service** [references/01.define-service.md](references/01.define-service.md)
|
|
145
|
+
* **Add Preflight** [references/02.add-preflight.md](references/02.add-preflight.md)
|
|
146
|
+
* **Add Project Scanner** [references/03.add-project-scanner.md](references/03.add-project-scanner.md)
|
|
147
|
+
* **Wire Events** [references/04.wire-events.md](references/04.wire-events.md)
|
|
148
|
+
* **Expected Outcome** [references/expected-outcome.md](references/expected-outcome.md)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Phase 0: Assess Service Requirements
|
|
2
|
+
|
|
3
|
+
Before writing any code, identify what the service needs.
|
|
4
|
+
|
|
5
|
+
## Steps
|
|
6
|
+
|
|
7
|
+
### 1. Identify the process
|
|
8
|
+
|
|
9
|
+
What does this service run?
|
|
10
|
+
|
|
11
|
+
- **Static command**: `'pnpm dev'` — always the same
|
|
12
|
+
- **Parameterized command**: `(params) => \`pnpm dev -p ${params?.port ?? 3000}\`` — varies per invocation
|
|
13
|
+
|
|
14
|
+
### 2. Determine readiness signals
|
|
15
|
+
|
|
16
|
+
What does the process print to stdout/stderr when it's ready?
|
|
17
|
+
|
|
18
|
+
- Copy the **exact line(s)** from the process output
|
|
19
|
+
- Identify the regex that matches each line
|
|
20
|
+
- Note any URLs, ports, or other values to capture from those lines
|
|
21
|
+
- If the process prints multiple readiness signals (e.g., Connect Studio URL + Switchboard URL + MCP URL), each needs its own pattern — all must match
|
|
22
|
+
|
|
23
|
+
### 3. Identify endpoints
|
|
24
|
+
|
|
25
|
+
What endpoints does the service expose?
|
|
26
|
+
|
|
27
|
+
- [ ] HTTP REST API → `type: 'api-rest'`
|
|
28
|
+
- [ ] GraphQL API → `type: 'api-graphql'`
|
|
29
|
+
- [ ] MCP server → `type: 'api-mcp'` (enables agent tool discovery)
|
|
30
|
+
- [ ] Website/UI → `type: 'website'`
|
|
31
|
+
- [ ] Other → `type: 'other'`
|
|
32
|
+
|
|
33
|
+
For each endpoint, note which stdout line contains its URL and which regex group captures it.
|
|
34
|
+
|
|
35
|
+
### 4. Identify preflight requirements
|
|
36
|
+
|
|
37
|
+
What must be true before the process can start?
|
|
38
|
+
|
|
39
|
+
- [ ] **Working directory**: Does it need specific files? (`package.json`, `powerhouse.config.ts`, etc.)
|
|
40
|
+
- [ ] **Binaries**: Does it need installed tools? (`node`, `pnpm`, `ph`, `docker`, etc.)
|
|
41
|
+
- [ ] **Ports**: What ports does it bind to? Are they configurable?
|
|
42
|
+
|
|
43
|
+
### 5. Determine lifecycle policies
|
|
44
|
+
|
|
45
|
+
- **Shutdown**: Which signal does the process respond to? (Most Node.js processes: SIGTERM)
|
|
46
|
+
- **Restart on crash?** How many retries? What delay between attempts?
|
|
47
|
+
- **Multiple instances?** Can several run simultaneously, or is it exclusive? (`maxInstances`)
|
|
48
|
+
|
|
49
|
+
### 6. Determine runtime parameters
|
|
50
|
+
|
|
51
|
+
Does the service need per-invocation parameters?
|
|
52
|
+
|
|
53
|
+
- Ports, URLs, feature flags → define `paramsSchema` (Zod schema)
|
|
54
|
+
- Parameters become flags on the auto-generated `{id}-start` command
|
|
55
|
+
- Parameters are passed to both `command()` and `env()` functions
|
|
56
|
+
|
|
57
|
+
### 7. Determine environment variables
|
|
58
|
+
|
|
59
|
+
Does the process need environment variables derived from CLI config or params?
|
|
60
|
+
|
|
61
|
+
- `env(config, params) => Record<string, string>`
|
|
62
|
+
- Common: `PORT`, `NODE_ENV`, `HOST`, API URLs, feature flags
|
|
63
|
+
|
|
64
|
+
### 8. Check for project scanning needs
|
|
65
|
+
|
|
66
|
+
Does one service definition serve multiple project directories?
|
|
67
|
+
|
|
68
|
+
- If yes → define `projectScanner` to enable `{id}-ls` command
|
|
69
|
+
- Scanner needs `isProjectFolder(path)` predicate at minimum
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Phase 1: Write the defineService Call
|
|
2
|
+
|
|
3
|
+
Define the service with its identity, command, readiness detection, and lifecycle config.
|
|
4
|
+
|
|
5
|
+
## Steps
|
|
6
|
+
|
|
7
|
+
### 1. Set identity fields
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { defineService } from '../framework.js';
|
|
11
|
+
|
|
12
|
+
export const myService = defineService({
|
|
13
|
+
id: 'my-service', // Kebab-case — used for command names and state files
|
|
14
|
+
name: 'My Service', // Display name for logs and status output
|
|
15
|
+
description: 'Runs the development server',
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### 2. Set the command
|
|
20
|
+
|
|
21
|
+
**Static command** — always the same:
|
|
22
|
+
```typescript
|
|
23
|
+
command: 'pnpm dev',
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Parameterized command** — varies per invocation:
|
|
27
|
+
```typescript
|
|
28
|
+
command: (params) => {
|
|
29
|
+
const parts = ['pnpm', 'dev'];
|
|
30
|
+
if (params?.port) parts.push('-p', String(params.port));
|
|
31
|
+
if (params?.watch !== false) parts.push('--watch');
|
|
32
|
+
return parts.join(' ');
|
|
33
|
+
},
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 3. Add paramsSchema (if needed)
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { z } from 'zod';
|
|
40
|
+
|
|
41
|
+
const myServiceParams = z.object({
|
|
42
|
+
port: z.coerce.number().default(3000).describe('Dev server port'),
|
|
43
|
+
watch: z.boolean().default(true).describe('Enable file watching'),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const myService = defineService({
|
|
47
|
+
// ...
|
|
48
|
+
paramsSchema: myServiceParams,
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Use `z.coerce.number()` for port parameters — CLI flags are always strings.
|
|
53
|
+
|
|
54
|
+
### 4. Add environment variables
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
env: (config, params) => ({
|
|
58
|
+
PORT: String(params?.port ?? 3000),
|
|
59
|
+
NODE_ENV: 'development',
|
|
60
|
+
HOST: '0.0.0.0',
|
|
61
|
+
}),
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The `env` function receives the resolved CLI config and start params. Return a flat `Record<string, string>`.
|
|
65
|
+
|
|
66
|
+
### 5. Configure readiness detection
|
|
67
|
+
|
|
68
|
+
**Single pattern** — one line to match:
|
|
69
|
+
```typescript
|
|
70
|
+
readiness: {
|
|
71
|
+
pattern: /Local:\s*http:\/\/localhost:(\d+)/,
|
|
72
|
+
captures: { 'server-url': 1 },
|
|
73
|
+
timeout: 30_000,
|
|
74
|
+
},
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Multiple patterns** — all must match:
|
|
78
|
+
```typescript
|
|
79
|
+
readiness: {
|
|
80
|
+
patterns: [
|
|
81
|
+
{
|
|
82
|
+
name: 'web-ui',
|
|
83
|
+
pattern: /Local:\s*(http:\/\/localhost:\d+)/,
|
|
84
|
+
captures: { 'web-url': { group: 1, type: 'website' } },
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'api',
|
|
88
|
+
pattern: /API server:\s*(http:\/\/[^\s]+)/,
|
|
89
|
+
captures: { 'api-url': { group: 1, type: 'api-rest' } },
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'mcp',
|
|
93
|
+
pattern: /MCP server available at (http:\/\/[^\s]+)/,
|
|
94
|
+
captures: { 'mcp-url': { group: 1, type: 'api-mcp' } },
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
timeout: 60_000,
|
|
98
|
+
},
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Tips:
|
|
102
|
+
- Use `[^\s]+` to match URLs (stops at whitespace)
|
|
103
|
+
- Set timeout high enough for slow services (Next.js builds can take 60s+)
|
|
104
|
+
- The `name` on each pattern appears in `service:pattern-matched` events for progress feedback
|
|
105
|
+
- Use typed captures (`{ group, type }`) for endpoints that should be discoverable
|
|
106
|
+
|
|
107
|
+
### 6. Configure shutdown
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
shutdown: { signal: 'SIGTERM', timeout: 10_000 },
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The framework sends `signal` first. If the process doesn't exit within `timeout` ms, SIGKILL is sent.
|
|
114
|
+
|
|
115
|
+
### 7. Configure restart (optional)
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
restart: { enabled: true, maxRetries: 3, delay: 5_000 },
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
On crash, the service auto-restarts up to `maxRetries` times with `delay` ms between attempts.
|
|
122
|
+
|
|
123
|
+
### 8. Set maxInstances (if needed)
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
maxInstances: 1, // Default — exclusive service (e.g., dev server)
|
|
127
|
+
maxInstances: 4, // Parallel workers
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 9. Assemble the full definition
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { defineService, checkWorkdir, checkCommand, checkPort } from '../framework.js';
|
|
134
|
+
import { z } from 'zod';
|
|
135
|
+
|
|
136
|
+
const devServerParams = z.object({
|
|
137
|
+
port: z.coerce.number().default(3000).describe('Dev server port'),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
export const devServer = defineService({
|
|
141
|
+
id: 'dev-server',
|
|
142
|
+
name: 'Dev Server',
|
|
143
|
+
description: 'Next.js development server',
|
|
144
|
+
command: (params) => `pnpm dev -p ${params?.port ?? 3000}`,
|
|
145
|
+
paramsSchema: devServerParams,
|
|
146
|
+
env: (_config, params) => ({
|
|
147
|
+
PORT: String(params?.port ?? 3000),
|
|
148
|
+
NODE_ENV: 'development',
|
|
149
|
+
}),
|
|
150
|
+
preflight: [
|
|
151
|
+
checkWorkdir(
|
|
152
|
+
(cwd) => fs.existsSync(path.join(cwd, 'package.json')),
|
|
153
|
+
'Not a Node.js project',
|
|
154
|
+
'Run dev-server-start --workdir <project-dir>',
|
|
155
|
+
),
|
|
156
|
+
checkPort((ctx) => (ctx.params?.port as number) ?? 3000, 'Dev Server'),
|
|
157
|
+
],
|
|
158
|
+
readiness: {
|
|
159
|
+
pattern: /Local:\s*http:\/\/localhost:(\d+)/,
|
|
160
|
+
captures: { 'server-url': 1 },
|
|
161
|
+
timeout: 60_000,
|
|
162
|
+
},
|
|
163
|
+
shutdown: { signal: 'SIGTERM', timeout: 10_000 },
|
|
164
|
+
restart: { enabled: true, maxRetries: 3, delay: 5_000 },
|
|
165
|
+
});
|
|
166
|
+
```
|