@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,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
+ ```