@powerhousedao/ph-clint-cli 0.1.0-dev.4 → 0.1.0-dev.40

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 (90) hide show
  1. package/dist/cli.js +51 -12
  2. package/dist/codegen/actions.d.ts +42 -0
  3. package/dist/codegen/actions.js +372 -0
  4. package/dist/codegen/builders/agent-ts.d.ts +4 -4
  5. package/dist/codegen/builders/agent-ts.js +83 -2
  6. package/dist/codegen/builders/app-index-ts.d.ts +3 -10
  7. package/dist/codegen/builders/app-index-ts.js +10 -14
  8. package/dist/codegen/builders/cli-package-json.js +65 -8
  9. package/dist/codegen/builders/cli-ts.js +65 -24
  10. package/dist/codegen/builders/config-ts.js +11 -15
  11. package/dist/codegen/builders/demo-agent-ts.d.ts +8 -0
  12. package/dist/codegen/builders/demo-agent-ts.js +44 -0
  13. package/dist/codegen/builders/eslint-config-js.d.ts +1 -1
  14. package/dist/codegen/builders/eslint-config-js.js +7 -4
  15. package/dist/codegen/builders/framework-gen-ts.d.ts +4 -5
  16. package/dist/codegen/builders/framework-gen-ts.js +21 -18
  17. package/dist/codegen/builders/framework-ts.js +33 -7
  18. package/dist/codegen/builders/index.d.ts +10 -1
  19. package/dist/codegen/builders/index.js +30 -5
  20. package/dist/codegen/builders/manifest-json.d.ts +6 -0
  21. package/dist/codegen/builders/manifest-json.js +31 -0
  22. package/dist/codegen/builders/mastra-index-ts.d.ts +3 -0
  23. package/dist/codegen/builders/mastra-index-ts.js +47 -5
  24. package/dist/codegen/builders/publish-config-js.d.ts +8 -0
  25. package/dist/codegen/builders/publish-config-js.js +32 -0
  26. package/dist/codegen/builders/publish-config-ts.d.ts +8 -0
  27. package/dist/codegen/builders/publish-config-ts.js +32 -0
  28. package/dist/codegen/builders/readme-md.js +7 -6
  29. package/dist/codegen/builders/root-package-json.d.ts +3 -0
  30. package/dist/codegen/builders/root-package-json.js +26 -11
  31. package/dist/codegen/builders/smoke-test-ts.d.ts +2 -0
  32. package/dist/codegen/builders/smoke-test-ts.js +15 -0
  33. package/dist/codegen/generated.d.ts +17 -0
  34. package/dist/codegen/generated.js +49 -0
  35. package/dist/codegen/index.d.ts +3 -0
  36. package/dist/codegen/index.js +139 -14
  37. package/dist/codegen/scaffold.d.ts +31 -3
  38. package/dist/codegen/scaffold.js +114 -16
  39. package/dist/commands/clint-project-build.js +10 -27
  40. package/dist/commands/clint-project-init.d.ts +30 -6
  41. package/dist/commands/clint-project-init.js +45 -33
  42. package/dist/commands/clint-project-publish.d.ts +1 -0
  43. package/dist/commands/clint-project-publish.js +5 -3
  44. package/dist/commands/clint-project-regen.js +25 -1
  45. package/dist/commands/clint-skills-sync.d.ts +10 -0
  46. package/dist/commands/clint-skills-sync.js +56 -0
  47. package/dist/config.d.ts +3 -5
  48. package/dist/config.js +5 -7
  49. package/dist/framework.d.ts +5 -169
  50. package/dist/framework.gen.d.ts +1 -32
  51. package/dist/framework.js +0 -4
  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/services/clint-project.js +21 -0
  76. package/dist/skills/sync.d.ts +40 -0
  77. package/dist/skills/sync.js +119 -0
  78. package/dist/spec/ensure-document.d.ts +17 -0
  79. package/dist/spec/ensure-document.js +62 -0
  80. package/dist/spec/from-document.d.ts +4 -1
  81. package/dist/spec/from-document.js +18 -8
  82. package/dist/spec/types.d.ts +85 -6
  83. package/dist/spec/types.js +90 -23
  84. package/dist/triggers/publish-trigger.d.ts +10 -0
  85. package/dist/triggers/publish-trigger.js +169 -0
  86. package/dist/triggers/spec-change.d.ts +5 -34
  87. package/dist/triggers/spec-change.js +130 -102
  88. package/package.json +7 -7
  89. package/prompts/skills-ext/.gitkeep +0 -0
  90. package/prompts/skills-tpl/.gitkeep +0 -0
@@ -3,29 +3,25 @@
3
3
  * reactor package. Impl code imports from `'{name}-app'` instead of drilling
4
4
  * into `'{name}-app/document-models/<slug>/v1/gen/...'`.
5
5
  *
6
- * Each entry re-exports a document-model subfolder's `index.ts`, which in
7
- * turn re-exports the module object (`PhClintProject`, ...), action creators,
8
- * document/state types, and type guards. Fully machine-owned: regen on every
9
- * run.
10
- *
11
- * Powerhouse's own codegen might emit its own `index.ts` (as is the case for
12
- * ph-clint-app today, which has manifest/editors/processors re-exports). When
13
- * that exists, leave it alone — users hand-merge. Our codegen only emits when
14
- * the file is absent or was emitted by a previous ph-clint-cli run (hash
15
- * matches the ledger).
6
+ * Only re-exports document types belonging to the app package (the project's
7
+ * own reactor package). External packages' document types are imported from
8
+ * their own npm packages, not from the app barrel.
16
9
  */
17
- import { getDocumentTypeSlug, } from '../../spec/types.js';
10
+ import { getAppPackageName, getDocumentTypeSlug, phAtLeast, } from '../../spec/types.js';
18
11
  export function buildAppIndexTs(spec) {
19
- if (!spec.features.powerhouse.enabled)
12
+ if (!phAtLeast(spec.features.powerhouse, 'Reactor'))
20
13
  return null;
21
- if (spec.documentTypes.length === 0)
14
+ const appPkg = getAppPackageName(spec);
15
+ const appEntry = spec.packages.find((p) => p.packageName === appPkg);
16
+ const docTypes = appEntry?.documentTypes ?? [];
17
+ if (docTypes.length === 0)
22
18
  return null;
23
19
  const lines = [];
24
20
  lines.push('// THIS FILE IS GENERATED BY ph-clint-cli.');
25
21
  lines.push('// Re-exports every registered document model from a single top-level');
26
22
  lines.push('// entry so impl code can `import { ... } from \'{name}-app\'`.');
27
23
  lines.push('');
28
- for (const docType of spec.documentTypes) {
24
+ for (const docType of docTypes) {
29
25
  const slug = getDocumentTypeSlug(docType);
30
26
  lines.push(`export * from './document-models/${slug}/index.js';`);
31
27
  }
@@ -4,13 +4,44 @@
4
4
  * When `features.powerhouse.enabled`, this file lives at
5
5
  * `{root}/{name}-cli/package.json`. Otherwise at `{root}/package.json`.
6
6
  */
7
- import { getBinName, getPackageName, } from '../../spec/types.js';
8
- /** Pinned version range for peer ph-clint packages emitted into generated projects. */
9
- const PH_CLINT_VERSION = '^0.1.0';
7
+ import { getAppDirName, getAppPackageName, getBinName, getPackageName, phAtLeast, } from '../../spec/types.js';
8
+ import { CLI_VERSION } from '../../config.js';
9
+ /**
10
+ * Derive a semver range from the running ph-clint-cli version that respects
11
+ * the dev / staging / production channel boundary. This is the version range
12
+ * emitted into *scaffolded* projects' package.json for @powerhousedao/ph-clint
13
+ * and @powerhousedao/ph-clint-dev dependencies.
14
+ *
15
+ * CLI_VERSION is the version of ph-clint-cli itself (read from its own
16
+ * package.json at startup). Since all framework packages share a lockstep
17
+ * version, this is also the correct base for the dependency range.
18
+ *
19
+ * Examples:
20
+ * "0.1.0" → "^0.1.0" (production — stable only)
21
+ * "0.1.0-staging.2" → "^0.1.0-staging.0" (staging + stable)
22
+ * "0.1.0-dev.4" → "^0.1.0-dev.0" (dev + staging + stable)
23
+ *
24
+ * This works because semver orders prereleases alphabetically:
25
+ * dev.N < staging.N < release. A caret range starting at dev.0 naturally
26
+ * upgrades through staging to production.
27
+ */
28
+ function phClintRange(version) {
29
+ const match = /^(\d+\.\d+\.\d+)(?:-([a-z]+)\.\d+)?/.exec(version);
30
+ if (!match)
31
+ return `^${version}`;
32
+ const base = match[1];
33
+ const channel = match[2]; // "dev", "staging", or undefined
34
+ // For prerelease channels, pin exact version. Caret ranges like ^0.1.0-dev.0
35
+ // resolve to `latest` dist-tag (0.1.0-dev.0) instead of the highest match,
36
+ // causing "export not found" errors when the scaffolding CLI is newer.
37
+ return channel ? version : `^${base}`;
38
+ }
39
+ /** Version range for @powerhousedao/ph-clint{,-dev} deps in scaffolded projects. */
40
+ const PH_CLINT_VERSION = phClintRange(CLI_VERSION);
10
41
  const POWERHOUSE_VERSION = '6.0.0-dev.170';
11
42
  export function buildCliPackageJson(spec) {
12
43
  const { mastra, powerhouse } = spec.features;
13
- const pkgName = powerhouse.enabled
44
+ const pkgName = phAtLeast(powerhouse, 'Reactor')
14
45
  ? `${getPackageName(spec)}-cli`
15
46
  : getPackageName(spec);
16
47
  const bin = getBinName(spec);
@@ -21,18 +52,32 @@ export function buildCliPackageJson(spec) {
21
52
  if (mastra.enabled) {
22
53
  dependencies['@mastra/core'] = '^1.22.0';
23
54
  dependencies['@mastra/libsql'] = '^1.7.4';
55
+ dependencies['@mastra/mcp'] = '^1.4.1';
24
56
  dependencies['@mastra/memory'] = '^1.13.1';
25
57
  }
26
- if (powerhouse.enabled) {
27
- dependencies[`${spec.name}-app`] = `file:../${spec.name}-app`;
58
+ if (phAtLeast(powerhouse, 'Reactor')) {
59
+ const appPkg = getAppPackageName(spec);
60
+ const appDir = getAppDirName(spec);
61
+ dependencies[appPkg] = `file:../${appDir}`;
28
62
  dependencies['@powerhousedao/reactor'] = POWERHOUSE_VERSION;
29
63
  dependencies['@powerhousedao/reactor-api'] = POWERHOUSE_VERSION;
30
64
  dependencies['@powerhousedao/shared'] = POWERHOUSE_VERSION;
31
65
  dependencies['document-model'] = POWERHOUSE_VERSION;
66
+ // External reactor packages (non-app) as versioned dependencies.
67
+ // Compare bare names (without scope) so that a spec entry with
68
+ // packageName "foo-app" still matches the scoped "@scope/foo-app".
69
+ const appBare = appPkg.replace(/^@[^/]+\//, '');
70
+ for (const pkg of spec.packages) {
71
+ const pkgBare = pkg.packageName.replace(/^@[^/]+\//, '');
72
+ if (pkgBare === appBare)
73
+ continue;
74
+ dependencies[pkg.packageName] = 'latest';
75
+ }
32
76
  }
33
77
  const scripts = {
34
78
  'build:skills': 'tsx scripts/build-skills.ts',
35
- build: 'pnpm build:skills && tsc',
79
+ 'build:manifest': 'build-manifest',
80
+ build: 'pnpm build:skills && tsc && pnpm build:manifest',
36
81
  dev: 'tsx src/main.ts',
37
82
  start: 'node dist/main.js',
38
83
  test: "NODE_OPTIONS='--experimental-vm-modules' jest --detectOpenHandles",
@@ -43,15 +88,26 @@ export function buildCliPackageJson(spec) {
43
88
  scripts['mastra:build'] = 'mastra build';
44
89
  scripts['mastra:start'] = 'mastra start';
45
90
  }
91
+ // Single-layout: publish scripts live in the CLI package.json (which IS the
92
+ // root). Split-layout gets these from root-package-json.ts instead.
93
+ if (!phAtLeast(powerhouse, 'Reactor')) {
94
+ scripts['publish:dev'] = 'ph-publish dev -c ./publish.config.js';
95
+ scripts['publish:staging'] = 'ph-publish staging -c ./publish.config.js';
96
+ scripts['publish:production'] = 'ph-publish production -c ./publish.config.js';
97
+ }
98
+ if (spec.deployment.serviceAnnouncement) {
99
+ scripts['test-service-registry'] = 'json-post-registry --withAuth';
100
+ }
46
101
  const pkg = {
47
102
  name: pkgName,
48
103
  version: spec.version,
49
- private: true,
104
+ publishConfig: { access: 'public' },
50
105
  type: 'module',
51
106
  };
52
107
  if (spec.description)
53
108
  pkg.description = spec.description;
54
109
  pkg.bin = { [bin]: './dist/main.js' };
110
+ pkg.files = ['dist'];
55
111
  pkg.scripts = scripts;
56
112
  pkg.dependencies = dependencies;
57
113
  pkg.devDependencies = {
@@ -62,6 +118,7 @@ export function buildCliPackageJson(spec) {
62
118
  'ts-jest': '^29.4.6',
63
119
  tsx: '^4.19.0',
64
120
  typescript: '^6.0.2',
121
+ 'typescript-eslint': '^8.33.0',
65
122
  };
66
123
  pkg.engines = { node: '>=22.13.0' };
67
124
  return JSON.stringify(pkg, null, 2) + '\n';
@@ -1,5 +1,12 @@
1
+ /**
2
+ * Builds `src/cli.ts` — the defineCli() call with feature blocks guarded by
3
+ * `@clint:begin`/`@clint:end` markers so the code generator can patch
4
+ * specific regions on subsequent runs (delta mode — future phase).
5
+ */
6
+ import { getAppPackageName, phAtLeast } from '../../spec/types.js';
1
7
  export function buildCliTs(spec) {
2
8
  const { mastra, routine, powerhouse } = spec.features;
9
+ const ph = powerhouse;
3
10
  const lines = [];
4
11
  lines.push('/**');
5
12
  lines.push(' * CLI definition.');
@@ -8,34 +15,33 @@ export function buildCliTs(spec) {
8
15
  lines.push(' * markers are rewritten by ph-clint when the project spec changes.');
9
16
  lines.push(' * Everything outside the markers is user-editable and preserved.');
10
17
  lines.push(' */');
11
- if (powerhouse.enabled) {
12
- lines.push(`import path from 'node:path';`);
13
- lines.push(`import { fileURLToPath } from 'node:url';`);
14
- lines.push(`import { defineCli, buildDefaultReactor } from '@powerhousedao/ph-clint';`);
15
- }
16
- else {
17
- lines.push(`import { defineCli } from '@powerhousedao/ph-clint';`);
18
+ const needsRoot = phAtLeast(ph, 'Reactor') || mastra.enabled;
19
+ lines.push('// @clint:begin imports');
20
+ const phClintImports = ['defineCli'];
21
+ if (phAtLeast(ph, 'Reactor'))
22
+ phClintImports.push('buildDefaultReactor');
23
+ if (spec.deployment.serviceAnnouncement)
24
+ phClintImports.push('jsonPostAnnounce');
25
+ lines.push(`import { ${phClintImports.join(', ')} } from '@powerhousedao/ph-clint';`);
26
+ if (needsRoot) {
27
+ lines.push("import path from 'node:path';");
18
28
  }
19
- lines.push(`import { CLI_NAME, CLI_VERSION } from './config.js';`);
29
+ lines.push(`import { CLI_NAME, CLI_VERSION${needsRoot ? ', CLI_ROOT' : ''} } from './config.js';`);
20
30
  lines.push(`import { configSchema, secretsSchema } from './framework.js';`);
21
- lines.push('');
22
- lines.push('// @clint:begin imports');
23
- if (powerhouse.enabled) {
24
- lines.push(`import { documentModels } from '${spec.name}-app';`);
31
+ if (phAtLeast(ph, 'Reactor')) {
32
+ lines.push(`import { documentModels } from '${getAppPackageName(spec)}';`);
25
33
  }
26
34
  if (mastra.enabled) {
27
35
  lines.push(`import { createAgent } from './agents/agent.js';`);
28
36
  }
29
37
  lines.push('// @clint:end imports');
30
38
  lines.push('');
31
- if (powerhouse.enabled) {
32
- lines.push(`const __dirname = path.dirname(fileURLToPath(import.meta.url));`);
33
- lines.push(`const appDir = path.resolve(__dirname, '../../${spec.name}-app');`);
34
- lines.push('');
35
- }
36
39
  lines.push('export const cli = defineCli({');
37
40
  lines.push(' name: CLI_NAME,');
38
41
  lines.push(' version: CLI_VERSION,');
42
+ if (phAtLeast(ph, 'Reactor')) {
43
+ lines.push(' root: CLI_ROOT,');
44
+ }
39
45
  lines.push(' description: `${CLI_NAME} v${CLI_VERSION}`,');
40
46
  lines.push(' configSchema,');
41
47
  lines.push(' secretsSchema,');
@@ -59,8 +65,34 @@ export function buildCliTs(spec) {
59
65
  lines.push('');
60
66
  lines.push(' // @clint:begin prompts');
61
67
  lines.push(' prompts: {');
62
- lines.push(' sources: [],');
63
- lines.push(' agents: {},');
68
+ if (mastra.enabled) {
69
+ lines.push(" artifacts: [path.join(CLI_ROOT, 'gen', 'skills'), path.join(CLI_ROOT, 'dist', 'gen', 'skills')],");
70
+ }
71
+ else {
72
+ lines.push(' artifacts: [],');
73
+ }
74
+ if (mastra.enabled && mastra.agentId) {
75
+ lines.push(' agents: {');
76
+ lines.push(` '${mastra.agentId}': {`);
77
+ if (mastra.profiles.length > 0) {
78
+ lines.push(` name: '${mastra.profiles[0].id}',`);
79
+ lines.push(' sections: [');
80
+ for (const p of mastra.profiles) {
81
+ lines.push(` '${p.id}.md',`);
82
+ }
83
+ lines.push(' ],');
84
+ }
85
+ else {
86
+ lines.push(" name: 'AgentBase',");
87
+ lines.push(" sections: ['AgentBase.md'],");
88
+ }
89
+ lines.push(' skills: [],');
90
+ lines.push(' },');
91
+ lines.push(' },');
92
+ }
93
+ else {
94
+ lines.push(' agents: {},');
95
+ }
64
96
  lines.push(' skills: {},');
65
97
  lines.push(' },');
66
98
  lines.push(' // @clint:end prompts');
@@ -69,6 +101,15 @@ export function buildCliTs(spec) {
69
101
  lines.push(' events: {},');
70
102
  lines.push(' // @clint:end events');
71
103
  lines.push('');
104
+ lines.push(' // @clint:begin serviceAnnouncement');
105
+ if (spec.deployment.serviceAnnouncement) {
106
+ lines.push(' serviceAnnouncement: {');
107
+ lines.push(' enabled: true,');
108
+ lines.push(' announce: (payload, ctx) => jsonPostAnnounce(payload, ctx),');
109
+ lines.push(' },');
110
+ }
111
+ lines.push(' // @clint:end serviceAnnouncement');
112
+ lines.push('');
72
113
  lines.push(' // @clint:begin interactive');
73
114
  lines.push(' interactive: {');
74
115
  lines.push(` welcome: ({ workdir }) =>`);
@@ -77,10 +118,10 @@ export function buildCliTs(spec) {
77
118
  lines.push(' `Workdir: ${workdir}`,');
78
119
  lines.push(" '',");
79
120
  if (mastra.enabled) {
80
- lines.push(" 'Type a message to talk to the agent, or /help for commands.',");
121
+ lines.push(" 'Type a message to talk to the agent, or / for commands.',");
81
122
  }
82
123
  else {
83
- lines.push(" 'Type /help for commands.',");
124
+ lines.push(" 'Type / for commands or /cli-docs for help.',");
84
125
  }
85
126
  lines.push(" ].join('\\n'),");
86
127
  lines.push(' },');
@@ -88,15 +129,15 @@ export function buildCliTs(spec) {
88
129
  lines.push('});');
89
130
  lines.push('');
90
131
  lines.push('// @clint:begin reactor');
91
- if (powerhouse.enabled) {
132
+ if (phAtLeast(ph, 'Reactor')) {
92
133
  lines.push('cli.configureReactor({');
93
134
  lines.push(' create: (ctx) =>');
94
135
  lines.push(' buildDefaultReactor(ctx, {');
95
136
  lines.push(' documentModels,');
96
137
  lines.push(` drive: { name: '${spec.name}' },`);
97
138
  lines.push(' }),');
98
- lines.push(` switchboard: { enabled: ${String(powerhouse.switchboard)} },`);
99
- lines.push(` connect: { enabled: ${String(powerhouse.connect)}, workdir: appDir },`);
139
+ lines.push(` switchboard: { enabled: ${String(phAtLeast(ph, 'Switchboard'))} },`);
140
+ lines.push(` connect: { enabled: ${String(phAtLeast(ph, 'Connect'))} },`);
100
141
  lines.push('});');
101
142
  }
102
143
  lines.push('// @clint:end reactor');
@@ -1,21 +1,17 @@
1
- /**
2
- * Builds `src/config.ts` — CLI identity constants (name, version, project
3
- * root path). The Zod `configSchema` / `secretsSchema` are emitted by
4
- * `buildFrameworkTs` into `src/framework.ts` (user-owned, init-only) so users
5
- * can extend them without fighting the generator.
6
- */
7
- import { getBinName, } from '../../spec/types.js';
8
1
  export function buildConfigTs(spec) {
9
- const cliName = getBinName(spec);
2
+ const hasCustomBin = !!spec.bin;
10
3
  const lines = [];
11
- lines.push(`import path from 'node:path';`);
12
- lines.push(`import { fileURLToPath } from 'node:url';`);
4
+ lines.push(`import { readPackageInfo } from '@powerhousedao/ph-clint';`);
13
5
  lines.push('');
14
- lines.push('/** CLI name — used for config resolution, env var prefixing, and .ph/ paths. */');
15
- lines.push(`export const CLI_NAME = '${cliName}';`);
16
- lines.push(`export const CLI_VERSION = '${spec.version}';`);
6
+ lines.push('const pkg = readPackageInfo(import.meta.url);');
17
7
  lines.push('');
18
- lines.push('/** Project root — resolved from this file location. */');
19
- lines.push('export const PROJECT_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), \'..\');');
8
+ lines.push('export const CLI_ROOT = pkg.root;');
9
+ if (hasCustomBin) {
10
+ lines.push(`export const CLI_NAME = '${spec.bin}';`);
11
+ }
12
+ else {
13
+ lines.push(`export const CLI_NAME = pkg.name.replace(/-cli$/, '');`);
14
+ }
15
+ lines.push('export const CLI_VERSION = pkg.version;');
20
16
  return lines.join('\n') + '\n';
21
17
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Builds `src/agents/demo-agent.ts` — a deterministic echo agent fallback.
3
+ *
4
+ * Generated when `features.mastra.enabled` with full agent config so the real
5
+ * agent factory can fall back to demo mode when no API key is configured.
6
+ */
7
+ import { type ClintProjectSpec } from '../../spec/types.js';
8
+ export declare function buildDemoAgentTs(spec: ClintProjectSpec): string | null;
@@ -0,0 +1,44 @@
1
+ export function buildDemoAgentTs(spec) {
2
+ const { mastra } = spec.features;
3
+ // Only needed alongside the real agent (which imports it)
4
+ if (!mastra.enabled || !mastra.agentId || mastra.models.length === 0)
5
+ return null;
6
+ const agentId = mastra.agentId;
7
+ return [
8
+ "import type { AgentProvider, StreamChunk } from '@powerhousedao/ph-clint';",
9
+ '',
10
+ '/**',
11
+ ' * Demo agent — deterministic echo responses without an API key.',
12
+ ' *',
13
+ ' * Echoes user prompts with conversation tracking per thread.',
14
+ ' */',
15
+ 'export function createDemoAgent(): AgentProvider {',
16
+ ' const conversations = new Map<string, string[]>();',
17
+ '',
18
+ ' return {',
19
+ ` id: '${agentId}',`,
20
+ ' async *stream(prompt, opts) {',
21
+ " const threadId = opts?.threadId ?? 'default';",
22
+ ' if (!conversations.has(threadId)) {',
23
+ ' conversations.set(threadId, []);',
24
+ ' }',
25
+ ' const history = conversations.get(threadId)!;',
26
+ ' history.push(prompt);',
27
+ '',
28
+ ' const turnCount = history.length;',
29
+ ' if (turnCount > 1) {',
30
+ ' yield {',
31
+ " type: 'text-delta',",
32
+ ' text: `I understand you\'re continuing our conversation (turn ${turnCount}). `,',
33
+ ' } satisfies StreamChunk;',
34
+ ' }',
35
+ ' yield {',
36
+ " type: 'text-delta',",
37
+ ' text: `You said: "${prompt}". I\'m the demo agent — set an API key for real LLM responses.`,',
38
+ ' } satisfies StreamChunk;',
39
+ ' },',
40
+ ' };',
41
+ '}',
42
+ '',
43
+ ].join('\n');
44
+ }
@@ -1,4 +1,4 @@
1
1
  /**
2
- * Builds `eslint.config.js` for the CLI — placeholder flat config.
2
+ * Builds `eslint.config.js` for the CLI — minimal TypeScript-ESLint config.
3
3
  */
4
4
  export declare function buildEslintConfigJs(): string;
@@ -1,12 +1,15 @@
1
1
  /**
2
- * Builds `eslint.config.js` for the CLI — placeholder flat config.
2
+ * Builds `eslint.config.js` for the CLI — minimal TypeScript-ESLint config.
3
3
  */
4
4
  export function buildEslintConfigJs() {
5
5
  return [
6
6
  '// @clint:begin eslint',
7
- '// Placeholder ESLint flat config — replaced by codegen when the',
8
- "// project's lint stack is configured.",
9
- 'export default [];',
7
+ "import tseslint from 'typescript-eslint';",
8
+ '',
9
+ 'export default tseslint.config(',
10
+ ' tseslint.configs.recommended,',
11
+ " { ignores: ['dist/', 'gen/', 'coverage/'] },",
12
+ ');',
10
13
  '// @clint:end eslint',
11
14
  '',
12
15
  ].join('\n');
@@ -4,14 +4,13 @@
4
4
  * `defineRegistry([...] as const)` call that describes which document models
5
5
  * are bound into `framework.ts`'s typed factories.
6
6
  *
7
- * Kept deliberately small: this file exports only `registry` and the
8
- * `Registry` type. The `createTypes` call lives in `framework.ts` (the
9
- * user-owned half) so `configSchema` edits stay local and we avoid an import
10
- * cycle between the two files.
7
+ * Iterates all packages' document types. The app package's modules are
8
+ * imported from the local `{name}-app` package; external packages' modules
9
+ * are imported from their npm package name.
11
10
  *
12
11
  * Emitted only when Powerhouse is enabled — flat-layout impls have no
13
12
  * reactor package to import modules from and bind `createTypes` directly in
14
- * `framework.ts`. When Powerhouse is on but no `documentTypes` are declared,
13
+ * `framework.ts`. When Powerhouse is on but no document types are declared,
15
14
  * we still emit this file with an empty `defineRegistry([])` so impl code's
16
15
  * `import { registry } from './framework.gen.js'` keeps resolving across
17
16
  * regens.
@@ -4,31 +4,34 @@
4
4
  * `defineRegistry([...] as const)` call that describes which document models
5
5
  * are bound into `framework.ts`'s typed factories.
6
6
  *
7
- * Kept deliberately small: this file exports only `registry` and the
8
- * `Registry` type. The `createTypes` call lives in `framework.ts` (the
9
- * user-owned half) so `configSchema` edits stay local and we avoid an import
10
- * cycle between the two files.
7
+ * Iterates all packages' document types. The app package's modules are
8
+ * imported from the local `{name}-app` package; external packages' modules
9
+ * are imported from their npm package name.
11
10
  *
12
11
  * Emitted only when Powerhouse is enabled — flat-layout impls have no
13
12
  * reactor package to import modules from and bind `createTypes` directly in
14
- * `framework.ts`. When Powerhouse is on but no `documentTypes` are declared,
13
+ * `framework.ts`. When Powerhouse is on but no document types are declared,
15
14
  * we still emit this file with an empty `defineRegistry([])` so impl code's
16
15
  * `import { registry } from './framework.gen.js'` keeps resolving across
17
16
  * regens.
18
17
  */
19
- import { getDocumentTypeModuleName, } from '../../spec/types.js';
20
- /** Local package name of the reactor package for this project. */
21
- function appPackageName(spec) {
22
- return `${spec.name}-app`;
23
- }
18
+ import { getAppPackageName, getDocumentTypeModuleName, phAtLeast, } from '../../spec/types.js';
24
19
  export function buildFrameworkGenTs(spec) {
25
- if (!spec.features.powerhouse.enabled)
20
+ if (!phAtLeast(spec.features.powerhouse, 'Reactor'))
26
21
  return null;
27
- const pkg = appPackageName(spec);
28
- const entries = spec.documentTypes.map((docType) => ({
29
- docType,
30
- name: getDocumentTypeModuleName(docType),
31
- }));
22
+ const appPkg = getAppPackageName(spec);
23
+ // Collect all (docType, moduleName, importFrom) tuples across packages.
24
+ const entries = [];
25
+ for (const pkg of spec.packages) {
26
+ const importFrom = pkg.packageName === appPkg ? appPkg : pkg.packageName;
27
+ for (const docType of pkg.documentTypes) {
28
+ entries.push({
29
+ docType,
30
+ name: getDocumentTypeModuleName(docType),
31
+ importFrom,
32
+ });
33
+ }
34
+ }
32
35
  const lines = [];
33
36
  lines.push('/**');
34
37
  lines.push(' * THIS FILE IS GENERATED BY ph-clint-cli. DO NOT EDIT.');
@@ -39,12 +42,12 @@ export function buildFrameworkGenTs(spec) {
39
42
  lines.push(' */');
40
43
  lines.push(`import { defineRegistry } from '@powerhousedao/ph-clint';`);
41
44
  for (const e of entries) {
42
- lines.push(`import { ${e.name} } from '${pkg}';`);
45
+ lines.push(`import { ${e.name} } from '${e.importFrom}';`);
43
46
  }
44
47
  lines.push('');
45
48
  lines.push('/**');
46
49
  lines.push(' * Typed document registry derived from the reactor package\'s document models.');
47
- lines.push(' * Regenerated whenever the spec\'s `documentTypes` list changes.');
50
+ lines.push(' * Regenerated whenever the spec\'s packages or document types change.');
48
51
  lines.push(' */');
49
52
  if (entries.length === 0) {
50
53
  lines.push('export const registry = defineRegistry([] as const);');
@@ -1,6 +1,26 @@
1
+ /**
2
+ * Builds `src/framework.ts` — the user-owned half of the typed-binding split.
3
+ *
4
+ * Emitted **only on create**. Once the file exists, codegen never overwrites
5
+ * it: users are expected to extend `configSchema` and `secretsSchema` here,
6
+ * and those edits must survive regens.
7
+ *
8
+ * Behavior differs by layout:
9
+ *
10
+ * - **Powerhouse enabled** — this file declares `configSchema`/`secretsSchema`
11
+ * and re-exports `registry`, `defineCommand`, `defineTrigger`,
12
+ * `defineService`, and `createDocumentChangeTrigger` from the companion
13
+ * `framework.gen.ts`. Adding a document type to the spec regenerates
14
+ * `framework.gen.ts`; this file keeps resolving its re-exports unchanged.
15
+ *
16
+ * - **Powerhouse disabled** — no reactor package exists to register modules
17
+ * against, so this file binds `createTypes({ configSchema })` directly and
18
+ * exports the typed factories from here. No `framework.gen.ts` is emitted.
19
+ */
20
+ import { phAtLeast } from '../../spec/types.js';
1
21
  export function buildFrameworkTs(spec) {
2
22
  const { mastra, powerhouse } = spec.features;
3
- const hasGen = powerhouse.enabled;
23
+ const hasGen = phAtLeast(powerhouse, 'Reactor');
4
24
  const lines = [];
5
25
  lines.push('/**');
6
26
  lines.push(' * Per-CLI typed framework binding.');
@@ -22,24 +42,30 @@ export function buildFrameworkTs(spec) {
22
42
  }
23
43
  lines.push('');
24
44
  lines.push('export const configSchema = z.object({');
45
+ lines.push(' // @clint:begin framework-config');
25
46
  if (mastra.enabled) {
26
- lines.push(" model: z.string().default('anthropic/claude-haiku-4-5').describe('LLM model to use'),");
47
+ const defaultModel = mastra.models.find(m => m.isDefault)?.id ?? 'anthropic/claude-sonnet-4-5';
48
+ lines.push(` model: z.string().default('${defaultModel}').describe('LLM model to use'),`, ` agentLogging: z.boolean().default(false).describe('Enable agent conversation logging'),`);
27
49
  }
28
- // Ports are derived from CLI name by resolveReactorDefaults() — no config fields needed.
29
- // Override via switchboard.port / connect.port in configureReactor() if required.
30
- lines.push(' // Add your own config fields here — this file survives regens.');
50
+ lines.push(' // @clint:end framework-config');
31
51
  lines.push('});');
32
52
  lines.push('');
33
53
  lines.push('export const secretsSchema = z.object({');
54
+ lines.push(' // @clint:begin framework-secrets');
34
55
  if (mastra.enabled) {
35
- lines.push(" apiKey: z.string().optional().describe('LLM API key'),");
56
+ const providers = [...new Set(mastra.models.map(m => m.id.split(/[:/]/)[0]))];
57
+ for (const provider of providers) {
58
+ const field = `${provider}ApiKey`;
59
+ lines.push(` ${field}: z.string().optional().describe('${provider} API key'),`);
60
+ }
36
61
  }
62
+ lines.push(' // @clint:end framework-secrets');
37
63
  lines.push('});');
38
64
  lines.push('');
39
65
  lines.push('export type Config = z.infer<typeof configSchema> &');
40
66
  lines.push(' z.infer<typeof secretsSchema>;');
41
67
  lines.push('');
42
- lines.push('const fullConfigSchema = configSchema.merge(secretsSchema);');
68
+ lines.push('const fullConfigSchema = configSchema.extend(secretsSchema.shape);');
43
69
  lines.push('');
44
70
  if (hasGen) {
45
71
  lines.push('export const {');
@@ -17,8 +17,11 @@ import { buildMastraIndexTs } from './mastra-index-ts.js';
17
17
  import { buildBuildSkillsScript } from './build-skills-ts.js';
18
18
  import { buildAgentBaseMd } from './agent-base-md.js';
19
19
  import { buildAgentTs } from './agent-ts.js';
20
+ import { buildDemoAgentTs } from './demo-agent-ts.js';
20
21
  import { buildFrameworkGenTs } from './framework-gen-ts.js';
21
22
  import { buildFrameworkTs } from './framework-ts.js';
23
+ import { buildManifestJson } from './manifest-json.js';
24
+ import { buildSmokeTestTs } from './smoke-test-ts.js';
22
25
  import { type ClintProjectSpec } from '../../spec/types.js';
23
26
  /**
24
27
  * A spec-driven file builder. Returns `null` from `build` to indicate the
@@ -36,8 +39,14 @@ export interface FileBuilder {
36
39
  */
37
40
  initOnly?: boolean;
38
41
  }
42
+ /**
43
+ * Generate dynamic file builders for agent profile markdown files.
44
+ * One file per profile in `prompts/agent-profiles/{id}.md`.
45
+ */
46
+ export declare function getProfileFileBuilders(spec: ClintProjectSpec): FileBuilder[];
39
47
  export declare const CLI_FILE_BUILDERS: FileBuilder[];
40
- export { buildCliPackageJson, buildCliTs, buildConfigTs, buildMainTs, buildTsconfigJson, buildJestConfigJs, buildEslintConfigJs, buildGitignore, buildMastraIndexTs, buildBuildSkillsScript, buildAgentBaseMd, buildAgentTs, buildFrameworkGenTs, buildFrameworkTs, };
48
+ export { buildCliPackageJson, buildCliTs, buildConfigTs, buildMainTs, buildTsconfigJson, buildJestConfigJs, buildEslintConfigJs, buildGitignore, buildMastraIndexTs, buildBuildSkillsScript, buildAgentBaseMd, buildAgentTs, buildDemoAgentTs, buildFrameworkGenTs, buildFrameworkTs, buildManifestJson, buildSmokeTestTs, };
41
49
  export { buildRootPackageJson } from './root-package-json.js';
50
+ export { buildPublishConfigJs } from './publish-config-js.js';
42
51
  export { buildReadme } from './readme-md.js';
43
52
  export { buildAppIndexTs } from './app-index-ts.js';