@powerhousedao/ph-clint-cli 0.1.0-dev.21 → 0.1.0-dev.23
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 +3 -1
- package/dist/codegen/builders/agent-ts.d.ts +4 -4
- package/dist/codegen/builders/agent-ts.js +83 -2
- package/dist/codegen/builders/cli-ts.js +14 -1
- package/dist/codegen/builders/framework-ts.js +2 -1
- package/dist/codegen/builders/index.d.ts +5 -0
- package/dist/codegen/builders/index.js +17 -1
- package/dist/codegen/builders/mastra-index-ts.d.ts +3 -0
- package/dist/codegen/builders/mastra-index-ts.js +23 -3
- package/dist/codegen/index.js +3 -2
- package/dist/commands/clint-project-init.d.ts +13 -0
- package/dist/commands/clint-project-init.js +35 -2
- package/dist/framework.d.ts +0 -9
- package/dist/framework.js +0 -4
- package/dist/services/clint-project.js +21 -0
- package/dist/spec/from-document.d.ts +4 -1
- package/dist/spec/from-document.js +3 -1
- package/dist/spec/types.d.ts +24 -0
- package/dist/spec/types.js +24 -1
- package/dist/triggers/publish-trigger.d.ts +0 -1
- package/dist/triggers/publish-trigger.js +95 -98
- package/dist/triggers/spec-change.d.ts +5 -3
- package/dist/triggers/spec-change.js +170 -153
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -68,7 +68,9 @@ export const cli = defineCli({
|
|
|
68
68
|
cli.configureReactor({
|
|
69
69
|
create: (ctx) => buildDefaultReactor(ctx, {
|
|
70
70
|
documentModels,
|
|
71
|
-
|
|
71
|
+
drives: [
|
|
72
|
+
{ name: 'Clint Folders', role: 'personal' },
|
|
73
|
+
],
|
|
72
74
|
subscriptions: { documentTypes: ['powerhouse/ph-clint-project'] },
|
|
73
75
|
}),
|
|
74
76
|
switchboard: { enabled: true },
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Builds `src/agents/agent.ts` — a
|
|
2
|
+
* Builds `src/agents/agent.ts` — a Mastra agent factory.
|
|
3
3
|
*
|
|
4
|
-
* Emitted only when `features.mastra.enabled`.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Emitted only when `features.mastra.enabled`. When the spec includes agent
|
|
5
|
+
* config (agentId, models, profiles), generates a real `@mastra/core` Agent.
|
|
6
|
+
* Otherwise falls back to a deterministic echo-style demo provider.
|
|
7
7
|
*/
|
|
8
8
|
import { type ClintProjectSpec } from '../../spec/types.js';
|
|
9
9
|
export declare function buildAgentTs(spec: ClintProjectSpec): string;
|
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/** Map provider prefix to AI SDK package. */
|
|
2
|
+
function providerImport(provider) {
|
|
3
|
+
switch (provider) {
|
|
4
|
+
case 'anthropic':
|
|
5
|
+
return { pkg: '@ai-sdk/anthropic', fn: 'anthropic' };
|
|
6
|
+
case 'openai':
|
|
7
|
+
return { pkg: '@ai-sdk/openai', fn: 'openai' };
|
|
8
|
+
case 'google':
|
|
9
|
+
return { pkg: '@ai-sdk/google', fn: 'google' };
|
|
10
|
+
default:
|
|
11
|
+
return { pkg: `@ai-sdk/${provider}`, fn: provider };
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function buildDemoAgent(spec) {
|
|
15
|
+
const id = spec.features.mastra.agentId ?? spec.name;
|
|
3
16
|
return [
|
|
4
17
|
"import type { AgentProvider, AgentSetupContext, StreamChunk } from '@powerhousedao/ph-clint';",
|
|
5
18
|
"import type { Config } from '../framework.js';",
|
|
@@ -32,3 +45,71 @@ export function buildAgentTs(spec) {
|
|
|
32
45
|
'',
|
|
33
46
|
].join('\n');
|
|
34
47
|
}
|
|
48
|
+
function buildRealAgent(spec) {
|
|
49
|
+
const mastra = spec.features.mastra;
|
|
50
|
+
const agentId = mastra.agentId;
|
|
51
|
+
const agentName = mastra.agentName;
|
|
52
|
+
const defaultModel = mastra.models.find(m => m.isDefault) ?? mastra.models[0];
|
|
53
|
+
const modelId = defaultModel?.id ?? 'anthropic/claude-sonnet-4-5';
|
|
54
|
+
const [provider] = modelId.split('/');
|
|
55
|
+
const { pkg, fn } = providerImport(provider);
|
|
56
|
+
const envKey = `${provider.toUpperCase()}_API_KEY`;
|
|
57
|
+
const lines = [];
|
|
58
|
+
lines.push("import { Agent } from '@mastra/core/agent';");
|
|
59
|
+
lines.push("import { MCPClient } from '@mastra/mcp';");
|
|
60
|
+
lines.push("import { createMastraHelpers } from '@powerhousedao/ph-clint/mastra';");
|
|
61
|
+
lines.push("import { createWorkdirStore } from '@powerhousedao/ph-clint';");
|
|
62
|
+
lines.push("import type { AgentSetupContext, AgentProvider } from '@powerhousedao/ph-clint';");
|
|
63
|
+
lines.push("import type { WrapAgentOptions } from '@powerhousedao/ph-clint/mastra';");
|
|
64
|
+
lines.push("import { CLI_NAME } from '../config.js';");
|
|
65
|
+
lines.push("import type { Config } from '../framework.js';");
|
|
66
|
+
lines.push("import { createDemoAgent } from './demo-agent.js';");
|
|
67
|
+
lines.push('');
|
|
68
|
+
lines.push('/**');
|
|
69
|
+
lines.push(' * Agent factory for the CLI.');
|
|
70
|
+
lines.push(' *');
|
|
71
|
+
lines.push(' * Returns a demo agent when no API key is configured, or wraps the full');
|
|
72
|
+
lines.push(' * agent as a ph-clint AgentProvider.');
|
|
73
|
+
lines.push(' */');
|
|
74
|
+
lines.push('export async function createAgent(ctx: AgentSetupContext<Config>): Promise<AgentProvider> {');
|
|
75
|
+
lines.push(` if (!ctx.config.apiKey) return createDemoAgent();`);
|
|
76
|
+
lines.push('');
|
|
77
|
+
lines.push(' const m = createMastraHelpers(ctx);');
|
|
78
|
+
lines.push('');
|
|
79
|
+
lines.push(' const agent = new Agent({');
|
|
80
|
+
lines.push(` id: '${agentId}',`);
|
|
81
|
+
lines.push(` name: '${agentName}',`);
|
|
82
|
+
lines.push(` instructions: m.getAgentInstructions('${agentId}'),`);
|
|
83
|
+
lines.push(' model: ctx.config.apiKey');
|
|
84
|
+
lines.push(" ? { id: ctx.config.model as `${string}/${string}`, apiKey: ctx.config.apiKey }");
|
|
85
|
+
lines.push(" : (ctx.config.model as `${string}/${string}`),");
|
|
86
|
+
lines.push(' tools: async () => {');
|
|
87
|
+
lines.push(" ctx.context.log?.debug('[agent] tools callback invoked');");
|
|
88
|
+
lines.push(' const tools = await m.getTools({ MCPClient });');
|
|
89
|
+
lines.push(" ctx.context.log?.debug(`[agent] tools resolved: ${Object.keys(tools).length} tools`);");
|
|
90
|
+
lines.push(' return tools;');
|
|
91
|
+
lines.push(' },');
|
|
92
|
+
lines.push(' workspace: await m.createWorkspace(),');
|
|
93
|
+
lines.push(' memory: await m.createMemory(),');
|
|
94
|
+
lines.push(' });');
|
|
95
|
+
lines.push('');
|
|
96
|
+
lines.push(' const store = createWorkdirStore(ctx.workdir, CLI_NAME);');
|
|
97
|
+
lines.push(' const wrapOpts: WrapAgentOptions = {');
|
|
98
|
+
lines.push(' maxSteps: 80,');
|
|
99
|
+
lines.push(' enableLogging: ctx.config.agentLogging,');
|
|
100
|
+
lines.push(" logDirectory: store.getStoreFolder('logs'),");
|
|
101
|
+
lines.push(' cacheControl: true,');
|
|
102
|
+
lines.push(' };');
|
|
103
|
+
lines.push(' return m.wrapAgent(agent, wrapOpts);');
|
|
104
|
+
lines.push('}');
|
|
105
|
+
lines.push('');
|
|
106
|
+
return lines.join('\n');
|
|
107
|
+
}
|
|
108
|
+
export function buildAgentTs(spec) {
|
|
109
|
+
const mastra = spec.features.mastra;
|
|
110
|
+
// Generate real agent when we have full config, demo otherwise
|
|
111
|
+
if (mastra.agentId && mastra.models.length > 0) {
|
|
112
|
+
return buildRealAgent(spec);
|
|
113
|
+
}
|
|
114
|
+
return buildDemoAgent(spec);
|
|
115
|
+
}
|
|
@@ -63,7 +63,20 @@ export function buildCliTs(spec) {
|
|
|
63
63
|
lines.push(' // @clint:begin prompts');
|
|
64
64
|
lines.push(' prompts: {');
|
|
65
65
|
lines.push(' artifacts: [],');
|
|
66
|
-
|
|
66
|
+
if (mastra.enabled && mastra.agentId && mastra.profiles.length > 0) {
|
|
67
|
+
lines.push(' agents: {');
|
|
68
|
+
lines.push(` '${mastra.agentId}': {`);
|
|
69
|
+
lines.push(' sections: [');
|
|
70
|
+
for (const p of mastra.profiles) {
|
|
71
|
+
lines.push(` { id: '${p.id}', title: '${p.title}', file: '${p.id}.md' },`);
|
|
72
|
+
}
|
|
73
|
+
lines.push(' ],');
|
|
74
|
+
lines.push(' },');
|
|
75
|
+
lines.push(' },');
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
lines.push(' agents: {},');
|
|
79
|
+
}
|
|
67
80
|
lines.push(' skills: {},');
|
|
68
81
|
lines.push(' },');
|
|
69
82
|
lines.push(' // @clint:end prompts');
|
|
@@ -43,7 +43,8 @@ export function buildFrameworkTs(spec) {
|
|
|
43
43
|
lines.push('');
|
|
44
44
|
lines.push('export const configSchema = z.object({');
|
|
45
45
|
if (mastra.enabled) {
|
|
46
|
-
|
|
46
|
+
const defaultModel = mastra.models.find(m => m.isDefault)?.id ?? 'anthropic/claude-sonnet-4-5';
|
|
47
|
+
lines.push(` model: z.string().default('${defaultModel}').describe('LLM model to use'),`);
|
|
47
48
|
}
|
|
48
49
|
// Ports are derived from CLI name by resolveReactorDefaults() — no config fields needed.
|
|
49
50
|
// Override via switchboard.port / connect.port in configureReactor() if required.
|
|
@@ -36,6 +36,11 @@ export interface FileBuilder {
|
|
|
36
36
|
*/
|
|
37
37
|
initOnly?: boolean;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Generate dynamic file builders for agent profile markdown files.
|
|
41
|
+
* One file per profile in `prompts/agent-profiles/{id}.md`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function getProfileFileBuilders(spec: ClintProjectSpec): FileBuilder[];
|
|
39
44
|
export declare const CLI_FILE_BUILDERS: FileBuilder[];
|
|
40
45
|
export { buildCliPackageJson, buildCliTs, buildConfigTs, buildMainTs, buildTsconfigJson, buildJestConfigJs, buildEslintConfigJs, buildGitignore, buildMastraIndexTs, buildBuildSkillsScript, buildAgentBaseMd, buildAgentTs, buildFrameworkGenTs, buildFrameworkTs, };
|
|
41
46
|
export { buildRootPackageJson } from './root-package-json.js';
|
|
@@ -19,6 +19,19 @@ import { buildAgentBaseMd } from './agent-base-md.js';
|
|
|
19
19
|
import { buildAgentTs } from './agent-ts.js';
|
|
20
20
|
import { buildFrameworkGenTs } from './framework-gen-ts.js';
|
|
21
21
|
import { buildFrameworkTs } from './framework-ts.js';
|
|
22
|
+
/**
|
|
23
|
+
* Generate dynamic file builders for agent profile markdown files.
|
|
24
|
+
* One file per profile in `prompts/agent-profiles/{id}.md`.
|
|
25
|
+
*/
|
|
26
|
+
export function getProfileFileBuilders(spec) {
|
|
27
|
+
const { mastra } = spec.features;
|
|
28
|
+
if (!mastra.enabled || mastra.profiles.length === 0)
|
|
29
|
+
return [];
|
|
30
|
+
return mastra.profiles.map((profile) => ({
|
|
31
|
+
relativePath: `prompts/agent-profiles/${profile.id}.md`,
|
|
32
|
+
build: () => profile.content + '\n',
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
22
35
|
export const CLI_FILE_BUILDERS = [
|
|
23
36
|
{ relativePath: 'package.json', build: buildCliPackageJson },
|
|
24
37
|
{ relativePath: 'tsconfig.json', build: () => buildTsconfigJson() },
|
|
@@ -34,7 +47,10 @@ export const CLI_FILE_BUILDERS = [
|
|
|
34
47
|
{ relativePath: 'src/framework.gen.ts', build: buildFrameworkGenTs },
|
|
35
48
|
{ relativePath: 'src/mastra/index.ts', build: buildMastraIndexTs },
|
|
36
49
|
{ relativePath: 'scripts/build-skills.ts', build: () => buildBuildSkillsScript() },
|
|
37
|
-
{
|
|
50
|
+
{
|
|
51
|
+
relativePath: 'prompts/agent-profiles/AgentBase.md',
|
|
52
|
+
build: (spec) => (spec.features.mastra.profiles.length > 0 ? null : buildAgentBaseMd(spec)),
|
|
53
|
+
},
|
|
38
54
|
{
|
|
39
55
|
relativePath: 'src/agents/agent.ts',
|
|
40
56
|
build: (spec) => (spec.features.mastra.enabled ? buildAgentTs(spec) : null),
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* When Mastra is off, emit a no-op placeholder so the directory still exists
|
|
5
5
|
* and `mastra dev` fails predictably rather than importing partial code.
|
|
6
|
+
*
|
|
7
|
+
* When Mastra is on with full agent config, emit a real Mastra instance
|
|
8
|
+
* that registers the agent.
|
|
6
9
|
*/
|
|
7
10
|
import { type ClintProjectSpec } from '../../spec/types.js';
|
|
8
11
|
export declare function buildMastraIndexTs(spec: ClintProjectSpec): string;
|
|
@@ -12,9 +12,29 @@ export function buildMastraIndexTs(spec) {
|
|
|
12
12
|
'',
|
|
13
13
|
].join('\n');
|
|
14
14
|
}
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
// Full agent config available — emit real Mastra instance
|
|
16
|
+
if (mastra.agentId && mastra.models.length > 0) {
|
|
17
|
+
const agentId = mastra.agentId;
|
|
18
|
+
return [
|
|
19
|
+
'/**',
|
|
20
|
+
' * Mastra Studio entry point for `mastra dev` / `mastra build` / `mastra start`.',
|
|
21
|
+
' *',
|
|
22
|
+
' * Exports a configured Mastra instance with the project agent registered.',
|
|
23
|
+
' */',
|
|
24
|
+
"import { Mastra } from '@mastra/core/mastra';",
|
|
25
|
+
"import { Agent } from '@mastra/core/agent';",
|
|
26
|
+
'',
|
|
27
|
+
'// @clint:begin mastra-index',
|
|
28
|
+
`const agent = new Agent({ id: '${agentId}', model: '${(mastra.models.find(m => m.isDefault) ?? mastra.models[0]).id}' });`,
|
|
29
|
+
'',
|
|
30
|
+
'export const mastra = new Mastra({',
|
|
31
|
+
` agents: { '${agentId}': agent },`,
|
|
32
|
+
'});',
|
|
33
|
+
'// @clint:end mastra-index',
|
|
34
|
+
'',
|
|
35
|
+
].join('\n');
|
|
36
|
+
}
|
|
37
|
+
// Mastra enabled but no full agent config yet — placeholder
|
|
18
38
|
return [
|
|
19
39
|
'/**',
|
|
20
40
|
' * Mastra Studio entry point for `mastra dev` / `mastra build` / `mastra start`.',
|
package/dist/codegen/index.js
CHANGED
|
@@ -35,7 +35,7 @@ import { hashContent, hashFile, readHashes, writeHashes, } from './hashes.js';
|
|
|
35
35
|
import { hasMarkers, spliceMarkerRegions } from './markers.js';
|
|
36
36
|
import { writeFileEnsuringDir, isDirEmptyEnough } from './write.js';
|
|
37
37
|
import { migrateFlatToSplit } from './migrate/flat-to-split.js';
|
|
38
|
-
import { CLI_FILE_BUILDERS, buildReadme, buildRootPackageJson, buildPublishConfigTs, buildAppIndexTs, } from './builders/index.js';
|
|
38
|
+
import { CLI_FILE_BUILDERS, getProfileFileBuilders, buildReadme, buildRootPackageJson, buildPublishConfigTs, buildAppIndexTs, } from './builders/index.js';
|
|
39
39
|
/**
|
|
40
40
|
* Generate / reconcile a project at `targetDir` from the given spec.
|
|
41
41
|
*/
|
|
@@ -79,7 +79,8 @@ function planFiles(spec, targetDir) {
|
|
|
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 = [];
|
|
82
|
-
|
|
82
|
+
const allBuilders = [...CLI_FILE_BUILDERS, ...getProfileFileBuilders(spec)];
|
|
83
|
+
for (const builder of allBuilders) {
|
|
83
84
|
const content = builder.build(spec);
|
|
84
85
|
if (content === null)
|
|
85
86
|
continue;
|
|
@@ -38,6 +38,17 @@ export declare const init: import("@powerhousedao/ph-clint").Command<z.ZodObject
|
|
|
38
38
|
powerhouse: "Disabled" | "Reactor" | "Switchboard" | "Connect";
|
|
39
39
|
mastra: {
|
|
40
40
|
enabled: boolean;
|
|
41
|
+
agentId: string | null;
|
|
42
|
+
agentName: string | null;
|
|
43
|
+
models: {
|
|
44
|
+
id: string;
|
|
45
|
+
isDefault: boolean;
|
|
46
|
+
}[];
|
|
47
|
+
profiles: {
|
|
48
|
+
id: string;
|
|
49
|
+
title: string;
|
|
50
|
+
content: string;
|
|
51
|
+
}[];
|
|
41
52
|
};
|
|
42
53
|
routine: {
|
|
43
54
|
enabled: boolean;
|
|
@@ -55,6 +66,8 @@ export declare const init: import("@powerhousedao/ph-clint").Command<z.ZodObject
|
|
|
55
66
|
}[];
|
|
56
67
|
scope?: string | undefined;
|
|
57
68
|
bin?: string | undefined;
|
|
69
|
+
documentId?: string | undefined;
|
|
70
|
+
documentType?: string | undefined;
|
|
58
71
|
};
|
|
59
72
|
fileCount: number;
|
|
60
73
|
};
|
|
@@ -13,11 +13,14 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import { defineCommand } from '@powerhousedao/ph-clint';
|
|
16
|
+
import { importSpec } from '@powerhousedao/ph-clint-app/document-models/ph-clint-project';
|
|
16
17
|
import { z } from 'zod';
|
|
17
18
|
import { clintProjectSpecSchema, } from '../spec/types.js';
|
|
18
19
|
import { generateProject } from '../codegen/index.js';
|
|
19
20
|
import { isDirEmptyEnough } from '../codegen/write.js';
|
|
20
21
|
import { runPhInit, runPnpmInstall } from '../codegen/scaffold.js';
|
|
22
|
+
import { writeProjectSpec } from '../spec/file.js';
|
|
23
|
+
import { specToImportInput } from '../triggers/spec-change.js';
|
|
21
24
|
const inputSchema = z.object({
|
|
22
25
|
dir: z
|
|
23
26
|
.string()
|
|
@@ -70,13 +73,22 @@ export function buildSpec(input) {
|
|
|
70
73
|
// Routine is forced on when Mastra is on — the REPL-only path works, but
|
|
71
74
|
// the agent routine loop is what most users actually want.
|
|
72
75
|
const routine = input.enableRoutine || input.enableMastra;
|
|
76
|
+
const mastra = input.enableMastra
|
|
77
|
+
? {
|
|
78
|
+
enabled: true,
|
|
79
|
+
agentId: `${name}-agent`,
|
|
80
|
+
agentName: `${name.charAt(0).toUpperCase()}${name.slice(1).replace(/-(\w)/g, (_, c) => ' ' + c.toUpperCase())} Agent`,
|
|
81
|
+
models: [{ id: 'anthropic/claude-sonnet-4-5', isDefault: true }],
|
|
82
|
+
profiles: [{ id: 'base', title: 'Base', content: 'You are a helpful assistant.' }],
|
|
83
|
+
}
|
|
84
|
+
: { enabled: false };
|
|
73
85
|
const candidate = {
|
|
74
86
|
name,
|
|
75
87
|
scope,
|
|
76
88
|
description: input.description ?? '',
|
|
77
89
|
features: {
|
|
78
90
|
powerhouse: input.enablePowerhouse ? 'Connect' : 'Disabled',
|
|
79
|
-
mastra
|
|
91
|
+
mastra,
|
|
80
92
|
routine: { enabled: routine },
|
|
81
93
|
},
|
|
82
94
|
};
|
|
@@ -90,7 +102,7 @@ export const init = defineCommand({
|
|
|
90
102
|
promptForDefaults: true,
|
|
91
103
|
promptOptional: ['description'],
|
|
92
104
|
},
|
|
93
|
-
execute: async (input, { workdir, stdout }) => {
|
|
105
|
+
execute: async (input, { workdir, stdout, reactor: getReactor, folders }) => {
|
|
94
106
|
const { name: bareName } = splitPackageName(input.name);
|
|
95
107
|
const targetDir = path.resolve(workdir, input.dir ?? bareName);
|
|
96
108
|
if (!input.force) {
|
|
@@ -110,6 +122,27 @@ export const init = defineCommand({
|
|
|
110
122
|
allowNonEmpty: input.force,
|
|
111
123
|
});
|
|
112
124
|
stdout(`Generated ${result.files.length} files in ${targetDir}\n`);
|
|
125
|
+
// Create spec document in personal drive (if reactor available)
|
|
126
|
+
if (getReactor && folders) {
|
|
127
|
+
try {
|
|
128
|
+
const reactor = await getReactor();
|
|
129
|
+
if (reactor) {
|
|
130
|
+
const newDoc = await reactor.client.createEmpty('powerhouse/ph-clint-project');
|
|
131
|
+
const docId = newDoc.header.id;
|
|
132
|
+
await folders.addDocument(docId, `specs/${spec.name}`, spec.name);
|
|
133
|
+
const importInput = specToImportInput(spec);
|
|
134
|
+
await reactor.client.execute(docId, 'main', [importSpec(importInput)]);
|
|
135
|
+
spec.documentId = docId;
|
|
136
|
+
spec.documentType = 'powerhouse/ph-clint-project';
|
|
137
|
+
await writeProjectSpec(targetDir, spec);
|
|
138
|
+
stdout(`Created spec document ${docId} in Clint Folders/specs/${spec.name}\n`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
// Non-fatal — trigger will pick it up on next startup
|
|
143
|
+
stdout(`Note: could not create spec document — ${err instanceof Error ? err.message : String(err)}\n`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
113
146
|
const split = spec.features.powerhouse !== 'Disabled';
|
|
114
147
|
// Reactor package scaffolding (split layout only).
|
|
115
148
|
if (split && result.appDir && !input.skipPhInit) {
|
package/dist/framework.d.ts
CHANGED
|
@@ -14,7 +14,6 @@ export declare const configSchema: z.ZodObject<{
|
|
|
14
14
|
model: z.ZodDefault<z.ZodString>;
|
|
15
15
|
devServicePort: z.ZodDefault<z.ZodNumber>;
|
|
16
16
|
phVersion: z.ZodDefault<z.ZodString>;
|
|
17
|
-
projectDocumentId: z.ZodOptional<z.ZodString>;
|
|
18
17
|
}, z.core.$strip>;
|
|
19
18
|
export declare const secretsSchema: z.ZodObject<{
|
|
20
19
|
apiKey: z.ZodOptional<z.ZodString>;
|
|
@@ -30,38 +29,32 @@ export declare const defineCommand: <TInput extends z.ZodType, TOutput = unknown
|
|
|
30
29
|
model: string;
|
|
31
30
|
devServicePort: number;
|
|
32
31
|
phVersion: string;
|
|
33
|
-
projectDocumentId?: string | undefined;
|
|
34
32
|
apiKey?: string | undefined;
|
|
35
33
|
}, import("@powerhousedao/ph-clint").InferRegistry<readonly [import("document-model").DocumentModelModule<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>]>>) => Promise<TOutput>;
|
|
36
34
|
}) => import("@powerhousedao/ph-clint").Command<TInput, TOutput, {
|
|
37
35
|
model: string;
|
|
38
36
|
devServicePort: number;
|
|
39
37
|
phVersion: string;
|
|
40
|
-
projectDocumentId?: string | undefined;
|
|
41
38
|
apiKey?: string | undefined;
|
|
42
39
|
}>, defineTrigger: <TState = Record<string, unknown>>(options: import("@powerhousedao/ph-clint").TriggerOptions<TState, {
|
|
43
40
|
model: string;
|
|
44
41
|
devServicePort: number;
|
|
45
42
|
phVersion: string;
|
|
46
|
-
projectDocumentId?: string | undefined;
|
|
47
43
|
apiKey?: string | undefined;
|
|
48
44
|
}, import("@powerhousedao/ph-clint").InferRegistry<readonly [import("document-model").DocumentModelModule<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>]>>) => import("@powerhousedao/ph-clint").Trigger<TState, {
|
|
49
45
|
model: string;
|
|
50
46
|
devServicePort: number;
|
|
51
47
|
phVersion: string;
|
|
52
|
-
projectDocumentId?: string | undefined;
|
|
53
48
|
apiKey?: string | undefined;
|
|
54
49
|
}, import("@powerhousedao/ph-clint").InferRegistry<readonly [import("document-model").DocumentModelModule<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>]>>, defineService: (options: import("@powerhousedao/ph-clint").ServiceDefinition<{
|
|
55
50
|
model: string;
|
|
56
51
|
devServicePort: number;
|
|
57
52
|
phVersion: string;
|
|
58
|
-
projectDocumentId?: string | undefined;
|
|
59
53
|
apiKey?: string | undefined;
|
|
60
54
|
}>) => import("@powerhousedao/ph-clint").ServiceDefinition<{
|
|
61
55
|
model: string;
|
|
62
56
|
devServicePort: number;
|
|
63
57
|
phVersion: string;
|
|
64
|
-
projectDocumentId?: string | undefined;
|
|
65
58
|
apiKey?: string | undefined;
|
|
66
59
|
}>, createDocumentChangeTrigger: <T extends string, TState extends {
|
|
67
60
|
pending: number;
|
|
@@ -71,13 +64,11 @@ export declare const defineCommand: <TInput extends z.ZodType, TOutput = unknown
|
|
|
71
64
|
model: string;
|
|
72
65
|
devServicePort: number;
|
|
73
66
|
phVersion: string;
|
|
74
|
-
projectDocumentId?: string | undefined;
|
|
75
67
|
apiKey?: string | undefined;
|
|
76
68
|
}, TState>) => import("@powerhousedao/ph-clint").Trigger<TState, {
|
|
77
69
|
model: string;
|
|
78
70
|
devServicePort: number;
|
|
79
71
|
phVersion: string;
|
|
80
|
-
projectDocumentId?: string | undefined;
|
|
81
72
|
apiKey?: string | undefined;
|
|
82
73
|
}, import("@powerhousedao/ph-clint").InferRegistry<readonly [import("document-model").DocumentModelModule<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>]>>;
|
|
83
74
|
export { registry } from './framework.gen.js';
|
package/dist/framework.js
CHANGED
|
@@ -25,10 +25,6 @@ export const configSchema = z.object({
|
|
|
25
25
|
.string()
|
|
26
26
|
.default('6.0.0-dev.170')
|
|
27
27
|
.describe('Pinned Powerhouse version used when emitting a new impl project'),
|
|
28
|
-
projectDocumentId: z
|
|
29
|
-
.string()
|
|
30
|
-
.optional()
|
|
31
|
-
.describe('ID of the powerhouse/ph-clint-project document that drives codegen (set on first regen; edit to switch projects)'),
|
|
32
28
|
});
|
|
33
29
|
export const secretsSchema = z.object({
|
|
34
30
|
apiKey: z.string().optional().describe('Anthropic API key'),
|
|
@@ -31,6 +31,11 @@ export const clintProject = defineService({
|
|
|
31
31
|
],
|
|
32
32
|
projectScanner: {
|
|
33
33
|
isProjectFolder: (p) => {
|
|
34
|
+
// Match if a project-spec.json exists (split-layout root)
|
|
35
|
+
const specPath = path.join(p, '.ph', 'ph-clint-cli', 'project-spec.json');
|
|
36
|
+
if (fs.existsSync(specPath))
|
|
37
|
+
return true;
|
|
38
|
+
// Match if package.json has ph-clint as a dependency (flat layout or cli subfolder)
|
|
34
39
|
const pkg = path.join(p, 'package.json');
|
|
35
40
|
if (!fs.existsSync(pkg))
|
|
36
41
|
return false;
|
|
@@ -42,6 +47,22 @@ export const clintProject = defineService({
|
|
|
42
47
|
return false;
|
|
43
48
|
}
|
|
44
49
|
},
|
|
50
|
+
getDocumentLink: (p) => {
|
|
51
|
+
const specPath = path.join(p, '.ph', 'ph-clint-cli', 'project-spec.json');
|
|
52
|
+
if (!fs.existsSync(specPath))
|
|
53
|
+
return undefined;
|
|
54
|
+
try {
|
|
55
|
+
const spec = JSON.parse(fs.readFileSync(specPath, 'utf8'));
|
|
56
|
+
if (spec.documentId) {
|
|
57
|
+
return {
|
|
58
|
+
documentId: spec.documentId,
|
|
59
|
+
documentType: spec.documentType ?? 'powerhouse/ph-clint-project',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch { /* noop */ }
|
|
64
|
+
return undefined;
|
|
65
|
+
},
|
|
45
66
|
},
|
|
46
67
|
restart: { enabled: false, maxRetries: 0, delay: 0 },
|
|
47
68
|
});
|
|
@@ -15,4 +15,7 @@ import { type ClintProjectSpec } from './types.js';
|
|
|
15
15
|
* Returns `null` if the document has no valid package name yet — callers
|
|
16
16
|
* should wait for the user to populate it before triggering codegen.
|
|
17
17
|
*/
|
|
18
|
-
export declare function specFromDocumentState(state: PhClintProjectGlobalState
|
|
18
|
+
export declare function specFromDocumentState(state: PhClintProjectGlobalState, meta?: {
|
|
19
|
+
documentId?: string;
|
|
20
|
+
documentType?: string;
|
|
21
|
+
}): ClintProjectSpec | null;
|
|
@@ -5,7 +5,7 @@ import { clintProjectSpecSchema, } from './types.js';
|
|
|
5
5
|
* Returns `null` if the document has no valid package name yet — callers
|
|
6
6
|
* should wait for the user to populate it before triggering codegen.
|
|
7
7
|
*/
|
|
8
|
-
export function specFromDocumentState(state) {
|
|
8
|
+
export function specFromDocumentState(state, meta) {
|
|
9
9
|
if (!state.name)
|
|
10
10
|
return null;
|
|
11
11
|
const parsed = clintProjectSpecSchema.safeParse({
|
|
@@ -29,6 +29,8 @@ export function specFromDocumentState(state) {
|
|
|
29
29
|
name: s.name,
|
|
30
30
|
githubUrl: s.githubUrl,
|
|
31
31
|
})),
|
|
32
|
+
documentId: meta?.documentId,
|
|
33
|
+
documentType: meta?.documentType,
|
|
32
34
|
});
|
|
33
35
|
return parsed.success ? parsed.data : null;
|
|
34
36
|
}
|
package/dist/spec/types.d.ts
CHANGED
|
@@ -23,6 +23,17 @@ export declare const powerhouseLevelSchema: z.ZodDefault<z.ZodEnum<{
|
|
|
23
23
|
export type PowerhouseLevel = z.infer<typeof powerhouseLevelSchema>;
|
|
24
24
|
/** Check whether `level` is at least `threshold` in the ordered enum. */
|
|
25
25
|
export declare function phAtLeast(level: PowerhouseLevel, threshold: PowerhouseLevel): boolean;
|
|
26
|
+
export declare const agentModelSchema: z.ZodObject<{
|
|
27
|
+
id: z.ZodString;
|
|
28
|
+
isDefault: z.ZodBoolean;
|
|
29
|
+
}, z.core.$strip>;
|
|
30
|
+
export type AgentModel = z.infer<typeof agentModelSchema>;
|
|
31
|
+
export declare const agentProfileSchema: z.ZodObject<{
|
|
32
|
+
id: z.ZodString;
|
|
33
|
+
title: z.ZodString;
|
|
34
|
+
content: z.ZodString;
|
|
35
|
+
}, z.core.$strip>;
|
|
36
|
+
export type AgentProfile = z.infer<typeof agentProfileSchema>;
|
|
26
37
|
export declare const powerhousePackageSchema: z.ZodObject<{
|
|
27
38
|
id: z.ZodString;
|
|
28
39
|
packageName: z.ZodString;
|
|
@@ -50,6 +61,17 @@ export declare const clintProjectSpecSchema: z.ZodObject<{
|
|
|
50
61
|
}>>;
|
|
51
62
|
mastra: z.ZodDefault<z.ZodObject<{
|
|
52
63
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
64
|
+
agentId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
65
|
+
agentName: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
66
|
+
models: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
67
|
+
id: z.ZodString;
|
|
68
|
+
isDefault: z.ZodBoolean;
|
|
69
|
+
}, z.core.$strip>>>;
|
|
70
|
+
profiles: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
71
|
+
id: z.ZodString;
|
|
72
|
+
title: z.ZodString;
|
|
73
|
+
content: z.ZodString;
|
|
74
|
+
}, z.core.$strip>>>;
|
|
53
75
|
}, z.core.$strip>>;
|
|
54
76
|
routine: z.ZodDefault<z.ZodObject<{
|
|
55
77
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -65,6 +87,8 @@ export declare const clintProjectSpecSchema: z.ZodObject<{
|
|
|
65
87
|
name: z.ZodString;
|
|
66
88
|
githubUrl: z.ZodString;
|
|
67
89
|
}, z.core.$strip>>>;
|
|
90
|
+
documentId: z.ZodOptional<z.ZodString>;
|
|
91
|
+
documentType: z.ZodOptional<z.ZodString>;
|
|
68
92
|
}, z.core.$strip>;
|
|
69
93
|
export type ClintProjectSpec = z.infer<typeof clintProjectSpecSchema>;
|
|
70
94
|
export type ClintProjectSpecInput = z.input<typeof clintProjectSpecSchema>;
|
package/dist/spec/types.js
CHANGED
|
@@ -26,13 +26,32 @@ export const powerhouseLevelSchema = z
|
|
|
26
26
|
export function phAtLeast(level, threshold) {
|
|
27
27
|
return (POWERHOUSE_LEVELS.indexOf(level) >= POWERHOUSE_LEVELS.indexOf(threshold));
|
|
28
28
|
}
|
|
29
|
+
export const agentModelSchema = z.object({
|
|
30
|
+
id: z.string(),
|
|
31
|
+
isDefault: z.boolean(),
|
|
32
|
+
});
|
|
33
|
+
export const agentProfileSchema = z.object({
|
|
34
|
+
id: z.string(),
|
|
35
|
+
title: z.string(),
|
|
36
|
+
content: z.string(),
|
|
37
|
+
});
|
|
29
38
|
const mastraFeatureSchema = z.object({
|
|
30
39
|
enabled: z.boolean().default(false),
|
|
40
|
+
agentId: z.string().nullable().default(null),
|
|
41
|
+
agentName: z.string().nullable().default(null),
|
|
42
|
+
models: z.array(agentModelSchema).default([]),
|
|
43
|
+
profiles: z.array(agentProfileSchema).default([]),
|
|
31
44
|
});
|
|
32
45
|
const routineFeatureSchema = z.object({
|
|
33
46
|
enabled: z.boolean().default(false),
|
|
34
47
|
});
|
|
35
|
-
const DEFAULT_MASTRA = {
|
|
48
|
+
const DEFAULT_MASTRA = {
|
|
49
|
+
enabled: false,
|
|
50
|
+
agentId: null,
|
|
51
|
+
agentName: null,
|
|
52
|
+
models: [],
|
|
53
|
+
profiles: [],
|
|
54
|
+
};
|
|
36
55
|
const DEFAULT_ROUTINE = { enabled: false };
|
|
37
56
|
export const powerhousePackageSchema = z.object({
|
|
38
57
|
id: z.string(),
|
|
@@ -79,6 +98,10 @@ export const clintProjectSpecSchema = z.object({
|
|
|
79
98
|
* (kebab-case) and a GitHub URL for installation.
|
|
80
99
|
*/
|
|
81
100
|
externalSkills: z.array(externalSkillSchema).default([]),
|
|
101
|
+
/** ID of the source specification document (if backed by a Powerhouse document). */
|
|
102
|
+
documentId: z.string().optional(),
|
|
103
|
+
/** Document type of the source specification document. */
|
|
104
|
+
documentType: z.string().optional(),
|
|
82
105
|
});
|
|
83
106
|
/** npm package name (e.g. `@scope/foo` or `foo`). */
|
|
84
107
|
export function getPackageName(spec) {
|
|
@@ -6,6 +6,5 @@ export declare const publishTrigger: import("@powerhousedao/ph-clint").Trigger<{
|
|
|
6
6
|
model: string;
|
|
7
7
|
devServicePort: number;
|
|
8
8
|
phVersion: string;
|
|
9
|
-
projectDocumentId?: string | undefined;
|
|
10
9
|
apiKey?: string | undefined;
|
|
11
10
|
}, import("@powerhousedao/ph-clint").InferRegistry<readonly [import("document-model").DocumentModelModule<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>]>>;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `publish-trigger` — watches `powerhouse/ph-clint-project` documents for
|
|
2
|
+
* `publish-trigger` — watches ALL `powerhouse/ph-clint-project` documents for
|
|
3
3
|
* publish records with `Pending` status and invokes the publish pipeline.
|
|
4
4
|
*
|
|
5
5
|
* Unlike `spec-change`, this trigger does NOT convert to a `ClintProjectSpec`.
|
|
6
6
|
* It reads `publishHistory` directly from the document state, since publish
|
|
7
7
|
* records are a doc-model concern (not a codegen input).
|
|
8
8
|
*
|
|
9
|
+
* Uses the multi-doc path (no `documentId`), scanning all matching documents
|
|
10
|
+
* on each poll. For each document with a pending publish record, it uses the
|
|
11
|
+
* project mapping to resolve the on-disk project directory.
|
|
12
|
+
*
|
|
9
13
|
* Flow:
|
|
10
14
|
* 1. On document change, scans `publishHistory` for records with status
|
|
11
15
|
* `"Pending"`.
|
|
@@ -15,26 +19,14 @@
|
|
|
15
19
|
* c. Dispatches SET_PUBLISH_STATUS({ id, status: "Succeeded"|"Failed" }).
|
|
16
20
|
*/
|
|
17
21
|
import { spawn } from 'node:child_process';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import { scanProjects, getProjectMapping } from '@powerhousedao/ph-clint';
|
|
18
24
|
import { setPublishStatus } from '@powerhousedao/ph-clint-app';
|
|
19
25
|
import { createDocumentChangeTrigger } from '../framework.js';
|
|
26
|
+
import { specFromDocumentState } from '../spec/from-document.js';
|
|
27
|
+
import { clintProject } from '../services/clint-project.js';
|
|
20
28
|
const DOCUMENT_TYPE = 'powerhouse/ph-clint-project';
|
|
21
29
|
const TAG = '[publish]';
|
|
22
|
-
/** Find the first ph-clint-project document on the default drive. */
|
|
23
|
-
async function findProjectDocumentId(client, driveId) {
|
|
24
|
-
try {
|
|
25
|
-
const children = await client.getChildren(driveId);
|
|
26
|
-
const results = (children?.results ?? []);
|
|
27
|
-
for (const node of results) {
|
|
28
|
-
if (node.header?.documentType === DOCUMENT_TYPE && node.header.id) {
|
|
29
|
-
return node.header.id;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
// fall through
|
|
35
|
-
}
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
38
30
|
/** Run a shell command and return stdout. Rejects on non-zero exit. */
|
|
39
31
|
function exec(cmd, args, cwd, onLog) {
|
|
40
32
|
return new Promise((resolve, reject) => {
|
|
@@ -82,91 +74,96 @@ export function tagToCli(tag) {
|
|
|
82
74
|
export const publishTrigger = createDocumentChangeTrigger({
|
|
83
75
|
id: 'publish-trigger',
|
|
84
76
|
documentType: DOCUMENT_TYPE,
|
|
77
|
+
// No documentId → watches ALL ph-clint-project docs
|
|
85
78
|
// Don't fire on startup — only react to new pending records.
|
|
86
79
|
initialReconcile: false,
|
|
87
|
-
|
|
88
|
-
documentId: async (ctx) => {
|
|
89
|
-
const configured = ctx.context.config.projectDocumentId &&
|
|
90
|
-
ctx.context.config.projectDocumentId.length > 0
|
|
91
|
-
? ctx.context.config.projectDocumentId
|
|
92
|
-
: undefined;
|
|
93
|
-
if (configured)
|
|
94
|
-
return configured;
|
|
95
|
-
const reactor = await ctx.reactor();
|
|
96
|
-
if (!reactor)
|
|
97
|
-
return undefined;
|
|
98
|
-
return findProjectDocumentId(reactor.client, reactor.driveId);
|
|
99
|
-
},
|
|
100
|
-
async onChange(doc, ctx) {
|
|
80
|
+
async onChange(docs, ctx) {
|
|
101
81
|
const log = ctx.context.log;
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
log?.info(`${TAG}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
log
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
82
|
+
const folders = ctx.context.folders;
|
|
83
|
+
// Build the mapping to resolve on-disk project directories
|
|
84
|
+
const scanner = clintProject.projectScanner;
|
|
85
|
+
const scanResults = scanner
|
|
86
|
+
? scanProjects(ctx.context.workdir, scanner)
|
|
87
|
+
: [];
|
|
88
|
+
const mapping = await getProjectMapping(scanResults, folders);
|
|
89
|
+
for (const doc of docs) {
|
|
90
|
+
const publishHistory = doc.state.global.publishHistory ?? [];
|
|
91
|
+
const pending = publishHistory.filter((r) => r.status === 'Pending');
|
|
92
|
+
if (pending.length === 0)
|
|
93
|
+
continue;
|
|
94
|
+
// Resolve the on-disk target directory for this document
|
|
95
|
+
const spec = specFromDocumentState(doc.state.global, {
|
|
96
|
+
documentId: doc.header.id,
|
|
97
|
+
documentType: doc.header.documentType,
|
|
98
|
+
});
|
|
99
|
+
const entry = mapping.find((m) => m.documentId === doc.header.id);
|
|
100
|
+
const workdir = entry?.path
|
|
101
|
+
?? (spec ? path.join(ctx.context.workdir, spec.name) : ctx.context.workdir);
|
|
102
|
+
// Process the first pending record (one per tick to avoid races).
|
|
103
|
+
const record = pending[0];
|
|
104
|
+
const cliTag = tagToCli(record.tag);
|
|
105
|
+
if (!cliTag) {
|
|
106
|
+
log?.warn(`${TAG} unknown publish tag "${record.tag}" for record ${record.id}`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
log?.info(`${TAG} found pending ${record.tag} publish for doc ${doc.header.id} (id=${record.id}, version=${record.version})`);
|
|
110
|
+
return {
|
|
111
|
+
type: 'function',
|
|
112
|
+
params: {
|
|
113
|
+
fn: async () => {
|
|
114
|
+
const reactor = await ctx.reactor();
|
|
115
|
+
if (!reactor) {
|
|
116
|
+
log?.error(`${TAG} reactor not available, cannot update publish status`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// Mark as InProgress.
|
|
120
|
+
log?.info(`${TAG} marking record ${record.id} as InProgress`);
|
|
121
|
+
await reactor.client.execute(doc.header.id, 'main', [
|
|
122
|
+
setPublishStatus({ id: record.id, status: 'InProgress' }),
|
|
123
|
+
]);
|
|
124
|
+
// 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)}`);
|
|
131
|
+
}
|
|
132
|
+
// Run the publish command.
|
|
133
|
+
let success = false;
|
|
134
|
+
try {
|
|
135
|
+
log?.info(`${TAG} invoking clint-project-publish --tag ${cliTag}`);
|
|
136
|
+
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
|
+
});
|
|
144
|
+
success = !result.text.includes('failed');
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
log?.error(`${TAG} publish failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
148
|
+
}
|
|
149
|
+
// Update status.
|
|
150
|
+
const finalStatus = success ? 'Succeeded' : 'Failed';
|
|
151
|
+
log?.info(`${TAG} marking record ${record.id} as ${finalStatus}`);
|
|
152
|
+
await reactor.client.execute(doc.header.id, 'main', [
|
|
153
|
+
setPublishStatus({
|
|
154
|
+
id: record.id,
|
|
155
|
+
status: finalStatus,
|
|
156
|
+
}),
|
|
157
|
+
]);
|
|
158
|
+
},
|
|
163
159
|
},
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
160
|
+
callbacks: {
|
|
161
|
+
onFailure: (err) => {
|
|
162
|
+
log?.error(`${TAG} publish trigger failed: ${err.message}`);
|
|
163
|
+
},
|
|
168
164
|
},
|
|
169
|
-
}
|
|
170
|
-
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
171
168
|
},
|
|
172
169
|
});
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import type { ImportSpecInput } from '@powerhousedao/ph-clint-app/document-models/ph-clint-project';
|
|
1
2
|
import type { ClintProjectSpec } from '../spec/types.js';
|
|
2
|
-
/** Stable hash of a ClintProjectSpec (sha256 of canonical-key JSON).
|
|
3
|
+
/** Stable hash of a ClintProjectSpec (sha256 of canonical-key JSON).
|
|
4
|
+
* Excludes documentId/documentType — they don't affect codegen output. */
|
|
3
5
|
export declare function hashSpec(spec: ClintProjectSpec): string;
|
|
6
|
+
export declare function specToImportInput(spec: ClintProjectSpec): ImportSpecInput;
|
|
4
7
|
export declare const specChangeTrigger: import("@powerhousedao/ph-clint").Trigger<{
|
|
5
8
|
pending: number;
|
|
6
9
|
}, {
|
|
7
10
|
model: string;
|
|
8
11
|
devServicePort: number;
|
|
9
12
|
phVersion: string;
|
|
10
|
-
projectDocumentId?: string | undefined;
|
|
11
13
|
apiKey?: string | undefined;
|
|
12
|
-
}, import("@powerhousedao/ph-clint").InferRegistry<readonly [import("document-model").DocumentModelModule<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>]>>;
|
|
14
|
+
}, import("@powerhousedao/ph-clint").InferRegistry<readonly [import("document-model").DocumentModelModule<import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").PhClintProjectPHState>]>>;
|
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `spec-change` trigger — watches
|
|
3
|
-
*
|
|
4
|
-
* that re-runs the code generator in update mode.
|
|
2
|
+
* `spec-change` trigger — watches ALL `powerhouse/ph-clint-project` documents
|
|
3
|
+
* and, when any change, re-runs the code generator for each affected project.
|
|
5
4
|
*
|
|
6
5
|
* Flow:
|
|
7
6
|
* 1. `createDocumentChangeTrigger` (from ph-clint) owns the subscription,
|
|
8
|
-
* event coalescing, doc loading, and type narrowing.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* 3.
|
|
7
|
+
* event coalescing, doc loading, and type narrowing. No `documentId` is
|
|
8
|
+
* set, so it loads ALL matching documents on each poll.
|
|
9
|
+
* 2. `getProjectMapping()` merges an on-disk scan (via the clint-project
|
|
10
|
+
* service's projectScanner) with personal-drive folder entries to
|
|
11
|
+
* correlate each document to an on-disk project directory.
|
|
12
|
+
* 3. For each document, `specFromDocumentState()` builds a spec, hashes it,
|
|
14
13
|
* and compares against `.ph/ph-clint-cli/.last-spec-hash`. If unchanged,
|
|
15
|
-
*
|
|
16
|
-
* `generateProject(spec, workdir, 'update')` and writes the new hash.
|
|
14
|
+
* skips. Otherwise regenerates at the mapped target directory.
|
|
17
15
|
*/
|
|
18
16
|
import crypto from 'node:crypto';
|
|
19
17
|
import fs from 'node:fs/promises';
|
|
20
18
|
import path from 'node:path';
|
|
19
|
+
import { scanProjects, getProjectMapping } from '@powerhousedao/ph-clint';
|
|
20
|
+
import { importSpec } from '@powerhousedao/ph-clint-app/document-models/ph-clint-project';
|
|
21
21
|
import { createDocumentChangeTrigger } from '../framework.js';
|
|
22
22
|
import { generateProject } from '../codegen/index.js';
|
|
23
23
|
import { runPhInit } from '../codegen/scaffold.js';
|
|
24
24
|
import { specFromDocumentState } from '../spec/from-document.js';
|
|
25
|
+
import { readProjectSpec, writeProjectSpec } from '../spec/file.js';
|
|
25
26
|
import { syncExternalSkills } from '../skills/sync.js';
|
|
27
|
+
import { clintProject } from '../services/clint-project.js';
|
|
26
28
|
const DOCUMENT_TYPE = 'powerhouse/ph-clint-project';
|
|
27
29
|
const HASH_DIR = path.join('.ph', 'ph-clint-cli');
|
|
28
30
|
const HASH_FILE = '.last-spec-hash';
|
|
@@ -51,11 +53,13 @@ async function writeLastSpecHash(targetDir, hash) {
|
|
|
51
53
|
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
52
54
|
await fs.writeFile(file, hash + '\n', 'utf8');
|
|
53
55
|
}
|
|
54
|
-
/** Stable hash of a ClintProjectSpec (sha256 of canonical-key JSON).
|
|
56
|
+
/** Stable hash of a ClintProjectSpec (sha256 of canonical-key JSON).
|
|
57
|
+
* Excludes documentId/documentType — they don't affect codegen output. */
|
|
55
58
|
export function hashSpec(spec) {
|
|
59
|
+
const { documentId, documentType, ...codegenRelevant } = spec;
|
|
56
60
|
return crypto
|
|
57
61
|
.createHash('sha256')
|
|
58
|
-
.update(canonicalJson(
|
|
62
|
+
.update(canonicalJson(codegenRelevant), 'utf8')
|
|
59
63
|
.digest('hex');
|
|
60
64
|
}
|
|
61
65
|
function canonicalJson(value) {
|
|
@@ -72,159 +76,172 @@ function canonicalJson(value) {
|
|
|
72
76
|
.join(',') +
|
|
73
77
|
'}');
|
|
74
78
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
79
|
+
export function specToImportInput(spec) {
|
|
80
|
+
return {
|
|
81
|
+
name: spec.name,
|
|
82
|
+
scope: spec.scope ?? null,
|
|
83
|
+
version: spec.version,
|
|
84
|
+
description: spec.description,
|
|
85
|
+
bin: spec.bin ?? null,
|
|
86
|
+
powerhouse: spec.features.powerhouse,
|
|
87
|
+
mastraEnabled: spec.features.mastra.enabled,
|
|
88
|
+
routineEnabled: spec.features.routine.enabled,
|
|
89
|
+
agentId: spec.features.mastra.agentId ?? null,
|
|
90
|
+
agentName: spec.features.mastra.agentName ?? null,
|
|
91
|
+
models: spec.features.mastra.models.map(m => ({
|
|
92
|
+
id: m.id,
|
|
93
|
+
isDefault: m.isDefault,
|
|
94
|
+
})),
|
|
95
|
+
profiles: spec.features.mastra.profiles.map(p => ({
|
|
96
|
+
id: p.id,
|
|
97
|
+
title: p.title,
|
|
98
|
+
content: p.content,
|
|
99
|
+
})),
|
|
100
|
+
packages: spec.packages.map(p => ({
|
|
101
|
+
id: p.id,
|
|
102
|
+
packageName: p.packageName,
|
|
103
|
+
documentTypes: p.documentTypes,
|
|
104
|
+
})),
|
|
105
|
+
externalSkills: spec.externalSkills.map(s => ({
|
|
106
|
+
id: s.id,
|
|
107
|
+
name: s.name,
|
|
108
|
+
githubUrl: s.githubUrl,
|
|
109
|
+
})),
|
|
110
|
+
};
|
|
90
111
|
}
|
|
91
112
|
export const specChangeTrigger = createDocumentChangeTrigger({
|
|
92
113
|
id: 'spec-change',
|
|
93
114
|
documentType: DOCUMENT_TYPE,
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
115
|
+
// No documentId → watches ALL ph-clint-project docs
|
|
116
|
+
async onChange(docs, ctx) {
|
|
117
|
+
const log = ctx.context.log;
|
|
118
|
+
const folders = ctx.context.folders;
|
|
119
|
+
// Build the mapping: on-disk projects + personal drive documents
|
|
120
|
+
const scanner = clintProject.projectScanner;
|
|
121
|
+
const scanResults = scanner
|
|
122
|
+
? scanProjects(ctx.context.workdir, scanner)
|
|
123
|
+
: [];
|
|
124
|
+
const mapping = await getProjectMapping(scanResults, folders);
|
|
125
|
+
// Auto-create spec documents for unlinked projects
|
|
103
126
|
const reactor = await ctx.reactor();
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
127
|
+
if (reactor && folders) {
|
|
128
|
+
for (const entry of mapping) {
|
|
129
|
+
if (entry.path && !entry.documentId) {
|
|
130
|
+
const existingSpec = await readProjectSpec(entry.path);
|
|
131
|
+
if (!existingSpec)
|
|
132
|
+
continue;
|
|
133
|
+
log?.info(`${TAG} creating spec document for unlinked project ${entry.name}`);
|
|
134
|
+
const newDoc = await reactor.client.createEmpty('powerhouse/ph-clint-project');
|
|
135
|
+
const docId = newDoc.header.id;
|
|
136
|
+
await folders.addDocument(docId, `specs/${entry.name}`, entry.name);
|
|
137
|
+
const importInput = specToImportInput(existingSpec);
|
|
138
|
+
await reactor.client.execute(docId, 'main', [importSpec(importInput)]);
|
|
139
|
+
existingSpec.documentId = docId;
|
|
140
|
+
existingSpec.documentType = 'powerhouse/ph-clint-project';
|
|
141
|
+
await writeProjectSpec(entry.path, existingSpec);
|
|
142
|
+
log?.info(`${TAG} linked project ${entry.name} to document ${docId}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
110
145
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
...current,
|
|
116
|
-
projectDocumentId: discovered,
|
|
146
|
+
for (const doc of docs) {
|
|
147
|
+
const spec = specFromDocumentState(doc.state.global, {
|
|
148
|
+
documentId: doc.header.id,
|
|
149
|
+
documentType: doc.header.documentType,
|
|
117
150
|
});
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
if (!spec) {
|
|
152
|
+
log?.debug(`${TAG} document ${doc.header.id} state did not validate against ClintProjectSpec, skipping`);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
// Find target directory from mapping
|
|
156
|
+
const entry = mapping.find((m) => m.documentId === doc.header.id);
|
|
157
|
+
const targetDir = entry?.path ?? path.join(ctx.context.workdir, spec.name);
|
|
158
|
+
// Per-project hash comparison
|
|
159
|
+
const nextHash = hashSpec(spec);
|
|
160
|
+
const prevHash = await readLastSpecHash(targetDir);
|
|
161
|
+
if (nextHash === prevHash) {
|
|
162
|
+
log?.debug(`${TAG} spec for ${spec.name} unchanged (hash=${nextHash.slice(0, 12)}…), skipping`);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
log?.debug(`${TAG} spec for ${spec.name} changed: ${prevHash ? prevHash.slice(0, 12) + '…' : '<none>'} → ${nextHash.slice(0, 12)}…`);
|
|
166
|
+
log?.info(`${TAG} regenerating ${spec.name} at ${targetDir} (doc=${doc.header.id})`);
|
|
167
|
+
const t0 = Date.now();
|
|
168
|
+
const warnings = [];
|
|
169
|
+
const result = await generateProject({
|
|
170
|
+
targetDir,
|
|
171
|
+
spec,
|
|
172
|
+
mode: 'update',
|
|
173
|
+
force: true,
|
|
174
|
+
onWarn: (m) => warnings.push(m),
|
|
175
|
+
});
|
|
176
|
+
// Write spec JSON with documentId
|
|
177
|
+
await writeProjectSpec(targetDir, spec);
|
|
178
|
+
await writeLastSpecHash(targetDir, nextHash);
|
|
179
|
+
const elapsed = Date.now() - t0;
|
|
180
|
+
if (log) {
|
|
181
|
+
if (result.migrated)
|
|
182
|
+
log.info(`${TAG} migrated flat → split layout`);
|
|
183
|
+
log.info(`${TAG} regenerated ${spec.name} in ${elapsed}ms: ${result.files.length} written, ${result.skipped.length} skipped, ${result.deleted.length} deleted`);
|
|
184
|
+
for (const w of warnings)
|
|
185
|
+
log.warn(`${TAG} ${w}`);
|
|
186
|
+
}
|
|
187
|
+
// Scaffold the reactor package (app dir) if it hasn't been initialized yet.
|
|
188
|
+
if (result.appDir) {
|
|
189
|
+
const appPkgJson = path.join(result.appDir, 'package.json');
|
|
190
|
+
let appExists = false;
|
|
191
|
+
try {
|
|
192
|
+
await fs.access(appPkgJson);
|
|
193
|
+
appExists = true;
|
|
194
|
+
}
|
|
195
|
+
catch { /* noop */ }
|
|
196
|
+
if (!appExists) {
|
|
197
|
+
log?.info(`${TAG} initializing reactor package at ${result.appDir}`);
|
|
198
|
+
const phResult = await runPhInit({
|
|
149
199
|
targetDir,
|
|
200
|
+
appDir: result.appDir,
|
|
150
201
|
spec,
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
onWarn: (m) => warnings.push(m),
|
|
202
|
+
log: (msg) => log?.info(`${TAG} ${msg}`),
|
|
203
|
+
stdio: 'ignore',
|
|
154
204
|
});
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (log) {
|
|
158
|
-
if (result.migrated)
|
|
159
|
-
log.info(`${TAG} migrated flat → split layout`);
|
|
160
|
-
log.info(`${TAG} regenerated in ${elapsed}ms: ${result.files.length} written, ${result.skipped.length} skipped, ${result.deleted.length} deleted`);
|
|
161
|
-
for (const w of warnings)
|
|
162
|
-
log.warn(`${TAG} ${w}`);
|
|
205
|
+
if (!phResult.ran) {
|
|
206
|
+
log?.warn(`${TAG} ph init skipped: ${phResult.reason ?? 'unknown'}`);
|
|
163
207
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const appPkgJson = path.join(result.appDir, 'package.json');
|
|
167
|
-
let appExists = false;
|
|
168
|
-
try {
|
|
169
|
-
await fs.access(appPkgJson);
|
|
170
|
-
appExists = true;
|
|
171
|
-
}
|
|
172
|
-
catch { /* noop */ }
|
|
173
|
-
if (!appExists) {
|
|
174
|
-
log?.info(`${TAG} initializing reactor package at ${result.appDir}`);
|
|
175
|
-
const phResult = await runPhInit({
|
|
176
|
-
targetDir,
|
|
177
|
-
appDir: result.appDir,
|
|
178
|
-
spec,
|
|
179
|
-
log: (msg) => log?.info(`${TAG} ${msg}`),
|
|
180
|
-
stdio: 'ignore',
|
|
181
|
-
});
|
|
182
|
-
if (!phResult.ran) {
|
|
183
|
-
log?.warn(`${TAG} ph init skipped: ${phResult.reason ?? 'unknown'}`);
|
|
184
|
-
}
|
|
185
|
-
else if (phResult.exitCode !== 0) {
|
|
186
|
-
log?.warn(`${TAG} ph init exited with code ${phResult.exitCode}`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
// Always ensure the app package name matches the spec's scope,
|
|
190
|
-
// even if ph init ran in a previous trigger cycle without scope.
|
|
191
|
-
if (spec.scope) {
|
|
192
|
-
try {
|
|
193
|
-
const raw = await fs.readFile(appPkgJson, 'utf8');
|
|
194
|
-
const pkg = JSON.parse(raw);
|
|
195
|
-
const appFolder = path.basename(result.appDir);
|
|
196
|
-
const scopedName = `@${spec.scope}/${appFolder}`;
|
|
197
|
-
if (pkg.name !== scopedName) {
|
|
198
|
-
pkg.name = scopedName;
|
|
199
|
-
pkg.publishConfig = { access: 'public' };
|
|
200
|
-
await fs.writeFile(appPkgJson, JSON.stringify(pkg, null, 2) + '\n');
|
|
201
|
-
log?.info(`${TAG} patched app package name to ${scopedName}`);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
catch (err) {
|
|
205
|
-
log?.warn(`${TAG} could not patch app package name: ${err instanceof Error ? err.message : String(err)}`);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
+
else if (phResult.exitCode !== 0) {
|
|
209
|
+
log?.warn(`${TAG} ph init exited with code ${phResult.exitCode}`);
|
|
208
210
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
211
|
+
}
|
|
212
|
+
// Always ensure the app package name matches the spec's scope,
|
|
213
|
+
// even if ph init ran in a previous trigger cycle without scope.
|
|
214
|
+
if (spec.scope) {
|
|
215
|
+
try {
|
|
216
|
+
const raw = await fs.readFile(appPkgJson, 'utf8');
|
|
217
|
+
const pkg = JSON.parse(raw);
|
|
218
|
+
const appFolder = path.basename(result.appDir);
|
|
219
|
+
const scopedName = `@${spec.scope}/${appFolder}`;
|
|
220
|
+
if (pkg.name !== scopedName) {
|
|
221
|
+
pkg.name = scopedName;
|
|
222
|
+
pkg.publishConfig = { access: 'public' };
|
|
223
|
+
await fs.writeFile(appPkgJson, JSON.stringify(pkg, null, 2) + '\n');
|
|
224
|
+
log?.info(`${TAG} patched app package name to ${scopedName}`);
|
|
218
225
|
}
|
|
219
226
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
catch (err) {
|
|
228
|
+
log?.warn(`${TAG} could not patch app package name: ${err instanceof Error ? err.message : String(err)}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Sync external skills after codegen.
|
|
233
|
+
if (spec.externalSkills.length > 0) {
|
|
234
|
+
const skillsResult = await syncExternalSkills({
|
|
235
|
+
targetDir,
|
|
236
|
+
desired: spec.externalSkills,
|
|
237
|
+
log: (msg) => log?.info(`${TAG} ${msg}`),
|
|
238
|
+
});
|
|
239
|
+
if (log && (skillsResult.added.length > 0 || skillsResult.removed.length > 0)) {
|
|
240
|
+
log.info(`${TAG} skills sync: +${skillsResult.added.length} -${skillsResult.removed.length}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Work done inline, no additional work item needed
|
|
245
|
+
return null;
|
|
229
246
|
},
|
|
230
247
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powerhousedao/ph-clint-cli",
|
|
3
|
-
"version": "0.1.0-dev.
|
|
3
|
+
"version": "0.1.0-dev.23",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
"@mastra/core": "^1.22.0",
|
|
29
29
|
"@mastra/libsql": "^1.7.4",
|
|
30
30
|
"@mastra/memory": "^1.13.1",
|
|
31
|
-
"@powerhousedao/ph-clint": "^0.1.0-dev.
|
|
32
|
-
"@powerhousedao/ph-clint-app": "^0.1.0-dev.
|
|
33
|
-
"@powerhousedao/ph-clint-dev": "^0.1.0-dev.
|
|
31
|
+
"@powerhousedao/ph-clint": "^0.1.0-dev.23",
|
|
32
|
+
"@powerhousedao/ph-clint-app": "^0.1.0-dev.23",
|
|
33
|
+
"@powerhousedao/ph-clint-dev": "^0.1.0-dev.23",
|
|
34
34
|
"@powerhousedao/reactor": "6.0.0-dev.170",
|
|
35
35
|
"@powerhousedao/reactor-api": "6.0.0-dev.170",
|
|
36
36
|
"@powerhousedao/shared": "6.0.0-dev.170",
|