@powerhousedao/ph-clint-cli 0.1.0-dev.51 → 0.1.0-dev.53

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
@@ -7,7 +7,7 @@
7
7
  * Everything outside the markers is user-editable and preserved.
8
8
  */
9
9
  import path from 'node:path';
10
- import { defineCli, buildDefaultReactor } from '@powerhousedao/ph-clint';
10
+ import { defineCli, buildDefaultReactor, deterministicId } from '@powerhousedao/ph-clint';
11
11
  import { z } from 'zod';
12
12
  import { CLI_NAME, CLI_VERSION, CLI_ROOT } from './config.js';
13
13
  import { configSchema, secretsSchema } from './framework.js';
@@ -38,6 +38,7 @@ export const cli = defineCli({
38
38
  // @clint:end services
39
39
  // @clint:begin triggers
40
40
  triggers: [specChangeTrigger, publishTrigger],
41
+ routine: { id: 'triggers', name: 'Triggers' },
41
42
  // @clint:end triggers
42
43
  // @clint:begin prompts
43
44
  prompts: {
@@ -108,7 +109,7 @@ cli.configureReactor({
108
109
  create: (ctx) => buildDefaultReactor(ctx, {
109
110
  documentModels,
110
111
  drives: [
111
- { name: 'Clint Folders', role: 'personal' },
112
+ { name: 'Clint Folders', role: 'personal', id: deterministicId(CLI_NAME, 'personal-drive') },
112
113
  ],
113
114
  subscriptions: { documentTypes: ['powerhouse/ph-clint-project'] },
114
115
  }),
@@ -30,7 +30,7 @@ export type PostGenAction = {
30
30
  export type PostGenActionKind = PostGenAction['kind'];
31
31
  export interface PostGenActionContext {
32
32
  log: (msg: string) => void;
33
- runProcess?: (command: string, opts?: Omit<ProcessRunOptions, 'onOutput'>) => Promise<{
33
+ runProcess: (command: string, opts?: Omit<ProcessRunOptions, 'onOutput'>) => Promise<{
34
34
  success: boolean;
35
35
  output: string;
36
36
  }>;
@@ -389,20 +389,11 @@ async function executeAction(action, ctx) {
389
389
  }
390
390
  case 'app-install':
391
391
  case 'cli-install': {
392
- if (ctx.runProcess) {
393
- const result = await ctx.runProcess('pnpm install', {
394
- cwd: action.dir,
395
- timeout: 300_000,
396
- });
397
- return result.success;
398
- }
399
- // Fallback: direct spawn
400
- const { runCommand } = await import('./exec.js');
401
- const result = await runCommand('pnpm', ['install'], {
392
+ const result = await ctx.runProcess('pnpm install', {
402
393
  cwd: action.dir,
403
- stdio: 'inherit',
394
+ timeout: 300_000,
404
395
  });
405
- return result.exitCode === 0;
396
+ return result.success;
406
397
  }
407
398
  case 'app-build':
408
399
  case 'cli-build': {
@@ -420,20 +411,11 @@ async function executeAction(action, ctx) {
420
411
  }
421
412
  }
422
413
  async function runBuild(dir, ctx) {
423
- if (ctx.runProcess) {
424
- const result = await ctx.runProcess('pnpm build', {
425
- cwd: dir,
426
- timeout: 120_000,
427
- });
428
- return result.success;
429
- }
430
- // Fallback: direct spawn
431
- const { runCommand } = await import('./exec.js');
432
- const result = await runCommand('pnpm', ['build'], {
414
+ const result = await ctx.runProcess('pnpm build', {
433
415
  cwd: dir,
434
- stdio: 'inherit',
416
+ timeout: 120_000,
435
417
  });
436
- return result.exitCode === 0;
418
+ return result.success;
437
419
  }
438
420
  function formatDuration(ms) {
439
421
  if (ms < 1000)
@@ -7,23 +7,37 @@
7
7
  * own reactor package). External packages' document types are imported from
8
8
  * their own npm packages, not from the app barrel.
9
9
  */
10
- import { getAppPackageName, getDocumentTypeSlug, phAtLeast, } from '../../spec/types.js';
10
+ import { getAppPackageName, getDocumentTypeSlug, isDocTypeGlob, phAtLeast, } from '../../spec/types.js';
11
11
  export function buildAppIndexTs(spec) {
12
12
  if (!phAtLeast(spec.features.powerhouse, 'Reactor'))
13
13
  return null;
14
+ const lines = [];
15
+ // Standard Powerhouse reactor-package barrel — matches what `ph init`
16
+ // generates. Required by main.tsx (Connect) and cli.ts imports.
17
+ lines.push('// THIS FILE IS GENERATED BY ph-clint-cli.');
18
+ lines.push("import type { Manifest } from 'document-model';");
19
+ lines.push("import manifestJson from './powerhouse.manifest.json' with { type: 'json' };");
20
+ lines.push("export { documentModels } from './document-models/document-models.js';");
21
+ lines.push("export { upgradeManifests } from './document-models/upgrade-manifests.js';");
22
+ lines.push("export { editors } from './editors/editors.js';");
23
+ lines.push("export { processorFactory } from './processors/factory.js';");
24
+ lines.push('export const manifest: Manifest = manifestJson;');
25
+ // Per-document-type re-exports so impl code can import from
26
+ // '{name}-app' instead of drilling into deep paths.
14
27
  const appPkg = getAppPackageName(spec);
15
28
  const appEntry = spec.packages.find((p) => p.packageName === appPkg);
16
29
  const docTypes = appEntry?.documentTypes ?? [];
17
- if (docTypes.length === 0)
18
- return null;
19
- const lines = [];
20
- lines.push('// THIS FILE IS GENERATED BY ph-clint-cli.');
21
- lines.push('// Re-exports every registered document model from a single top-level');
22
- lines.push('// entry so impl code can `import { ... } from \'{name}-app\'`.');
23
- lines.push('');
24
- for (const docType of docTypes) {
25
- const slug = getDocumentTypeSlug(docType);
26
- lines.push(`export * from './document-models/${slug}/index.js';`);
30
+ if (docTypes.length > 0) {
31
+ const hasGlobs = docTypes.some(isDocTypeGlob);
32
+ const explicits = docTypes.filter((dt) => !isDocTypeGlob(dt));
33
+ lines.push('');
34
+ if (hasGlobs) {
35
+ lines.push("export * from './document-models/index.js';");
36
+ }
37
+ for (const docType of explicits) {
38
+ const slug = getDocumentTypeSlug(docType);
39
+ lines.push(`export * from './document-models/${slug}/index.js';`);
40
+ }
27
41
  }
28
42
  return lines.join('\n') + '\n';
29
43
  }
@@ -1,13 +1,19 @@
1
1
  /**
2
2
  * Builds `src/framework.gen.ts` — the fully machine-owned half of the
3
3
  * typed-binding split. Regenerated on every `init` and `update`; holds the
4
- * `defineRegistry([...] as const)` call that describes which document models
4
+ * `defineRegistry([...])` call that describes which document models
5
5
  * are bound into `framework.ts`'s typed factories.
6
6
  *
7
7
  * Iterates all packages' document types. The app package's modules are
8
8
  * imported from the local `{name}-app` package; external packages' modules
9
9
  * are imported from their npm package name.
10
10
  *
11
+ * Document type entries may be explicit IDs (`powerhouse/ph-clint-project`)
12
+ * or glob patterns (`*​/*`, `powerhouse/*`, `acme/doc-*`). Explicit IDs
13
+ * produce static named imports; glob patterns import the package's
14
+ * `documentModels` array and filter at runtime so newly-added document
15
+ * models are picked up without a codegen re-run.
16
+ *
11
17
  * Emitted only when Powerhouse is enabled — flat-layout impls have no
12
18
  * reactor package to import modules from and bind `createTypes` directly in
13
19
  * `framework.ts`. When Powerhouse is on but no document types are declared,
@@ -1,13 +1,19 @@
1
1
  /**
2
2
  * Builds `src/framework.gen.ts` — the fully machine-owned half of the
3
3
  * typed-binding split. Regenerated on every `init` and `update`; holds the
4
- * `defineRegistry([...] as const)` call that describes which document models
4
+ * `defineRegistry([...])` call that describes which document models
5
5
  * are bound into `framework.ts`'s typed factories.
6
6
  *
7
7
  * Iterates all packages' document types. The app package's modules are
8
8
  * imported from the local `{name}-app` package; external packages' modules
9
9
  * are imported from their npm package name.
10
10
  *
11
+ * Document type entries may be explicit IDs (`powerhouse/ph-clint-project`)
12
+ * or glob patterns (`*​/*`, `powerhouse/*`, `acme/doc-*`). Explicit IDs
13
+ * produce static named imports; glob patterns import the package's
14
+ * `documentModels` array and filter at runtime so newly-added document
15
+ * models are picked up without a codegen re-run.
16
+ *
11
17
  * Emitted only when Powerhouse is enabled — flat-layout impls have no
12
18
  * reactor package to import modules from and bind `createTypes` directly in
13
19
  * `framework.ts`. When Powerhouse is on but no document types are declared,
@@ -15,28 +21,44 @@
15
21
  * `import { registry } from './framework.gen.js'` keeps resolving across
16
22
  * regens.
17
23
  */
18
- import { getAppPackageName, getDocumentTypeModuleName, getDocumentTypeSlug, phAtLeast, } from '../../spec/types.js';
24
+ import { docTypeGlobToRegex, getAppPackageName, getDocumentTypeModuleName, getDocumentTypeSlug, isDocTypeGlob, phAtLeast, } from '../../spec/types.js';
19
25
  export function buildFrameworkGenTs(spec) {
20
26
  if (!phAtLeast(spec.features.powerhouse, 'Reactor'))
21
27
  return null;
22
28
  const appPkg = getAppPackageName(spec);
23
- // Collect all (docType, moduleName, importFrom) tuples across packages.
24
- // App-package modules are imported from the app barrel which re-exports
25
- // bare PascalCase names. External packages use the per-slug deep subpath
26
- // (`<pkg>/document-models/<slug>`) so TypeScript can resolve internal
27
- // types (e.g. PHState) from stable import paths for declaration emit.
29
+ // Collect entries across packages. Each package may contribute both
30
+ // explicit named imports and glob-based runtime filters.
28
31
  const entries = [];
32
+ let globCounter = 0;
29
33
  for (const pkg of spec.packages) {
30
34
  const isApp = pkg.packageName === appPkg;
31
- for (const docType of pkg.documentTypes) {
35
+ // Collect glob patterns for this package (may be multiple).
36
+ const globs = pkg.documentTypes.filter(isDocTypeGlob);
37
+ const explicits = pkg.documentTypes.filter((dt) => !isDocTypeGlob(dt));
38
+ for (const docType of explicits) {
32
39
  const slug = getDocumentTypeSlug(docType);
33
40
  entries.push({
34
- docType,
41
+ kind: 'explicit',
35
42
  name: getDocumentTypeModuleName(docType),
36
43
  importFrom: isApp ? appPkg : `${pkg.packageName}/document-models/${slug}`,
37
44
  });
38
45
  }
46
+ if (globs.length > 0) {
47
+ // Merge all globs for this package into a single regex alternation.
48
+ const combined = globs.length === 1
49
+ ? docTypeGlobToRegex(globs[0])
50
+ : globs.map((g) => `(?:${docTypeGlobToRegex(g)})`).join('|');
51
+ const alias = globCounter === 0 ? 'globModels' : `globModels${globCounter}`;
52
+ globCounter++;
53
+ entries.push({
54
+ kind: 'glob',
55
+ modelsAlias: alias,
56
+ importFrom: isApp ? appPkg : pkg.packageName,
57
+ regexSource: combined,
58
+ });
59
+ }
39
60
  }
61
+ const hasGlobs = entries.some((e) => e.kind === 'glob');
40
62
  const lines = [];
41
63
  lines.push('/**');
42
64
  lines.push(' * THIS FILE IS GENERATED BY ph-clint-cli. DO NOT EDIT.');
@@ -46,8 +68,14 @@ export function buildFrameworkGenTs(spec) {
46
68
  lines.push(' * this file will be overwritten.');
47
69
  lines.push(' */');
48
70
  lines.push(`import { defineRegistry } from '@powerhousedao/ph-clint';`);
71
+ // Emit imports — named imports for explicit entries, documentModels for globs.
49
72
  for (const e of entries) {
50
- lines.push(`import { ${e.name} } from '${e.importFrom}';`);
73
+ if (e.kind === 'explicit') {
74
+ lines.push(`import { ${e.name} } from '${e.importFrom}';`);
75
+ }
76
+ else {
77
+ lines.push(`import { documentModels as ${e.modelsAlias} } from '${e.importFrom}';`);
78
+ }
51
79
  }
52
80
  lines.push('');
53
81
  lines.push('/**');
@@ -57,13 +85,29 @@ export function buildFrameworkGenTs(spec) {
57
85
  if (entries.length === 0) {
58
86
  lines.push('export const registry = defineRegistry([] as const);');
59
87
  }
60
- else {
88
+ else if (!hasGlobs) {
89
+ // Pure explicit — use `as const` for full type inference.
61
90
  lines.push('export const registry = defineRegistry([');
62
91
  for (const e of entries) {
63
- lines.push(` ${e.name},`);
92
+ if (e.kind === 'explicit') {
93
+ lines.push(` ${e.name},`);
94
+ }
64
95
  }
65
96
  lines.push('] as const);');
66
97
  }
98
+ else {
99
+ // Mix of explicit + glob — runtime filter loses `as const` inference.
100
+ lines.push('export const registry = defineRegistry([');
101
+ for (const e of entries) {
102
+ if (e.kind === 'explicit') {
103
+ lines.push(` ${e.name},`);
104
+ }
105
+ else {
106
+ lines.push(` ...${e.modelsAlias}.filter((m) => /${e.regexSource}/.test(m.documentModel.global.id)),`);
107
+ }
108
+ }
109
+ lines.push(']);');
110
+ }
67
111
  lines.push('');
68
112
  lines.push('export type Registry = typeof registry;');
69
113
  return lines.join('\n') + '\n';
@@ -1,20 +1,2 @@
1
- export interface RunCommandOptions {
2
- cwd: string;
3
- /** Override stdio; default `'inherit'`. Pass `'ignore'` for tests. */
4
- stdio?: 'inherit' | 'ignore';
5
- /** Extra env vars (merged with process.env). */
6
- env?: Record<string, string>;
7
- }
8
- export interface RunCommandResult {
9
- command: string;
10
- args: string[];
11
- cwd: string;
12
- exitCode: number;
13
- }
14
- /**
15
- * Run `command args` in `cwd`, forwarding stdio by default. Resolves to
16
- * the exit code. Does NOT throw on non-zero — callers decide how to react.
17
- */
18
- export declare function runCommand(command: string, args: string[], options: RunCommandOptions): Promise<RunCommandResult>;
19
1
  /** Returns true if `command` is on PATH. */
20
2
  export declare function hasCommandOnPath(command: string): Promise<boolean>;
@@ -1,34 +1,7 @@
1
1
  /**
2
2
  * Small helpers for invoking external commands during code generation.
3
- *
4
- * We deliberately keep this separate from `ph-clint`'s process manager:
5
- * here we need synchronous-feeling prompt-forwarding (stdio inherited) so
6
- * the user can respond to interactive prompts from `ph init` or from a
7
- * package manager.
8
3
  */
9
4
  import { spawn } from 'node:child_process';
10
- /**
11
- * Run `command args` in `cwd`, forwarding stdio by default. Resolves to
12
- * the exit code. Does NOT throw on non-zero — callers decide how to react.
13
- */
14
- export async function runCommand(command, args, options) {
15
- return new Promise((resolve, reject) => {
16
- const child = spawn(command, args, {
17
- cwd: options.cwd,
18
- stdio: options.stdio ?? 'inherit',
19
- env: options.env ? { ...process.env, ...options.env } : process.env,
20
- });
21
- child.once('error', reject);
22
- child.once('close', (code) => {
23
- resolve({
24
- command,
25
- args,
26
- cwd: options.cwd,
27
- exitCode: code ?? 0,
28
- });
29
- });
30
- });
31
- }
32
5
  /** Returns true if `command` is on PATH. */
33
6
  export async function hasCommandOnPath(command) {
34
7
  return new Promise((resolve) => {
@@ -7,6 +7,7 @@ type RunProcess = (command: string, opts?: Omit<ProcessRunOptions, 'onOutput'>)
7
7
  success: boolean;
8
8
  output: string;
9
9
  }>;
10
+ export type { RunProcess };
10
11
  export interface PhInitOptions {
11
12
  /** Absolute path to the project root. */
12
13
  targetDir: string;
@@ -17,8 +18,8 @@ export interface PhInitOptions {
17
18
  log?: (msg: string) => void;
18
19
  /** Override `ph` binary name (for tests). */
19
20
  binName?: string;
20
- /** Subprocess runner — uses CommandContext.runProcess when available. */
21
- runProcess?: RunProcess;
21
+ /** Subprocess runner — matches CommandContext.runProcess. */
22
+ runProcess: RunProcess;
22
23
  /** Explicit Powerhouse version to pin `ph init` to (tag or semver). */
23
24
  phVersion?: string;
24
25
  }
@@ -52,7 +53,7 @@ export declare function runPhInstallPackages(options: {
52
53
  appDir: string;
53
54
  packages: string[];
54
55
  log?: (msg: string) => void;
55
- runProcess?: RunProcess;
56
+ runProcess: RunProcess;
56
57
  }): Promise<boolean>;
57
58
  export interface PnpmInstallOptions {
58
59
  /** Directories to run `pnpm install` in, in order. */
@@ -60,8 +61,8 @@ export interface PnpmInstallOptions {
60
61
  log?: (msg: string) => void;
61
62
  /** Override `pnpm` binary name (for tests). */
62
63
  binName?: string;
63
- /** Subprocess runner — uses CommandContext.runProcess when available. */
64
- runProcess?: RunProcess;
64
+ /** Subprocess runner — matches CommandContext.runProcess. */
65
+ runProcess: RunProcess;
65
66
  }
66
67
  export interface PnpmInstallResult {
67
68
  ran: string[];
@@ -69,4 +70,3 @@ export interface PnpmInstallResult {
69
70
  reason?: string;
70
71
  }
71
72
  export declare function runPnpmInstall(options: PnpmInstallOptions): Promise<PnpmInstallResult>;
72
- export {};
@@ -60,30 +60,17 @@ export async function runPhInit(options) {
60
60
  await fs.rm(options.appDir, { recursive: true, force: true });
61
61
  const args = ['init', appFolder, ...versionArgs, '--pnpm'];
62
62
  log(`Running \`${binName} init ${appFolder}\` (version: ${phVersion ?? 'auto'}) in ${options.targetDir} …`);
63
- const run = options.runProcess;
64
- if (run) {
65
- const result = await run(`${binName} ${args.join(' ')}`, {
66
- cwd: options.targetDir,
67
- timeout: 300_000, // ph init + pnpm install can be slow
68
- });
69
- const exitCode = result.success ? 0 : 1;
70
- if (!result.success) {
71
- log(`\`${binName} init\` failed. ` +
72
- 'You can re-run it manually after resolving the issue.');
73
- }
74
- // Patch scoped name
75
- await patchScopedName(options, appFolder, log);
76
- return { ran: true, exitCode };
77
- }
78
- // Fallback: direct spawn with stdio inherit (for backward compat / tests without context)
79
- const { runCommand } = await import('./exec.js');
80
- const result = await runCommand(binName, args, { cwd: options.targetDir, stdio: 'inherit' });
81
- if (result.exitCode !== 0) {
82
- log(`\`${binName} init\` exited with code ${result.exitCode}. ` +
63
+ const result = await options.runProcess(`${binName} ${args.join(' ')}`, {
64
+ cwd: options.targetDir,
65
+ timeout: 300_000,
66
+ });
67
+ const exitCode = result.success ? 0 : 1;
68
+ if (!result.success) {
69
+ log(`\`${binName} init\` failed. ` +
83
70
  'You can re-run it manually after resolving the issue.');
84
71
  }
85
72
  await patchScopedName(options, appFolder, log);
86
- return { ran: true, exitCode: result.exitCode };
73
+ return { ran: true, exitCode };
87
74
  }
88
75
  /**
89
76
  * Patch the app `package.json` name to the scoped form (`@scope/name-app`).
@@ -132,19 +119,11 @@ export async function runPhInstallPackages(options) {
132
119
  }
133
120
  const args = ['install', ...packages, '--local', '--pnpm'];
134
121
  log(`Running \`${binName} install ${packages.join(' ')} --local\` in ${path.basename(appDir)} …`);
135
- if (runProcess) {
136
- const result = await runProcess(`${binName} ${args.join(' ')}`, {
137
- cwd: appDir,
138
- timeout: 300_000,
139
- });
140
- return result.success;
141
- }
142
- const { runCommand } = await import('./exec.js');
143
- const result = await runCommand(binName, args, {
122
+ const result = await runProcess(`${binName} ${args.join(' ')}`, {
144
123
  cwd: appDir,
145
- stdio: 'inherit',
124
+ timeout: 300_000,
146
125
  });
147
- return result.exitCode === 0;
126
+ return result.success;
148
127
  }
149
128
  export async function runPnpmInstall(options) {
150
129
  const log = options.log ?? (() => { });
@@ -157,27 +136,13 @@ export async function runPnpmInstall(options) {
157
136
  const ran = [];
158
137
  for (const dir of options.dirs) {
159
138
  log(`Running \`${binName} install\` in ${dir} …`);
160
- const run = options.runProcess;
161
- if (run) {
162
- const result = await run(`${binName} install`, {
163
- cwd: dir,
164
- timeout: 300_000,
165
- });
166
- if (!result.success) {
167
- log(`\`${binName} install\` failed in ${dir}. ` +
168
- 'Retry manually once the underlying issue is resolved.');
169
- }
170
- }
171
- else {
172
- const { runCommand } = await import('./exec.js');
173
- const result = await runCommand(binName, ['install'], {
174
- cwd: dir,
175
- stdio: 'inherit',
176
- });
177
- if (result.exitCode !== 0) {
178
- log(`\`${binName} install\` exited with code ${result.exitCode} in ${dir}. ` +
179
- 'Retry manually once the underlying issue is resolved.');
180
- }
139
+ const result = await options.runProcess(`${binName} install`, {
140
+ cwd: dir,
141
+ timeout: 300_000,
142
+ });
143
+ if (!result.success) {
144
+ log(`\`${binName} install\` failed in ${dir}. ` +
145
+ 'Retry manually once the underlying issue is resolved.');
181
146
  }
182
147
  ran.push(dir);
183
148
  }
@@ -4,4 +4,9 @@ export declare const build: import("@powerhousedao/ph-clint").Command<z.ZodObjec
4
4
  verbose: z.ZodDefault<z.ZodBoolean>;
5
5
  }, z.core.$strip>, {
6
6
  text: string;
7
- }, Record<string, unknown>>;
7
+ }, {
8
+ model: string;
9
+ devServicePort: number;
10
+ phVersion: string;
11
+ apiKey?: string | undefined;
12
+ }, import("@powerhousedao/ph-clint").InferRegistry<readonly [import("document-model").DocumentModelModule<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>]>>;
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { defineCommand } from '@powerhousedao/ph-clint';
2
+ import { defineCommand } from '../framework.js';
3
3
  import { z } from 'zod';
4
4
  import { detectLayout } from '@powerhousedao/ph-clint-dev';
5
5
  const inputSchema = z.object({
@@ -78,5 +78,10 @@ export declare const init: import("@powerhousedao/ph-clint").Command<z.ZodObject
78
78
  };
79
79
  fileCount: number;
80
80
  };
81
- }, Record<string, unknown>>;
81
+ }, {
82
+ model: string;
83
+ devServicePort: number;
84
+ phVersion: string;
85
+ apiKey?: string | undefined;
86
+ }, import("@powerhousedao/ph-clint").InferRegistry<readonly [import("document-model").DocumentModelModule<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>]>>;
82
87
  export {};
@@ -12,7 +12,7 @@
12
12
  * 8. Print next-steps guidance.
13
13
  */
14
14
  import path from 'node:path';
15
- import { defineCommand } from '@powerhousedao/ph-clint';
15
+ import { defineCommand } from '../framework.js';
16
16
  import { z } from 'zod';
17
17
  import { clintProjectSpecSchema, } from '../spec/types.js';
18
18
  import { generateProject } from '../codegen/index.js';
@@ -13,4 +13,9 @@ export declare const clintProjectPublish: import("@powerhousedao/ph-clint").Comm
13
13
  }, z.core.$strip>, {
14
14
  text: string;
15
15
  data: import("@powerhousedao/ph-clint-dev/publish").PublishResult;
16
- }, Record<string, unknown>>;
16
+ }, {
17
+ model: string;
18
+ devServicePort: number;
19
+ phVersion: string;
20
+ apiKey?: string | undefined;
21
+ }, import("@powerhousedao/ph-clint").InferRegistry<readonly [import("document-model").DocumentModelModule<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>]>>;
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { defineCommand } from '@powerhousedao/ph-clint';
2
+ import { defineCommand } from '../framework.js';
3
3
  import { z } from 'zod';
4
4
  import { resolvePublishPlan, buildPackages, publishPackages, } from '@powerhousedao/ph-clint-dev/publish';
5
5
  const inputSchema = z.object({
@@ -15,4 +15,9 @@ export declare const regen: import("@powerhousedao/ph-clint").Command<z.ZodObjec
15
15
  migrated: boolean;
16
16
  warnings: string[];
17
17
  };
18
- }, Record<string, unknown>>;
18
+ }, {
19
+ model: string;
20
+ devServicePort: number;
21
+ phVersion: string;
22
+ apiKey?: string | undefined;
23
+ }, import("@powerhousedao/ph-clint").InferRegistry<readonly [import("document-model").DocumentModelModule<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>]>>;
@@ -7,7 +7,7 @@
7
7
  * migration fires first.
8
8
  */
9
9
  import path from 'node:path';
10
- import { defineCommand } from '@powerhousedao/ph-clint';
10
+ import { defineCommand } from '../framework.js';
11
11
  import { z } from 'zod';
12
12
  import { readProjectSpec } from '../spec/file.js';
13
13
  import { generateProject } from '../codegen/index.js';
@@ -7,4 +7,9 @@ export declare const skillsSync: import("@powerhousedao/ph-clint").Command<z.Zod
7
7
  } | {
8
8
  text: string;
9
9
  data: import("../skills/sync.js").SyncResult;
10
- }, Record<string, unknown>>;
10
+ }, {
11
+ model: string;
12
+ devServicePort: number;
13
+ phVersion: string;
14
+ apiKey?: string | undefined;
15
+ }, import("@powerhousedao/ph-clint").InferRegistry<readonly [import("document-model").DocumentModelModule<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>]>>;
@@ -4,7 +4,7 @@
4
4
  * longer referenced, and updates `.skills-manifest.json`.
5
5
  */
6
6
  import path from 'node:path';
7
- import { defineCommand } from '@powerhousedao/ph-clint';
7
+ import { defineCommand } from '../framework.js';
8
8
  import { z } from 'zod';
9
9
  import { readProjectSpec } from '../spec/file.js';
10
10
  import { syncExternalSkills } from '../skills/sync.js';
@@ -36,7 +36,7 @@ export declare const defineCommand: <TInput extends z.ZodType, TOutput = unknown
36
36
  devServicePort: number;
37
37
  phVersion: string;
38
38
  apiKey?: string | undefined;
39
- }>, defineTrigger: <TState = Record<string, unknown>>(options: import("@powerhousedao/ph-clint").TriggerOptions<TState, {
39
+ }, import("@powerhousedao/ph-clint").InferRegistry<readonly [import("document-model").DocumentModelModule<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>]>>, defineTrigger: <TState = Record<string, unknown>>(options: import("@powerhousedao/ph-clint").TriggerOptions<TState, {
40
40
  model: string;
41
41
  devServicePort: number;
42
42
  phVersion: string;
@@ -143,3 +143,22 @@ export declare function getDocumentTypeSlug(documentType: string): string;
143
143
  * it needs both for the import and the `defineRegistry` entry.
144
144
  */
145
145
  export declare function getDocumentTypeModuleName(documentType: string): string;
146
+ /**
147
+ * Returns true when a documentType entry contains glob wildcards (`*`).
148
+ * Glob entries like `*​/*`, `powerhouse/*`, or `powerhouse/doc-*` are spec
149
+ * shorthand meaning "all matching document types from this package" and
150
+ * must be resolved at runtime rather than emitted as static imports.
151
+ */
152
+ export declare function isDocTypeGlob(documentType: string): boolean;
153
+ /**
154
+ * Convert a documentType glob pattern to a regex source string suitable
155
+ * for embedding in a `/pattern/` literal. Each `*` matches any non-`/`
156
+ * characters. Forward slashes are escaped so the output is safe inside
157
+ * `/.../`.
158
+ *
159
+ * Examples:
160
+ * `*​/*` → `^[^\/]*\/[^\/]*$` (any org, any name)
161
+ * `powerhouse/*` → `^powerhouse\/[^\/]*$`
162
+ * `powerhouse/doc-*` → `^powerhouse\/doc-[^\/]*$`
163
+ */
164
+ export declare function docTypeGlobToRegex(pattern: string): string;
@@ -182,3 +182,28 @@ export function getDocumentTypeModuleName(documentType) {
182
182
  .map((s) => s[0].toUpperCase() + s.slice(1))
183
183
  .join('');
184
184
  }
185
+ /**
186
+ * Returns true when a documentType entry contains glob wildcards (`*`).
187
+ * Glob entries like `*​/*`, `powerhouse/*`, or `powerhouse/doc-*` are spec
188
+ * shorthand meaning "all matching document types from this package" and
189
+ * must be resolved at runtime rather than emitted as static imports.
190
+ */
191
+ export function isDocTypeGlob(documentType) {
192
+ return documentType.includes('*');
193
+ }
194
+ /**
195
+ * Convert a documentType glob pattern to a regex source string suitable
196
+ * for embedding in a `/pattern/` literal. Each `*` matches any non-`/`
197
+ * characters. Forward slashes are escaped so the output is safe inside
198
+ * `/.../`.
199
+ *
200
+ * Examples:
201
+ * `*​/*` → `^[^\/]*\/[^\/]*$` (any org, any name)
202
+ * `powerhouse/*` → `^powerhouse\/[^\/]*$`
203
+ * `powerhouse/doc-*` → `^powerhouse\/doc-[^\/]*$`
204
+ */
205
+ export function docTypeGlobToRegex(pattern) {
206
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\\/]/g, '\\$&');
207
+ const withWildcards = escaped.replace(/\*/g, '[^\\/]*');
208
+ return `^${withWildcards}$`;
209
+ }
@@ -18,7 +18,6 @@
18
18
  * b. Runs `ph-clint clint-project-publish --tag <tag>`.
19
19
  * c. Dispatches SET_PUBLISH_STATUS({ id, status: "Succeeded"|"Failed" }).
20
20
  */
21
- import { spawn } from 'node:child_process';
22
21
  import path from 'node:path';
23
22
  import { scanProjects, getProjectMapping } from '@powerhousedao/ph-clint';
24
23
  import { setPublishStatus } from '@powerhousedao/ph-clint-app';
@@ -27,37 +26,6 @@ import { specFromDocumentState } from '../spec/from-document.js';
27
26
  import { clintProject } from '../services/clint-project.js';
28
27
  const DOCUMENT_TYPE = 'powerhouse/ph-clint-project';
29
28
  const TAG = '[publish]';
30
- /** Run a shell command and return stdout. Rejects on non-zero exit. */
31
- function exec(cmd, args, cwd, onLog) {
32
- return new Promise((resolve, reject) => {
33
- const proc = spawn(cmd, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
34
- const out = [];
35
- const err = [];
36
- proc.stdout?.on('data', (d) => {
37
- const s = d.toString();
38
- out.push(s);
39
- if (onLog)
40
- for (const line of s.split('\n'))
41
- if (line.trim())
42
- onLog(line.trim());
43
- });
44
- proc.stderr?.on('data', (d) => {
45
- const s = d.toString();
46
- err.push(s);
47
- if (onLog)
48
- for (const line of s.split('\n'))
49
- if (line.trim())
50
- onLog(line.trim());
51
- });
52
- proc.on('error', reject);
53
- proc.on('close', (code) => {
54
- if (code === 0)
55
- resolve(out.join(''));
56
- else
57
- reject(new Error(`${cmd} ${args.join(' ')} exited ${code}\n${err.join('')}`));
58
- });
59
- });
60
- }
61
29
  /** Map document model PublishTag values to CLI --tag values. */
62
30
  export function tagToCli(tag) {
63
31
  switch (tag) {
@@ -107,6 +75,7 @@ export const publishTrigger = createDocumentChangeTrigger({
107
75
  continue;
108
76
  }
109
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;
110
79
  return {
111
80
  type: 'function',
112
81
  params: {
@@ -122,25 +91,20 @@ export const publishTrigger = createDocumentChangeTrigger({
122
91
  setPublishStatus({ id: record.id, status: 'InProgress' }),
123
92
  ]);
124
93
  // Install dependencies in the generated project before publishing.
125
- log?.info(`${TAG} running pnpm install in ${workdir}`);
126
- try {
127
- await exec('pnpm', ['install'], workdir, (msg) => log?.debug(`${TAG} [install] ${msg}`));
128
- }
129
- catch (installErr) {
130
- log?.error(`${TAG} pnpm install failed: ${installErr instanceof Error ? installErr.message : String(installErr)}`);
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}`);
131
101
  }
132
102
  // Run the publish command.
133
103
  let success = false;
134
104
  try {
135
- log?.info(`${TAG} invoking clint-project-publish --tag ${cliTag}`);
105
+ stdout(`${TAG} invoking clint-project-publish --tag ${cliTag}\n`);
136
106
  const { clintProjectPublish } = await import('../commands/clint-project-publish.js');
137
- const result = await clintProjectPublish.execute({ tag: cliTag, dryRun: false, skipBuild: false, skipGitCheck: true, verbose: false },
138
- // @ts-expect-error — minimal context; publish uses workdir + stdout.
139
- {
140
- workdir,
141
- stdout: (msg) => log?.info(`${TAG} ${msg}`),
142
- log,
143
- });
107
+ const result = await clintProjectPublish.execute({ tag: cliTag, dir: workdir, dryRun: false, skipBuild: false, skipGitCheck: true, verbose: false }, ctx.commandContext);
144
108
  success = !result.text.includes('failed');
145
109
  }
146
110
  catch (err) {
@@ -148,7 +112,7 @@ export const publishTrigger = createDocumentChangeTrigger({
148
112
  }
149
113
  // Update status.
150
114
  const finalStatus = success ? 'Succeeded' : 'Failed';
151
- log?.info(`${TAG} marking record ${record.id} as ${finalStatus}`);
115
+ stdout(`${TAG} marking record ${record.id} as ${finalStatus}\n`);
152
116
  await reactor.client.execute(doc.header.id, 'main', [
153
117
  setPublishStatus({
154
118
  id: record.id,
@@ -105,12 +105,18 @@ export function specToImportInput(spec) {
105
105
  name: s.name,
106
106
  githubUrl: s.githubUrl,
107
107
  })),
108
+ agentDescription: spec.features.mastra.agentDescription ?? null,
109
+ agentImage: spec.features.mastra.agentImage ?? null,
110
+ enableChat: spec.features.mastra.common.enableChat,
111
+ proxyEnabled: spec.deployment.proxyEnabled,
112
+ supportedResources: spec.deployment.supportedResources,
108
113
  };
109
114
  }
110
115
  export const specChangeTrigger = createDocumentChangeTrigger({
111
116
  id: 'spec-change',
112
117
  documentType: DOCUMENT_TYPE,
113
118
  // No documentId → watches ALL ph-clint-project docs
119
+ callOnEmpty: true, // bootstrap from disk when no documents exist yet
114
120
  async onChange(docs, ctx) {
115
121
  const log = ctx.context.log;
116
122
  const folders = ctx.context.folders;
@@ -187,8 +193,10 @@ export const specChangeTrigger = createDocumentChangeTrigger({
187
193
  log.warn(`${TAG} ${w}`);
188
194
  }
189
195
  // Run post-generation actions (ph-init, install, build, skills-sync).
196
+ // Route through ctx.context.stdout so output flows through
197
+ // routine.onOutput → service log buffer → ServicePanel.
190
198
  await runPostGenActions(result.pendingActions, {
191
- log: (msg) => log?.info(`${TAG} ${msg}`),
199
+ log: (msg) => ctx.context.stdout(`${TAG} ${msg}\n`),
192
200
  runProcess: ctx.context.runProcess,
193
201
  });
194
202
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powerhousedao/ph-clint-cli",
3
- "version": "0.1.0-dev.51",
3
+ "version": "0.1.0-dev.53",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -29,9 +29,9 @@
29
29
  "@mastra/core": "^1.22.0",
30
30
  "@mastra/libsql": "^1.7.4",
31
31
  "@mastra/memory": "^1.13.1",
32
- "@powerhousedao/ph-clint": "^0.1.0-dev.51",
33
- "@powerhousedao/ph-clint-app": "^0.1.0-dev.51",
34
- "@powerhousedao/ph-clint-dev": "^0.1.0-dev.51",
32
+ "@powerhousedao/ph-clint": "^0.1.0-dev.53",
33
+ "@powerhousedao/ph-clint-app": "^0.1.0-dev.53",
34
+ "@powerhousedao/ph-clint-dev": "^0.1.0-dev.53",
35
35
  "@powerhousedao/reactor": "6.0.0-dev.170",
36
36
  "@powerhousedao/reactor-api": "6.0.0-dev.170",
37
37
  "@powerhousedao/shared": "6.0.0-dev.170",