@powerhousedao/ph-clint-cli 0.1.0-dev.6 → 0.1.0-dev.61

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 (101) hide show
  1. package/dist/cli.js +52 -10
  2. package/dist/codegen/actions.d.ts +1 -0
  3. package/dist/codegen/actions.js +1 -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.d.ts +1 -8
  9. package/dist/codegen/builders/cli-package-json.js +1 -96
  10. package/dist/codegen/builders/cli-ts.js +88 -37
  11. package/dist/codegen/builders/config-ts.d.ts +1 -1
  12. package/dist/codegen/builders/config-ts.js +2 -8
  13. package/dist/codegen/builders/demo-agent-ts.d.ts +8 -0
  14. package/dist/codegen/builders/demo-agent-ts.js +50 -0
  15. package/dist/codegen/builders/eslint-config-js.d.ts +1 -1
  16. package/dist/codegen/builders/eslint-config-js.js +7 -4
  17. package/dist/codegen/builders/framework-gen-ts.d.ts +11 -6
  18. package/dist/codegen/builders/framework-gen-ts.js +73 -21
  19. package/dist/codegen/builders/framework-ts.js +33 -7
  20. package/dist/codegen/builders/index.d.ts +1 -44
  21. package/dist/codegen/builders/index.js +1 -61
  22. package/dist/codegen/builders/manifest-json.d.ts +6 -0
  23. package/dist/codegen/builders/manifest-json.js +31 -0
  24. package/dist/codegen/builders/mastra-index-ts.d.ts +3 -0
  25. package/dist/codegen/builders/mastra-index-ts.js +47 -5
  26. package/dist/codegen/builders/publish-config-js.d.ts +8 -0
  27. package/dist/codegen/builders/publish-config-js.js +33 -0
  28. package/dist/codegen/builders/publish-config-ts.d.ts +4 -2
  29. package/dist/codegen/builders/publish-config-ts.js +25 -11
  30. package/dist/codegen/builders/readme-md.js +7 -6
  31. package/dist/codegen/builders/root-package-json.js +14 -11
  32. package/dist/codegen/builders/smoke-test-ts.d.ts +2 -0
  33. package/dist/codegen/builders/smoke-test-ts.js +15 -0
  34. package/dist/codegen/exec.d.ts +1 -20
  35. package/dist/codegen/exec.js +1 -41
  36. package/dist/codegen/generated.d.ts +1 -0
  37. package/dist/codegen/generated.js +1 -0
  38. package/dist/codegen/hashes.d.ts +1 -6
  39. package/dist/codegen/hashes.js +1 -60
  40. package/dist/codegen/index.d.ts +8 -36
  41. package/dist/codegen/index.js +6 -293
  42. package/dist/codegen/markers.d.ts +1 -36
  43. package/dist/codegen/markers.js +1 -86
  44. package/dist/codegen/scaffold.d.ts +1 -34
  45. package/dist/codegen/scaffold.js +1 -60
  46. package/dist/codegen/write.d.ts +1 -6
  47. package/dist/codegen/write.js +1 -32
  48. package/dist/commands/clint-project-build.d.ts +6 -1
  49. package/dist/commands/clint-project-build.js +11 -28
  50. package/dist/commands/clint-project-init.d.ts +40 -10
  51. package/dist/commands/clint-project-init.js +55 -47
  52. package/dist/commands/clint-project-publish.d.ts +7 -1
  53. package/dist/commands/clint-project-publish.js +6 -4
  54. package/dist/commands/clint-project-regen.d.ts +6 -1
  55. package/dist/commands/clint-project-regen.js +26 -2
  56. package/dist/commands/clint-skills-sync.d.ts +15 -0
  57. package/dist/commands/clint-skills-sync.js +56 -0
  58. package/dist/framework.d.ts +6 -170
  59. package/dist/framework.gen.d.ts +1 -32
  60. package/dist/framework.js +1 -5
  61. package/{prompts/agent-profiles/AgentBase.md → dist/gen/agent-profiles/PhClintDevAgent.md} +1 -1
  62. package/dist/gen/skills/cli-setup/.cli-docs.md +135 -0
  63. package/dist/gen/skills/cli-setup/SKILL.md +125 -0
  64. package/dist/gen/skills/cli-setup/references/00.assess-cli-needs.md +53 -0
  65. package/dist/gen/skills/cli-setup/references/01.define-config-schema.md +78 -0
  66. package/dist/gen/skills/cli-setup/references/02.wire-cli.md +141 -0
  67. package/dist/gen/skills/cli-setup/references/03.add-entrypoint.md +128 -0
  68. package/dist/gen/skills/cli-setup/references/expected-outcome.md +3 -0
  69. package/dist/gen/skills/command-definition/.cli-docs.md +84 -0
  70. package/dist/gen/skills/command-definition/SKILL.md +99 -0
  71. package/dist/gen/skills/command-definition/references/00.assess-requirements.md +58 -0
  72. package/dist/gen/skills/command-definition/references/01.define-schema.md +72 -0
  73. package/dist/gen/skills/command-definition/references/02.implement-execute.md +118 -0
  74. package/dist/gen/skills/command-definition/references/03.add-prompting.md +54 -0
  75. package/dist/gen/skills/command-definition/references/expected-outcome.md +3 -0
  76. package/dist/gen/skills/service-definition/.cli-docs.md +214 -0
  77. package/dist/gen/skills/service-definition/SKILL.md +148 -0
  78. package/dist/gen/skills/service-definition/references/00.assess-service.md +69 -0
  79. package/dist/gen/skills/service-definition/references/01.define-service.md +166 -0
  80. package/dist/gen/skills/service-definition/references/02.add-preflight.md +132 -0
  81. package/dist/gen/skills/service-definition/references/03.add-project-scanner.md +82 -0
  82. package/dist/gen/skills/service-definition/references/04.wire-events.md +120 -0
  83. package/dist/gen/skills/service-definition/references/expected-outcome.md +3 -0
  84. package/dist/services/clint-project.js +21 -0
  85. package/dist/skills/sync.d.ts +1 -0
  86. package/dist/skills/sync.js +1 -0
  87. package/dist/spec/ensure-document.d.ts +17 -0
  88. package/dist/spec/ensure-document.js +56 -0
  89. package/dist/spec/file.d.ts +1 -4
  90. package/dist/spec/file.js +1 -34
  91. package/dist/spec/from-document.d.ts +4 -1
  92. package/dist/spec/from-document.js +4 -16
  93. package/dist/spec/types.d.ts +1 -62
  94. package/dist/spec/types.js +1 -112
  95. package/dist/triggers/publish-trigger.d.ts +10 -0
  96. package/dist/triggers/publish-trigger.js +133 -0
  97. package/dist/triggers/spec-change.d.ts +5 -34
  98. package/dist/triggers/spec-change.js +137 -102
  99. package/package.json +12 -12
  100. package/prompts/skills-ext/.gitkeep +0 -0
  101. package/prompts/skills-tpl/.gitkeep +0 -0
@@ -0,0 +1,120 @@
1
+ # Phase 4: Wire Service Lifecycle Events
2
+
3
+ Handle service state transitions for logging, notifications, and dependent actions.
4
+
5
+ ## Steps
6
+
7
+ ### 1. Add event handlers in defineCli
8
+
9
+ Service events are wired via the `events` property in `defineCli()`:
10
+
11
+ ```typescript
12
+ import { defineCli } from '@powerhousedao/ph-clint';
13
+
14
+ const cli = defineCli({
15
+ // ...
16
+ services: [myService],
17
+ events: {
18
+ 'service:pattern-matched': (event, log) => {
19
+ log.info(`${event.name}: matched "${event.patternName}" (${event.remaining} remaining)`);
20
+ },
21
+ 'service:ready': (event, log) => {
22
+ log.info(`${event.name} is ready`);
23
+ if (event.endpoints) {
24
+ for (const [name, url] of Object.entries(event.endpoints)) {
25
+ log.info(` ${name}: ${url}`);
26
+ }
27
+ }
28
+ },
29
+ 'service:failed': (event, log) => {
30
+ log.error(`${event.name} failed: ${event.error}`);
31
+ },
32
+ 'service:restarting': (event, log) => {
33
+ log.warn(`${event.name} restarting (attempt ${event.attempt}/${event.maxRetries})`);
34
+ },
35
+ 'service:stopped': (event, log) => {
36
+ log.info(`${event.name} stopped`);
37
+ },
38
+ },
39
+ });
40
+ ```
41
+
42
+ ### 2. Understand event payloads
43
+
44
+ Each event handler receives `(event, log)`:
45
+ - `event` — typed payload with `id`, `instanceId`, `name`, and event-specific fields
46
+ - `log` — structured Logger with `debug`, `info`, `warn`, `error` methods
47
+
48
+ **`service:pattern-matched`** — fired progressively as each readiness pattern matches:
49
+ - `event.patternName` — which pattern matched
50
+ - `event.endpoints` — captured endpoints so far
51
+ - `event.endpointTypes` — endpoint types so far
52
+ - `event.remaining` — number of patterns still unmatched
53
+
54
+ **`service:ready`** — fired when ALL patterns have matched:
55
+ - `event.endpoints` — all captured endpoints
56
+ - `event.endpointTypes` — all endpoint types
57
+
58
+ **`service:failed`** — fired on readiness timeout or process crash:
59
+ - `event.error` — error message (includes log tail for diagnosis)
60
+
61
+ **`service:restarting`** — fired when auto-restart triggers:
62
+ - `event.attempt` — current attempt number
63
+ - `event.maxRetries` — maximum attempts configured
64
+
65
+ **`service:stopped`** — fired after explicit stop completes
66
+
67
+ ### 3. Common event patterns
68
+
69
+ **Log endpoints on ready**:
70
+ ```typescript
71
+ 'service:ready': (event, log) => {
72
+ const urls = Object.entries(event.endpoints ?? {})
73
+ .map(([name, url]) => ` ${name}: ${url}`)
74
+ .join('\n');
75
+ log.info(`${event.name} ready\n${urls}`);
76
+ },
77
+ ```
78
+
79
+ **Trigger dependent service startup**:
80
+ ```typescript
81
+ 'service:ready': async (event, log) => {
82
+ if (event.id === 'backend') {
83
+ log.info('Backend ready, starting frontend...');
84
+ // Frontend can now connect to the backend URL
85
+ }
86
+ },
87
+ ```
88
+
89
+ **Progress feedback during startup**:
90
+ ```typescript
91
+ 'service:pattern-matched': (event, log) => {
92
+ if (event.remaining > 0) {
93
+ log.info(`${event.name}: ${event.patternName} ready, waiting for ${event.remaining} more...`);
94
+ }
95
+ },
96
+ ```
97
+
98
+ ### 4. Register the service in defineCli
99
+
100
+ **In a codegen-managed project** (cli.ts has `@clint:begin` markers):
101
+
102
+ Do NOT manually edit `cli.ts` marker regions. Instead:
103
+
104
+ 1. Add the service to `project-spec.json`
105
+ 2. Run `clint-project-regen` — this regenerates the `@clint:begin services`, `@clint:begin imports`, and `@clint:begin events` marker regions
106
+ 3. Run `clint-project-build` to verify TypeScript compiles
107
+
108
+ **In a manually-managed project** (no markers):
109
+
110
+ ```typescript
111
+ import { myService } from './services/my-service.js';
112
+
113
+ const cli = defineCli({
114
+ services: [myService],
115
+ events: { /* ... */ },
116
+ // ...
117
+ });
118
+ ```
119
+
120
+ This auto-injects the management commands: `{id}-start`, `{id}-stop`, `{id}-restart`, `{id}-ps`, `{id}-logs`, `{id}-manage`, and `{id}-ls` (if projectScanner is defined).
@@ -0,0 +1,3 @@
1
+ ## Expected Outcome
2
+
3
+ Service is defined with readiness detection, preflight checks, and lifecycle config. It is registered in `defineCli`, auto-generates management commands (`{id}-start`, `{id}-stop`, etc.), and transitions through starting/ready/failed states correctly. Endpoints are captured and typed. Preflight checks fail fast with clear messages and hints.
@@ -31,6 +31,11 @@ export const clintProject = defineService({
31
31
  ],
32
32
  projectScanner: {
33
33
  isProjectFolder: (p) => {
34
+ // Match if a project-spec.json exists (split-layout root)
35
+ const specPath = path.join(p, '.ph', 'ph-clint-cli', 'project-spec.json');
36
+ if (fs.existsSync(specPath))
37
+ return true;
38
+ // Match if package.json has ph-clint as a dependency (flat layout or cli subfolder)
34
39
  const pkg = path.join(p, 'package.json');
35
40
  if (!fs.existsSync(pkg))
36
41
  return false;
@@ -42,6 +47,22 @@ export const clintProject = defineService({
42
47
  return false;
43
48
  }
44
49
  },
50
+ getDocumentLink: (p) => {
51
+ const specPath = path.join(p, '.ph', 'ph-clint-cli', 'project-spec.json');
52
+ if (!fs.existsSync(specPath))
53
+ return undefined;
54
+ try {
55
+ const spec = JSON.parse(fs.readFileSync(specPath, 'utf8'));
56
+ if (spec.documentId) {
57
+ return {
58
+ documentId: spec.documentId,
59
+ documentType: spec.documentType ?? 'powerhouse/ph-clint-project',
60
+ };
61
+ }
62
+ }
63
+ catch { /* noop */ }
64
+ return undefined;
65
+ },
45
66
  },
46
67
  restart: { enabled: false, maxRetries: 0, delay: 0 },
47
68
  });
@@ -0,0 +1 @@
1
+ export { type ManifestEntry, type SkillsManifest, type SyncResult, type SyncOptions, readManifest, diffSkills, syncExternalSkills, } from '@powerhousedao/ph-clint-dev/skills/sync';
@@ -0,0 +1 @@
1
+ export { readManifest, diffSkills, syncExternalSkills, } from '@powerhousedao/ph-clint-dev/skills/sync';
@@ -0,0 +1,17 @@
1
+ import type { FolderOperations } from '@powerhousedao/ph-clint';
2
+ import type { ReactorContext, DocumentRegistry } from '@powerhousedao/ph-clint';
3
+ import type { ClintProjectSpec } from './types.js';
4
+ export interface EnsureSpecDocumentResult {
5
+ docId: string;
6
+ created: boolean;
7
+ }
8
+ /**
9
+ * Ensure the spec document exists in the personal drive. Creates one if
10
+ * missing or if the existing `documentId` no longer resolves to a drive node.
11
+ */
12
+ export declare function ensureSpecDocument<R extends DocumentRegistry = DocumentRegistry>(opts: {
13
+ spec: ClintProjectSpec;
14
+ targetDir: string;
15
+ reactor: ReactorContext<R>;
16
+ folders: FolderOperations;
17
+ }): Promise<EnsureSpecDocumentResult>;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Ensure a ph-clint-project spec document exists in the personal drive.
3
+ *
4
+ * Single code path used by:
5
+ * - `clint-project-init` (after scaffolding)
6
+ * - `clint-project-regen` (recover deleted docs)
7
+ * - `spec-change` trigger (auto-link unlinked projects)
8
+ *
9
+ * If the spec already has a `documentId` that exists in the drive, this is
10
+ * a no-op. If the document was deleted (or never created), a new one is
11
+ * created, added to the drive at `specs/{name}`, and the spec JSON on disk
12
+ * is updated with the new `documentId`.
13
+ */
14
+ import { importSpec } from '@powerhousedao/ph-clint-app/document-models/ph-clint-project';
15
+ import { writeProjectSpec } from './file.js';
16
+ import { specToImportInput } from '../triggers/spec-change.js';
17
+ const DOCUMENT_TYPE = 'powerhouse/ph-clint-project';
18
+ /**
19
+ * Ensure the spec document exists in the personal drive. Creates one if
20
+ * missing or if the existing `documentId` no longer resolves to a drive node.
21
+ */
22
+ export async function ensureSpecDocument(opts) {
23
+ const { spec, targetDir, reactor, folders } = opts;
24
+ // Check if the document already exists in the drive's folder tree
25
+ if (spec.documentId) {
26
+ const entries = await folders.findByType(DOCUMENT_TYPE);
27
+ const inDrive = entries.some((e) => e.id === spec.documentId);
28
+ if (inDrive)
29
+ return { docId: spec.documentId, created: false };
30
+ }
31
+ let docId;
32
+ if (spec.documentId) {
33
+ try {
34
+ await reactor.client.get(spec.documentId);
35
+ docId = spec.documentId;
36
+ // Orphan recovery: doc exists in reactor but not in drive. Re-attach via
37
+ // addDocument; we can't retrofit CREATE/UPGRADE into the drive's stream.
38
+ await folders.addDocument(docId, `specs/${spec.name}`, spec.name);
39
+ }
40
+ catch {
41
+ const created = await folders.createDocument(DOCUMENT_TYPE, `specs/${spec.name}`, spec.name);
42
+ docId = created.documentId;
43
+ }
44
+ }
45
+ else {
46
+ const created = await folders.createDocument(DOCUMENT_TYPE, `specs/${spec.name}`, spec.name);
47
+ docId = created.documentId;
48
+ }
49
+ const importInput = specToImportInput(spec);
50
+ await reactor.client.execute(docId, 'main', [importSpec(importInput)]);
51
+ // Persist the link back to disk
52
+ spec.documentId = docId;
53
+ spec.documentType = DOCUMENT_TYPE;
54
+ await writeProjectSpec(targetDir, spec);
55
+ return { docId, created: true };
56
+ }
@@ -1,4 +1 @@
1
- import { type ClintProjectSpec } from './types.js';
2
- export declare function getSpecPath(projectDir: string): string;
3
- export declare function readProjectSpec(projectDir: string): Promise<ClintProjectSpec | null>;
4
- export declare function writeProjectSpec(projectDir: string, spec: ClintProjectSpec): Promise<void>;
1
+ export { getSpecPath, readProjectSpec, writeProjectSpec, } from '@powerhousedao/ph-clint-dev/spec/file';
package/dist/spec/file.js CHANGED
@@ -1,34 +1 @@
1
- /**
2
- * Persistence for `ClintProjectSpec`.
3
- * Stored at `{impl-project}/.ph/ph-clint-cli/project-spec.json`.
4
- */
5
- import fs from 'node:fs/promises';
6
- import path from 'node:path';
7
- import { clintProjectSpecSchema, } from './types.js';
8
- const SPEC_DIR = path.join('.ph', 'ph-clint-cli');
9
- const SPEC_FILE = 'project-spec.json';
10
- export function getSpecPath(projectDir) {
11
- return path.join(projectDir, SPEC_DIR, SPEC_FILE);
12
- }
13
- function isEnoent(err) {
14
- return (!!err &&
15
- typeof err === 'object' &&
16
- 'code' in err &&
17
- err.code === 'ENOENT');
18
- }
19
- export async function readProjectSpec(projectDir) {
20
- try {
21
- const raw = await fs.readFile(getSpecPath(projectDir), 'utf8');
22
- return clintProjectSpecSchema.parse(JSON.parse(raw));
23
- }
24
- catch (err) {
25
- if (isEnoent(err))
26
- return null;
27
- throw err;
28
- }
29
- }
30
- export async function writeProjectSpec(projectDir, spec) {
31
- const file = getSpecPath(projectDir);
32
- await fs.mkdir(path.dirname(file), { recursive: true });
33
- await fs.writeFile(file, JSON.stringify(spec, null, 2) + '\n', 'utf8');
34
- }
1
+ export { getSpecPath, readProjectSpec, writeProjectSpec, } from '@powerhousedao/ph-clint-dev/spec/file';
@@ -15,4 +15,7 @@ import { type ClintProjectSpec } from './types.js';
15
15
  * Returns `null` if the document has no valid package name yet — callers
16
16
  * should wait for the user to populate it before triggering codegen.
17
17
  */
18
- export declare function specFromDocumentState(state: PhClintProjectGlobalState): ClintProjectSpec | null;
18
+ export declare function specFromDocumentState(state: PhClintProjectGlobalState, meta?: {
19
+ documentId?: string;
20
+ documentType?: string;
21
+ }): ClintProjectSpec | null;
@@ -5,26 +5,14 @@ import { clintProjectSpecSchema, } from './types.js';
5
5
  * Returns `null` if the document has no valid package name yet — callers
6
6
  * should wait for the user to populate it before triggering codegen.
7
7
  */
8
- export function specFromDocumentState(state) {
8
+ export function specFromDocumentState(state, meta) {
9
9
  if (!state.name)
10
10
  return null;
11
- // `documentTypes` is a forward-compatible field — older `ph-clint-project`
12
- // document states don't declare it yet. Pull through if present, otherwise
13
- // leave it to the schema default (`[]`). Codegen treats the empty list as
14
- // "no registry entries" and emits an empty `defineRegistry([...])`.
15
- const documentTypes = state.documentTypes ?? undefined;
16
11
  const parsed = clintProjectSpecSchema.safeParse({
17
- name: state.name,
12
+ ...state,
18
13
  scope: state.scope ?? undefined,
19
- version: state.version,
20
- description: state.description,
21
- bin: state.bin ?? undefined,
22
- features: {
23
- powerhouse: { ...state.features.powerhouse },
24
- mastra: { ...state.features.mastra },
25
- routine: { ...state.features.routine },
26
- },
27
- ...(Array.isArray(documentTypes) ? { documentTypes } : {}),
14
+ documentId: meta?.documentId,
15
+ documentType: meta?.documentType,
28
16
  });
29
17
  return parsed.success ? parsed.data : null;
30
18
  }
@@ -1,62 +1 @@
1
- /**
2
- * ClintProjectSpec — the in-memory shape that describes a ph-clint
3
- * implementation project. Persisted at
4
- * `{impl-project}/.ph/ph-clint-cli/project-spec.json` and consumed by
5
- * the code generator (src/codegen/).
6
- *
7
- * Intentionally minimal. Richer structure (services, skills, agents,
8
- * triggers, document-models) gets added in later phases as each
9
- * generator step actually needs it.
10
- */
11
- import { z } from 'zod';
12
- export declare const clintProjectSpecSchema: z.ZodObject<{
13
- name: z.ZodString;
14
- scope: z.ZodOptional<z.ZodString>;
15
- version: z.ZodDefault<z.ZodString>;
16
- description: z.ZodDefault<z.ZodString>;
17
- bin: z.ZodOptional<z.ZodString>;
18
- features: z.ZodDefault<z.ZodObject<{
19
- powerhouse: z.ZodDefault<z.ZodObject<{
20
- enabled: z.ZodDefault<z.ZodBoolean>;
21
- switchboard: z.ZodDefault<z.ZodBoolean>;
22
- connect: z.ZodDefault<z.ZodBoolean>;
23
- }, z.core.$strip>>;
24
- mastra: z.ZodDefault<z.ZodObject<{
25
- enabled: z.ZodDefault<z.ZodBoolean>;
26
- }, z.core.$strip>>;
27
- routine: z.ZodDefault<z.ZodObject<{
28
- enabled: z.ZodDefault<z.ZodBoolean>;
29
- }, z.core.$strip>>;
30
- }, z.core.$strip>>;
31
- documentTypes: z.ZodDefault<z.ZodArray<z.ZodString>>;
32
- }, z.core.$strip>;
33
- export type ClintProjectSpec = z.infer<typeof clintProjectSpecSchema>;
34
- export type ClintProjectSpecInput = z.input<typeof clintProjectSpecSchema>;
35
- /** npm package name (e.g. `@scope/foo` or `foo`). */
36
- export declare function getPackageName(spec: ClintProjectSpec): string;
37
- /** bin name (defaults to the bare project name). */
38
- export declare function getBinName(spec: ClintProjectSpec): string;
39
- /**
40
- * Name of the CLI sub-folder when the project is split (Powerhouse enabled).
41
- * For flat projects this is irrelevant — callers use the project root directly.
42
- */
43
- export declare function getCliFolderName(spec: ClintProjectSpec): string;
44
- /**
45
- * Name of the reactor-package sub-folder when the project is split.
46
- */
47
- export declare function getAppFolderName(spec: ClintProjectSpec): string;
48
- /**
49
- * Folder slug for a documentType ID. The slug is the portion after the last
50
- * `/` (e.g. `powerhouse/ph-clint-project` → `ph-clint-project`). Matches the
51
- * convention used by Powerhouse's reactor-package codegen — each document
52
- * model lives at `document-models/<slug>/`.
53
- */
54
- export declare function getDocumentTypeSlug(documentType: string): string;
55
- /**
56
- * PascalCase export name for a documentType ID. Powerhouse's module
57
- * generator exports each module as `PascalCase(<slug>)` (e.g.
58
- * `powerhouse/ph-clint-project` → `PhClintProject`). The top-level reactor
59
- * package re-exports these names, so this helper gives codegen the string
60
- * it needs both for the import and the `defineRegistry` entry.
61
- */
62
- export declare function getDocumentTypeModuleName(documentType: string): string;
1
+ export { POWERHOUSE_LEVELS, powerhouseLevelSchema, type PowerhouseLevel, phAtLeast, agentModelSchema, type AgentModel, agentProfileSchema, type AgentProfile, powerhousePackageSchema, type PowerhousePackage, externalSkillSchema, type ExternalSkill, clintProjectSpecSchema, type ClintProjectSpec, type ClintProjectSpecInput, getPackageName, getBinName, getCliFolderName, getAppFolderName, getAllDocumentTypes, getAppPackageName, getAppDirName, getDocumentTypeSlug, getDocumentTypeModuleName, isDocTypeGlob, docTypeGlobToRegex, } from '@powerhousedao/ph-clint-dev/spec';
@@ -1,112 +1 @@
1
- /**
2
- * ClintProjectSpec — the in-memory shape that describes a ph-clint
3
- * implementation project. Persisted at
4
- * `{impl-project}/.ph/ph-clint-cli/project-spec.json` and consumed by
5
- * the code generator (src/codegen/).
6
- *
7
- * Intentionally minimal. Richer structure (services, skills, agents,
8
- * triggers, document-models) gets added in later phases as each
9
- * generator step actually needs it.
10
- */
11
- import { z } from 'zod';
12
- const powerhouseFeatureSchema = z.object({
13
- enabled: z.boolean().default(false),
14
- switchboard: z.boolean().default(true),
15
- connect: z.boolean().default(true),
16
- });
17
- const mastraFeatureSchema = z.object({
18
- enabled: z.boolean().default(false),
19
- });
20
- const routineFeatureSchema = z.object({
21
- enabled: z.boolean().default(false),
22
- });
23
- // Zod 4 does not cascade `.default({})` through nested object schemas — a
24
- // missing outer key fills in `{}`, but nested keys with their own `.default`
25
- // are not re-defaulted unless the object itself re-parses. We spell out full
26
- // defaults here so `clintProjectSpecSchema.parse({ name: 'x' })` yields a
27
- // fully-populated feature tree.
28
- const DEFAULT_POWERHOUSE = {
29
- enabled: false,
30
- switchboard: true,
31
- connect: true,
32
- };
33
- const DEFAULT_MASTRA = { enabled: false };
34
- const DEFAULT_ROUTINE = { enabled: false };
35
- export const clintProjectSpecSchema = z.object({
36
- name: z
37
- .string()
38
- .regex(/^[a-z0-9][a-z0-9-]*$/, 'lowercase letters, digits, hyphens only'),
39
- scope: z
40
- .string()
41
- .regex(/^[a-z0-9][a-z0-9-]*$/, 'lowercase letters, digits, hyphens only')
42
- .optional(),
43
- version: z.string().default('0.1.0'),
44
- description: z.string().default(''),
45
- bin: z.string().optional(),
46
- features: z
47
- .object({
48
- powerhouse: powerhouseFeatureSchema.default(DEFAULT_POWERHOUSE),
49
- mastra: mastraFeatureSchema.default(DEFAULT_MASTRA),
50
- routine: routineFeatureSchema.default(DEFAULT_ROUTINE),
51
- })
52
- .default({
53
- powerhouse: DEFAULT_POWERHOUSE,
54
- mastra: DEFAULT_MASTRA,
55
- routine: DEFAULT_ROUTINE,
56
- }),
57
- /**
58
- * Document types registered with this CLI — full documentType ID strings
59
- * (e.g. `powerhouse/ph-clint-project`). Drives codegen of `src/framework.gen.ts`,
60
- * which emits a typed `defineRegistry([...] as const)` call so impl code
61
- * gets narrowed `reactor.client.get(id, 'my-type')` access. Empty on
62
- * fresh projects — populated as document models are added to the reactor
63
- * package.
64
- */
65
- documentTypes: z.array(z.string()).default([]),
66
- });
67
- /** npm package name (e.g. `@scope/foo` or `foo`). */
68
- export function getPackageName(spec) {
69
- return spec.scope ? `@${spec.scope}/${spec.name}` : spec.name;
70
- }
71
- /** bin name (defaults to the bare project name). */
72
- export function getBinName(spec) {
73
- return spec.bin ?? spec.name;
74
- }
75
- /**
76
- * Name of the CLI sub-folder when the project is split (Powerhouse enabled).
77
- * For flat projects this is irrelevant — callers use the project root directly.
78
- */
79
- export function getCliFolderName(spec) {
80
- return `${spec.name}-cli`;
81
- }
82
- /**
83
- * Name of the reactor-package sub-folder when the project is split.
84
- */
85
- export function getAppFolderName(spec) {
86
- return `${spec.name}-app`;
87
- }
88
- /**
89
- * Folder slug for a documentType ID. The slug is the portion after the last
90
- * `/` (e.g. `powerhouse/ph-clint-project` → `ph-clint-project`). Matches the
91
- * convention used by Powerhouse's reactor-package codegen — each document
92
- * model lives at `document-models/<slug>/`.
93
- */
94
- export function getDocumentTypeSlug(documentType) {
95
- const lastSlash = documentType.lastIndexOf('/');
96
- return lastSlash >= 0 ? documentType.slice(lastSlash + 1) : documentType;
97
- }
98
- /**
99
- * PascalCase export name for a documentType ID. Powerhouse's module
100
- * generator exports each module as `PascalCase(<slug>)` (e.g.
101
- * `powerhouse/ph-clint-project` → `PhClintProject`). The top-level reactor
102
- * package re-exports these names, so this helper gives codegen the string
103
- * it needs both for the import and the `defineRegistry` entry.
104
- */
105
- export function getDocumentTypeModuleName(documentType) {
106
- const slug = getDocumentTypeSlug(documentType);
107
- return slug
108
- .split(/[-_]/)
109
- .filter((s) => s.length > 0)
110
- .map((s) => s[0].toUpperCase() + s.slice(1))
111
- .join('');
112
- }
1
+ export { POWERHOUSE_LEVELS, powerhouseLevelSchema, phAtLeast, agentModelSchema, agentProfileSchema, powerhousePackageSchema, externalSkillSchema, clintProjectSpecSchema, getPackageName, getBinName, getCliFolderName, getAppFolderName, getAllDocumentTypes, getAppPackageName, getAppDirName, getDocumentTypeSlug, getDocumentTypeModuleName, isDocTypeGlob, docTypeGlobToRegex, } from '@powerhousedao/ph-clint-dev/spec';
@@ -0,0 +1,10 @@
1
+ /** Map document model PublishTag values to CLI --tag values. */
2
+ export declare function tagToCli(tag: string): 'dev' | 'staging' | 'production' | null;
3
+ export declare const publishTrigger: import("@powerhousedao/ph-clint").Trigger<{
4
+ pending: number;
5
+ }, {
6
+ model: string;
7
+ devServicePort: number;
8
+ phVersion: string;
9
+ apiKey?: string | undefined;
10
+ }, import("@powerhousedao/ph-clint").InferRegistry<readonly [import("document-model").DocumentModelModule<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>]>>;
@@ -0,0 +1,133 @@
1
+ /**
2
+ * `publish-trigger` — watches ALL `powerhouse/ph-clint-project` documents for
3
+ * publish records with `Pending` status and invokes the publish pipeline.
4
+ *
5
+ * Unlike `spec-change`, this trigger does NOT convert to a `ClintProjectSpec`.
6
+ * It reads `publishHistory` directly from the document state, since publish
7
+ * records are a doc-model concern (not a codegen input).
8
+ *
9
+ * Uses the multi-doc path (no `documentId`), scanning all matching documents
10
+ * on each poll. For each document with a pending publish record, it uses the
11
+ * project mapping to resolve the on-disk project directory.
12
+ *
13
+ * Flow:
14
+ * 1. On document change, scans `publishHistory` for records with status
15
+ * `"Pending"`.
16
+ * 2. For each pending record, returns a work item that:
17
+ * a. Dispatches SET_PUBLISH_STATUS({ id, status: "InProgress" }).
18
+ * b. Runs `ph-clint clint-project-publish --tag <tag>`.
19
+ * c. Dispatches SET_PUBLISH_STATUS({ id, status: "Succeeded"|"Failed" }).
20
+ */
21
+ import path from 'node:path';
22
+ import { scanProjects, getProjectMapping } from '@powerhousedao/ph-clint';
23
+ import { setPublishStatus } from '@powerhousedao/ph-clint-app';
24
+ import { createDocumentChangeTrigger } from '../framework.js';
25
+ import { specFromDocumentState } from '../spec/from-document.js';
26
+ import { clintProject } from '../services/clint-project.js';
27
+ const DOCUMENT_TYPE = 'powerhouse/ph-clint-project';
28
+ const TAG = '[publish]';
29
+ /** Map document model PublishTag values to CLI --tag values. */
30
+ export function tagToCli(tag) {
31
+ switch (tag) {
32
+ case 'Dev':
33
+ return 'dev';
34
+ case 'Staging':
35
+ return 'staging';
36
+ case 'Production':
37
+ return 'production';
38
+ default:
39
+ return null;
40
+ }
41
+ }
42
+ export const publishTrigger = createDocumentChangeTrigger({
43
+ id: 'publish-trigger',
44
+ documentType: DOCUMENT_TYPE,
45
+ // No documentId → watches ALL ph-clint-project docs
46
+ // Don't fire on startup — only react to new pending records.
47
+ initialReconcile: false,
48
+ async onChange(docs, ctx) {
49
+ const log = ctx.context.log;
50
+ const folders = ctx.context.folders;
51
+ // Build the mapping to resolve on-disk project directories
52
+ const scanner = clintProject.projectScanner;
53
+ const scanResults = scanner
54
+ ? scanProjects(ctx.context.workdir, scanner)
55
+ : [];
56
+ const mapping = await getProjectMapping(scanResults, folders);
57
+ for (const doc of docs) {
58
+ const publishHistory = doc.state.global.publishHistory ?? [];
59
+ const pending = publishHistory.filter((r) => r.status === 'Pending');
60
+ if (pending.length === 0)
61
+ continue;
62
+ // Resolve the on-disk target directory for this document
63
+ const spec = specFromDocumentState(doc.state.global, {
64
+ documentId: doc.header.id,
65
+ documentType: doc.header.documentType,
66
+ });
67
+ const entry = mapping.find((m) => m.documentId === doc.header.id);
68
+ const workdir = entry?.path
69
+ ?? (spec ? path.join(ctx.context.workdir, spec.name) : ctx.context.workdir);
70
+ // Process the first pending record (one per tick to avoid races).
71
+ const record = pending[0];
72
+ const cliTag = tagToCli(record.tag);
73
+ if (!cliTag) {
74
+ log?.warn(`${TAG} unknown publish tag "${record.tag}" for record ${record.id}`);
75
+ continue;
76
+ }
77
+ log?.info(`${TAG} found pending ${record.tag} publish for doc ${doc.header.id} (id=${record.id}, version=${record.version})`);
78
+ const { runProcess, stdout } = ctx.context;
79
+ return {
80
+ type: 'function',
81
+ params: {
82
+ fn: async () => {
83
+ const reactor = await ctx.reactor();
84
+ if (!reactor) {
85
+ log?.error(`${TAG} reactor not available, cannot update publish status`);
86
+ return;
87
+ }
88
+ // Mark as InProgress.
89
+ log?.info(`${TAG} marking record ${record.id} as InProgress`);
90
+ await reactor.client.execute(doc.header.id, 'main', [
91
+ setPublishStatus({ id: record.id, status: 'InProgress' }),
92
+ ]);
93
+ // Install dependencies in the generated project before publishing.
94
+ stdout(`${TAG} running pnpm install in ${workdir}\n`);
95
+ const installResult = await runProcess('pnpm install', {
96
+ cwd: workdir,
97
+ timeout: 300_000,
98
+ });
99
+ if (!installResult.success) {
100
+ log?.error(`${TAG} pnpm install failed in ${workdir}`);
101
+ }
102
+ // Run the publish command.
103
+ let success = false;
104
+ try {
105
+ stdout(`${TAG} invoking clint-project-publish --tag ${cliTag}\n`);
106
+ const { clintProjectPublish } = await import('../commands/clint-project-publish.js');
107
+ const result = await clintProjectPublish.execute({ tag: cliTag, dir: workdir, dryRun: false, skipBuild: false, skipGitCheck: true, verbose: false }, ctx.commandContext);
108
+ success = !result.text.includes('failed');
109
+ }
110
+ catch (err) {
111
+ log?.error(`${TAG} publish failed: ${err instanceof Error ? err.message : String(err)}`);
112
+ }
113
+ // Update status.
114
+ const finalStatus = success ? 'Succeeded' : 'Failed';
115
+ stdout(`${TAG} marking record ${record.id} as ${finalStatus}\n`);
116
+ await reactor.client.execute(doc.header.id, 'main', [
117
+ setPublishStatus({
118
+ id: record.id,
119
+ status: finalStatus,
120
+ }),
121
+ ]);
122
+ },
123
+ },
124
+ callbacks: {
125
+ onFailure: (err) => {
126
+ log?.error(`${TAG} publish trigger failed: ${err.message}`);
127
+ },
128
+ },
129
+ };
130
+ }
131
+ return null;
132
+ },
133
+ });