@powerhousedao/ph-clint-cli 0.1.0-dev.22 → 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.
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Builds `src/agents/agent.ts` — a minimal Mastra agent factory.
2
+ * Builds `src/agents/agent.ts` — a Mastra agent factory.
3
3
  *
4
- * Emitted only when `features.mastra.enabled`. The generated agent falls back
5
- * to a deterministic echo-style demo provider when no API key is configured,
6
- * so `pnpm dev` works immediately out of the box.
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
- export function buildAgentTs(spec) {
2
- const id = spec.name;
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
- lines.push(' agents: {},');
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
- lines.push(" model: z.string().default('anthropic/claude-haiku-4-5').describe('LLM model to use'),");
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
- { relativePath: 'prompts/agent-profiles/AgentBase.md', build: buildAgentBaseMd },
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
- // When Mastra is enabled but we still emit the demo `AgentProvider` shim
16
- // (not a real `@mastra/core` Agent), there is no `mastra` export yet —
17
- // `mastra dev` will surface a helpful message once you wire a real Agent.
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`.',
@@ -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
- for (const builder of CLI_FILE_BUILDERS) {
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;
@@ -73,13 +73,22 @@ export function buildSpec(input) {
73
73
  // Routine is forced on when Mastra is on — the REPL-only path works, but
74
74
  // the agent routine loop is what most users actually want.
75
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 };
76
85
  const candidate = {
77
86
  name,
78
87
  scope,
79
88
  description: input.description ?? '',
80
89
  features: {
81
90
  powerhouse: input.enablePowerhouse ? 'Connect' : 'Disabled',
82
- mastra: { enabled: input.enableMastra },
91
+ mastra,
83
92
  routine: { enabled: routine },
84
93
  },
85
94
  };
@@ -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>;
@@ -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 = { enabled: false };
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(),
@@ -86,6 +86,17 @@ export function specToImportInput(spec) {
86
86
  powerhouse: spec.features.powerhouse,
87
87
  mastraEnabled: spec.features.mastra.enabled,
88
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
+ })),
89
100
  packages: spec.packages.map(p => ({
90
101
  id: p.id,
91
102
  packageName: p.packageName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powerhousedao/ph-clint-cli",
3
- "version": "0.1.0-dev.22",
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.22",
32
- "@powerhousedao/ph-clint-app": "^0.1.0-dev.22",
33
- "@powerhousedao/ph-clint-dev": "^0.1.0-dev.22",
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",