@powerhousedao/ph-clint-cli 0.1.0-dev.15 → 0.1.0-dev.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -19,7 +19,9 @@ import { regen } from './commands/clint-project-regen.js';
19
19
  import { build } from './commands/clint-project-build.js';
20
20
  import { clintProjectPublish } from './commands/clint-project-publish.js';
21
21
  import { clintProject } from './services/clint-project.js';
22
+ import { skillsSync } from './commands/clint-skills-sync.js';
22
23
  import { specChangeTrigger } from './triggers/spec-change.js';
24
+ import { publishTrigger } from './triggers/publish-trigger.js';
23
25
  // @clint:end imports
24
26
  // Connect (ph connect) must run inside the Reactor Package (ph-clint-app).
25
27
  const appDir = path.resolve(CLI_ROOT, '../ph-clint-app');
@@ -34,13 +36,13 @@ export const cli = defineCli({
34
36
  configSchema,
35
37
  secretsSchema,
36
38
  // @clint:begin commands
37
- commands: [init, regen, build, clintProjectPublish],
39
+ commands: [init, regen, build, clintProjectPublish, skillsSync],
38
40
  // @clint:end commands
39
41
  // @clint:begin services
40
42
  services: [clintProject],
41
43
  // @clint:end services
42
44
  // @clint:begin triggers
43
- triggers: [specChangeTrigger],
45
+ triggers: [specChangeTrigger, publishTrigger],
44
46
  // @clint:end triggers
45
47
  // @clint:begin prompts
46
48
  prompts: {
@@ -3,16 +3,9 @@
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
10
  import { type ClintProjectSpec } from '../../spec/types.js';
18
11
  export declare function buildAppIndexTs(spec: ClintProjectSpec): string | null;
@@ -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,7 +4,7 @@
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';
7
+ import { getAppDirName, getAppPackageName, getBinName, getPackageName, phAtLeast, } from '../../spec/types.js';
8
8
  import { CLI_VERSION } from '../../config.js';
9
9
  /**
10
10
  * Derive a semver range from the running ph-clint-cli version that respects
@@ -41,7 +41,7 @@ const PH_CLINT_VERSION = phClintRange(CLI_VERSION);
41
41
  const POWERHOUSE_VERSION = '6.0.0-dev.170';
42
42
  export function buildCliPackageJson(spec) {
43
43
  const { mastra, powerhouse } = spec.features;
44
- const pkgName = powerhouse.enabled
44
+ const pkgName = phAtLeast(powerhouse, 'Reactor')
45
45
  ? `${getPackageName(spec)}-cli`
46
46
  : getPackageName(spec);
47
47
  const bin = getBinName(spec);
@@ -54,12 +54,20 @@ export function buildCliPackageJson(spec) {
54
54
  dependencies['@mastra/libsql'] = '^1.7.4';
55
55
  dependencies['@mastra/memory'] = '^1.13.1';
56
56
  }
57
- if (powerhouse.enabled) {
58
- dependencies[`${spec.name}-app`] = `file:../${spec.name}-app`;
57
+ if (phAtLeast(powerhouse, 'Reactor')) {
58
+ const appPkg = getAppPackageName(spec);
59
+ const appDir = getAppDirName(spec);
60
+ dependencies[appPkg] = `file:../${appDir}`;
59
61
  dependencies['@powerhousedao/reactor'] = POWERHOUSE_VERSION;
60
62
  dependencies['@powerhousedao/reactor-api'] = POWERHOUSE_VERSION;
61
63
  dependencies['@powerhousedao/shared'] = POWERHOUSE_VERSION;
62
64
  dependencies['document-model'] = POWERHOUSE_VERSION;
65
+ // External reactor packages (non-app) as versioned dependencies.
66
+ for (const pkg of spec.packages) {
67
+ if (pkg.packageName !== appPkg) {
68
+ dependencies[pkg.packageName] = 'latest';
69
+ }
70
+ }
63
71
  }
64
72
  const scripts = {
65
73
  'build:skills': 'tsx scripts/build-skills.ts',
@@ -77,12 +85,13 @@ export function buildCliPackageJson(spec) {
77
85
  const pkg = {
78
86
  name: pkgName,
79
87
  version: spec.version,
80
- private: true,
88
+ publishConfig: { access: 'public' },
81
89
  type: 'module',
82
90
  };
83
91
  if (spec.description)
84
92
  pkg.description = spec.description;
85
93
  pkg.bin = { [bin]: './dist/main.js' };
94
+ pkg.files = ['dist', 'prompts'];
86
95
  pkg.scripts = scripts;
87
96
  pkg.dependencies = dependencies;
88
97
  pkg.devDependencies = {
@@ -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,7 +15,7 @@ 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) {
18
+ if (phAtLeast(ph, 'Reactor')) {
12
19
  lines.push(`import path from 'node:path';`);
13
20
  lines.push(`import { existsSync } from 'node:fs';`);
14
21
  lines.push(`import { defineCli, buildDefaultReactor } from '@powerhousedao/ph-clint';`);
@@ -16,22 +23,22 @@ export function buildCliTs(spec) {
16
23
  else {
17
24
  lines.push(`import { defineCli } from '@powerhousedao/ph-clint';`);
18
25
  }
19
- lines.push(`import { CLI_NAME, CLI_VERSION${powerhouse.enabled ? ', CLI_ROOT' : ''} } from './config.js';`);
26
+ lines.push(`import { CLI_NAME, CLI_VERSION${phAtLeast(ph, 'Reactor') ? ', CLI_ROOT' : ''} } from './config.js';`);
20
27
  lines.push(`import { configSchema, secretsSchema } from './framework.js';`);
21
28
  lines.push('');
22
29
  lines.push('// @clint:begin imports');
23
- if (powerhouse.enabled) {
24
- lines.push(`import { documentModels } from '${spec.name}-app';`);
30
+ if (phAtLeast(ph, 'Reactor')) {
31
+ lines.push(`import { documentModels } from '${getAppPackageName(spec)}';`);
25
32
  }
26
33
  if (mastra.enabled) {
27
34
  lines.push(`import { createAgent } from './agents/agent.js';`);
28
35
  }
29
36
  lines.push('// @clint:end imports');
30
37
  lines.push('');
31
- if (powerhouse.enabled) {
38
+ if (phAtLeast(ph, 'Reactor')) {
32
39
  lines.push(`const appDir = path.resolve(CLI_ROOT, '../${spec.name}-app');`);
33
40
  lines.push('');
34
- if (powerhouse.connect) {
41
+ if (phAtLeast(ph, 'Connect')) {
35
42
  lines.push('function resolveConnectAssets(dir: string): string | undefined {');
36
43
  lines.push(" const assetsDir = path.join(dir, 'dist', 'connect');");
37
44
  lines.push(" return existsSync(path.join(assetsDir, 'index.html')) ? assetsDir : undefined;");
@@ -78,7 +85,7 @@ export function buildCliTs(spec) {
78
85
  lines.push(' // @clint:begin interactive');
79
86
  lines.push(' interactive: {');
80
87
  const ctxArgs = [];
81
- if (powerhouse.enabled && (powerhouse.switchboard || powerhouse.connect)) {
88
+ if (phAtLeast(ph, 'Switchboard')) {
82
89
  ctxArgs.push('config');
83
90
  }
84
91
  ctxArgs.push('workdir');
@@ -86,10 +93,10 @@ export function buildCliTs(spec) {
86
93
  lines.push(' [');
87
94
  lines.push(' `${CLI_NAME} v${CLI_VERSION}`,');
88
95
  lines.push(' `Workdir: ${workdir}`,');
89
- if (powerhouse.enabled && powerhouse.switchboard) {
96
+ if (phAtLeast(ph, 'Switchboard')) {
90
97
  lines.push(' `Switchboard: http://localhost:${config.switchboardPort}`,');
91
98
  }
92
- if (powerhouse.enabled && powerhouse.connect) {
99
+ if (phAtLeast(ph, 'Connect')) {
93
100
  lines.push(' `Connect: http://localhost:${config.connectPort}`,');
94
101
  }
95
102
  lines.push(" '',");
@@ -105,15 +112,15 @@ export function buildCliTs(spec) {
105
112
  lines.push('});');
106
113
  lines.push('');
107
114
  lines.push('// @clint:begin reactor');
108
- if (powerhouse.enabled) {
115
+ if (phAtLeast(ph, 'Reactor')) {
109
116
  lines.push('cli.configureReactor({');
110
117
  lines.push(' create: (ctx) =>');
111
118
  lines.push(' buildDefaultReactor(ctx, {');
112
119
  lines.push(' documentModels,');
113
120
  lines.push(` drive: { name: '${spec.name}' },`);
114
121
  lines.push(' }),');
115
- lines.push(` switchboard: { enabled: ${String(powerhouse.switchboard)} },`);
116
- if (powerhouse.connect) {
122
+ lines.push(` switchboard: { enabled: ${String(phAtLeast(ph, 'Switchboard'))} },`);
123
+ if (phAtLeast(ph, 'Connect')) {
117
124
  lines.push(` connect: { enabled: true, workdir: appDir, assetsDir: resolveConnectAssets(appDir) },`);
118
125
  }
119
126
  else {
@@ -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.');
@@ -1,18 +1,21 @@
1
1
  export function buildPublishConfigTs(spec) {
2
2
  const app = `${spec.name}-app`;
3
3
  const cli = `${spec.name}-cli`;
4
- return `import { definePublishConfig } from '@powerhousedao/ph-clint-dev/publish';
5
-
6
- export default definePublishConfig({
4
+ // The publish pipeline appends the prerelease suffix (e.g. -dev.0), so we
5
+ // only store the base semver (M.m.p) here.
6
+ const baseVersion = spec.version.replace(/-.*$/, '');
7
+ return `// Publish config — loaded by ph-clint's publish pipeline.
8
+ // definePublishConfig is an identity function; a plain export is equivalent.
9
+ export default {
7
10
  groups: {
8
11
  '${spec.name}': {
9
- version: '${spec.version}',
12
+ version: '${baseVersion}',
10
13
  packages: [
11
14
  { path: '${app}', category: 'app' },
12
15
  { path: '${cli}', category: 'cli' },
13
16
  ],
14
17
  },
15
18
  },
16
- });
19
+ };
17
20
  `;
18
21
  }
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Builds the project README for a freshly-generated ph-clint project.
3
3
  */
4
- import { getPackageName, } from '../../spec/types.js';
4
+ import { getPackageName, phAtLeast, } from '../../spec/types.js';
5
5
  export function buildReadme(spec) {
6
6
  const { mastra, routine, powerhouse } = spec.features;
7
+ const ph = powerhouse;
7
8
  const title = getPackageName(spec);
8
9
  const lines = [];
9
10
  lines.push(`# ${title}`);
@@ -19,10 +20,10 @@ export function buildReadme(spec) {
19
20
  lines.push('');
20
21
  lines.push('## Enabled features');
21
22
  lines.push('');
22
- lines.push(`- **Powerhouse**: ${powerhouse.enabled ? 'on' : 'off'}`);
23
- if (powerhouse.enabled) {
24
- lines.push(` - Switchboard: ${powerhouse.switchboard ? 'on' : 'off'}`);
25
- lines.push(` - Connect: ${powerhouse.connect ? 'on' : 'off'}`);
23
+ lines.push(`- **Powerhouse**: ${ph !== 'Disabled' ? `on (${ph})` : 'off'}`);
24
+ if (phAtLeast(ph, 'Reactor')) {
25
+ lines.push(` - Switchboard: ${phAtLeast(ph, 'Switchboard') ? 'on' : 'off'}`);
26
+ lines.push(` - Connect: ${phAtLeast(ph, 'Connect') ? 'on' : 'off'}`);
26
27
  }
27
28
  lines.push(`- **Mastra agent**: ${mastra.enabled ? 'on' : 'off'}`);
28
29
  lines.push(`- **Routine loop**: ${routine.enabled ? 'on' : 'off'}`);
@@ -32,7 +33,7 @@ export function buildReadme(spec) {
32
33
  lines.push('Toggle features or update metadata in `.ph/ph-clint-cli/project-spec.json`,');
33
34
  lines.push('then re-run `ph-clint clint-project-regen` to regenerate.');
34
35
  lines.push('');
35
- if (powerhouse.enabled) {
36
+ if (phAtLeast(ph, 'Reactor')) {
36
37
  lines.push('## Split layout');
37
38
  lines.push('');
38
39
  lines.push(`This project is split into \`${spec.name}-cli/\` (the CLI) and \`${spec.name}-app/\` (the Powerhouse reactor package).`);
@@ -6,13 +6,13 @@
6
6
  * Uses `pnpm --prefix <dir>` for all orchestration scripts — no cwd mutation.
7
7
  * Build order: app first, then cli (cli depends on app via `file:` dep).
8
8
  */
9
- import { getPackageName, } from '../../spec/types.js';
9
+ import { getPackageName, phAtLeast, } from '../../spec/types.js';
10
10
  export function buildRootPackageJson(spec) {
11
11
  const cli = `${spec.name}-cli`;
12
12
  const app = `${spec.name}-app`;
13
13
  const prefix = (dir, cmd) => `pnpm --prefix ${dir} ${cmd}`;
14
14
  const both = (cmd) => `${prefix(app, cmd)} && ${prefix(cli, cmd)}`;
15
- const connectEnabled = spec.features.powerhouse.enabled && spec.features.powerhouse.connect;
15
+ const connectEnabled = phAtLeast(spec.features.powerhouse, 'Connect');
16
16
  const buildScript = connectEnabled
17
17
  ? `${prefix(app, 'build')} && ${prefix(app, 'connect:build')} && ${prefix(cli, 'build')}`
18
18
  : both('build');
@@ -50,8 +50,8 @@ export async function generateProject(options) {
50
50
  let migrated = false;
51
51
  if (resolvedMode === 'update' &&
52
52
  existingSpec &&
53
- !existingSpec.features.powerhouse.enabled &&
54
- spec.features.powerhouse.enabled) {
53
+ existingSpec.features.powerhouse === 'Disabled' &&
54
+ spec.features.powerhouse !== 'Disabled') {
55
55
  await migrateFlatToSplit({
56
56
  targetDir,
57
57
  spec,
@@ -75,7 +75,7 @@ function resolveMode(requested, specExists) {
75
75
  }
76
76
  /** Build the list of files the new spec would emit, with their content. */
77
77
  function planFiles(spec, targetDir) {
78
- const split = spec.features.powerhouse.enabled;
78
+ const split = spec.features.powerhouse !== 'Disabled';
79
79
  const cliDir = split ? path.join(targetDir, getCliFolderName(spec)) : targetDir;
80
80
  const appDir = split ? path.join(targetDir, getAppFolderName(spec)) : null;
81
81
  const planned = [];
@@ -201,13 +201,20 @@ async function runUpdate(options, warn, migrated) {
201
201
  for (const p of planned) {
202
202
  if (p.initOnly) {
203
203
  // User-owned files (e.g. `src/framework.ts` with its `configSchema`)
204
- // are emitted only on create. In update mode we never overwrite them
205
- // and never re-create them if missing — the user may have deliberately
206
- // deleted or inlined them. Keep whatever hash the previous run stored
207
- // so the deletion pass (step 2) still considers them "ours".
208
- const stored = previous[p.relativePath];
209
- if (stored !== undefined)
210
- next[p.relativePath] = stored;
204
+ // are emitted only on create. In update mode we never overwrite them —
205
+ // but we DO create them if they don't yet exist on disk (e.g. first
206
+ // spec-change trigger run where no explicit `init` was run before).
207
+ const onDisk = await hashFile(p.absolutePath);
208
+ if (onDisk === null && p.content) {
209
+ await writeFileEnsuringDir(p.absolutePath, p.content);
210
+ next[p.relativePath] = hashContent(p.content);
211
+ files.push({ absolutePath: p.absolutePath, relativePath: p.relativePath });
212
+ }
213
+ else {
214
+ const stored = previous[p.relativePath];
215
+ if (stored !== undefined)
216
+ next[p.relativePath] = stored;
217
+ }
211
218
  continue;
212
219
  }
213
220
  const existing = await hashFile(p.absolutePath);
@@ -31,6 +31,27 @@ export async function runPhInit(options) {
31
31
  if (result.exitCode !== 0) {
32
32
  log(`\`${binName} init\` exited with code ${result.exitCode}. ` +
33
33
  'You can re-run it manually after resolving the issue.');
34
+ return { ran: true, exitCode: result.exitCode };
35
+ }
36
+ // ph init creates an unscoped package name. If the spec has a scope, patch
37
+ // the app's package.json to use the scoped name so `file:` deps and publish
38
+ // both resolve correctly.
39
+ if (options.spec.scope) {
40
+ const appPkgJsonPath = path.join(options.appDir, 'package.json');
41
+ try {
42
+ const raw = await fs.readFile(appPkgJsonPath, 'utf8');
43
+ const pkg = JSON.parse(raw);
44
+ const scopedName = `@${options.spec.scope}/${appFolder}`;
45
+ if (pkg.name !== scopedName) {
46
+ pkg.name = scopedName;
47
+ pkg.publishConfig = { access: 'public' };
48
+ await fs.writeFile(appPkgJsonPath, JSON.stringify(pkg, null, 2) + '\n');
49
+ log(`Patched app package name to ${scopedName}`);
50
+ }
51
+ }
52
+ catch (err) {
53
+ log(`Warning: could not patch app package name: ${err instanceof Error ? err.message : String(err)}`);
54
+ }
34
55
  }
35
56
  return { ran: true, exitCode: result.exitCode };
36
57
  }
@@ -35,11 +35,7 @@ export declare const init: import("@powerhousedao/ph-clint").Command<z.ZodObject
35
35
  version: string;
36
36
  description: string;
37
37
  features: {
38
- powerhouse: {
39
- enabled: boolean;
40
- switchboard: boolean;
41
- connect: boolean;
42
- };
38
+ powerhouse: "Disabled" | "Reactor" | "Switchboard" | "Connect";
43
39
  mastra: {
44
40
  enabled: boolean;
45
41
  };
@@ -47,7 +43,16 @@ export declare const init: import("@powerhousedao/ph-clint").Command<z.ZodObject
47
43
  enabled: boolean;
48
44
  };
49
45
  };
50
- documentTypes: string[];
46
+ packages: {
47
+ id: string;
48
+ packageName: string;
49
+ documentTypes: string[];
50
+ }[];
51
+ externalSkills: {
52
+ id: string;
53
+ name: string;
54
+ githubUrl: string;
55
+ }[];
51
56
  scope?: string | undefined;
52
57
  bin?: string | undefined;
53
58
  };
@@ -2,7 +2,7 @@
2
2
  * `ph-clint clint-project-init` — the project bootstrap wizard.
3
3
  *
4
4
  * Behavior:
5
- * 1. Resolve target dir (positional `dir`, defaults to cwd).
5
+ * 1. Resolve target dir (positional `dir`, defaults to the project name).
6
6
  * 2. Assert it is empty (or `--force`).
7
7
  * 3. Collect project identity + feature toggles (prompt for missing).
8
8
  * 4. Assemble a ClintProjectSpec and validate with Zod.
@@ -22,7 +22,7 @@ const inputSchema = z.object({
22
22
  dir: z
23
23
  .string()
24
24
  .optional()
25
- .describe('Target directory (defaults to the current directory)'),
25
+ .describe('Target directory (defaults to the project name)'),
26
26
  name: z
27
27
  .string()
28
28
  .describe('Project name — bare, or `@scope/name`. Lowercase letters, digits, hyphens.'),
@@ -75,7 +75,7 @@ export function buildSpec(input) {
75
75
  scope,
76
76
  description: input.description ?? '',
77
77
  features: {
78
- powerhouse: { enabled: input.enablePowerhouse },
78
+ powerhouse: input.enablePowerhouse ? 'Connect' : 'Disabled',
79
79
  mastra: { enabled: input.enableMastra },
80
80
  routine: { enabled: routine },
81
81
  },
@@ -91,7 +91,8 @@ export const init = defineCommand({
91
91
  promptOptional: ['description'],
92
92
  },
93
93
  execute: async (input, { workdir, stdout }) => {
94
- const targetDir = path.resolve(workdir, input.dir ?? '.');
94
+ const { name: bareName } = splitPackageName(input.name);
95
+ const targetDir = path.resolve(workdir, input.dir ?? bareName);
95
96
  if (!input.force) {
96
97
  const empty = await isDirEmptyEnough(targetDir);
97
98
  if (!empty) {
@@ -109,7 +110,7 @@ export const init = defineCommand({
109
110
  allowNonEmpty: input.force,
110
111
  });
111
112
  stdout(`Generated ${result.files.length} files in ${targetDir}\n`);
112
- const split = spec.features.powerhouse.enabled;
113
+ const split = spec.features.powerhouse !== 'Disabled';
113
114
  // Reactor package scaffolding (split layout only).
114
115
  if (split && result.appDir && !input.skipPhInit) {
115
116
  const phResult = await runPhInit({
@@ -8,6 +8,7 @@ export declare const clintProjectPublish: import("@powerhousedao/ph-clint").Comm
8
8
  dir: z.ZodOptional<z.ZodString>;
9
9
  dryRun: z.ZodDefault<z.ZodBoolean>;
10
10
  skipBuild: z.ZodDefault<z.ZodBoolean>;
11
+ skipGitCheck: z.ZodDefault<z.ZodBoolean>;
11
12
  verbose: z.ZodDefault<z.ZodBoolean>;
12
13
  }, z.core.$strip>, {
13
14
  text: string;
@@ -7,6 +7,7 @@ const inputSchema = z.object({
7
7
  dir: z.string().optional().describe('Project root directory'),
8
8
  dryRun: z.boolean().default(false).describe('Preview without publishing'),
9
9
  skipBuild: z.boolean().default(false),
10
+ skipGitCheck: z.boolean().default(false).describe('Skip git working tree check'),
10
11
  verbose: z.boolean().default(false),
11
12
  });
12
13
  function formatPlanPreview(plan) {
@@ -34,11 +35,12 @@ export const clintProjectPublish = defineCommand({
34
35
  logLines.push(msg);
35
36
  stdout(msg + '\n');
36
37
  };
37
- // 1. Resolve plan
38
+ // 1. Resolve plan — discover config from the target dir, not process.cwd().
39
+ const configFile = path.join(dir, 'publish.config.ts');
38
40
  const plan = await resolvePublishPlan({
39
41
  tag: input.tag,
40
- configPath: undefined, // auto-discover from dir
41
- skipGitCheck: input.dryRun,
42
+ configPath: configFile,
43
+ skipGitCheck: input.dryRun || input.skipGitCheck,
42
44
  log,
43
45
  });
44
46
  // 2. Show preview
@@ -0,0 +1,10 @@
1
+ import { z } from 'zod';
2
+ export declare const skillsSync: import("@powerhousedao/ph-clint").Command<z.ZodObject<{
3
+ dir: z.ZodOptional<z.ZodString>;
4
+ }, z.core.$strip>, {
5
+ text: string;
6
+ data?: undefined;
7
+ } | {
8
+ text: string;
9
+ data: import("../skills/sync.js").SyncResult;
10
+ }, Record<string, unknown>>;