@porchestra/cli 1.0.0 → 1.0.1
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/bin/porchestra.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1602 -0
- package/dist/index.js.map +1 -0
- package/package.json +11 -1
- package/src/agents/testPrompt/ast.json +0 -71
- package/src/agents/testPrompt/config.ts +0 -18
- package/src/agents/testPrompt/index.ts +0 -64
- package/src/agents/testPrompt/schemas.ts +0 -45
- package/src/agents/testPrompt/tools.ts +0 -88
- package/src/commands/agents.ts +0 -173
- package/src/commands/config.ts +0 -97
- package/src/commands/explore.ts +0 -160
- package/src/commands/login.ts +0 -101
- package/src/commands/logout.ts +0 -52
- package/src/commands/pull.ts +0 -220
- package/src/commands/status.ts +0 -78
- package/src/commands/whoami.ts +0 -56
- package/src/core/api/client.ts +0 -133
- package/src/core/auth/auth-service.ts +0 -176
- package/src/core/auth/token-manager.ts +0 -47
- package/src/core/config/config-manager.ts +0 -107
- package/src/core/config/config-schema.ts +0 -56
- package/src/core/config/project-tracker.ts +0 -158
- package/src/core/generators/code-generator.ts +0 -329
- package/src/core/generators/schema-generator.ts +0 -59
- package/src/index.ts +0 -85
- package/src/types/index.ts +0 -214
- package/src/utils/date.ts +0 -23
- package/src/utils/errors.ts +0 -38
- package/src/utils/logger.ts +0 -11
- package/src/utils/path-utils.ts +0 -47
- package/tests/unit/config-manager.test.ts +0 -74
- package/tests/unit/config-schema.test.ts +0 -61
- package/tests/unit/path-utils.test.ts +0 -53
- package/tests/unit/schema-generator.test.ts +0 -82
- package/tsconfig.json +0 -30
- package/tsup.config.ts +0 -19
- package/vitest.config.ts +0 -20
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { ConfigManager } from '../config/config-manager.js';
|
|
2
|
-
|
|
3
|
-
export class TokenManager {
|
|
4
|
-
constructor(private configManager: ConfigManager) {}
|
|
5
|
-
|
|
6
|
-
async getToken(): Promise<string | undefined> {
|
|
7
|
-
const config = await this.configManager.get();
|
|
8
|
-
return config.auth?.token;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async getTokenId(): Promise<string | undefined> {
|
|
12
|
-
const config = await this.configManager.get();
|
|
13
|
-
return config.auth?.tokenId;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async getExpiresAt(): Promise<string | undefined> {
|
|
17
|
-
const config = await this.configManager.get();
|
|
18
|
-
return config.auth?.expiresAt;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async isAuthenticated(): Promise<boolean> {
|
|
22
|
-
const token = await this.getToken();
|
|
23
|
-
if (!token) return false;
|
|
24
|
-
|
|
25
|
-
const expiresAt = await this.getExpiresAt();
|
|
26
|
-
if (!expiresAt) return false;
|
|
27
|
-
|
|
28
|
-
const expiryDate = new Date(expiresAt);
|
|
29
|
-
return expiryDate > new Date();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async getDaysUntilExpiry(): Promise<number | null> {
|
|
33
|
-
const expiresAt = await this.getExpiresAt();
|
|
34
|
-
if (!expiresAt) return null;
|
|
35
|
-
|
|
36
|
-
const expiryDate = new Date(expiresAt);
|
|
37
|
-
const now = new Date();
|
|
38
|
-
const diffMs = expiryDate.getTime() - now.getTime();
|
|
39
|
-
return Math.ceil(diffMs / (1000 * 60 * 60 * 24));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async isExpiringSoon(days = 7): Promise<boolean> {
|
|
43
|
-
const daysUntilExpiry = await this.getDaysUntilExpiry();
|
|
44
|
-
if (daysUntilExpiry === null) return false;
|
|
45
|
-
return daysUntilExpiry <= days;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import { z } from 'zod';
|
|
5
|
-
import { CliConfig, CliConfigSchema } from './config-schema.js';
|
|
6
|
-
|
|
7
|
-
const DEFAULT_CONFIG_DIR = path.join(os.homedir(), '.porchestra');
|
|
8
|
-
const CONFIG_FILE = 'config.json';
|
|
9
|
-
|
|
10
|
-
export class ConfigManager {
|
|
11
|
-
private configPath: string;
|
|
12
|
-
private config: CliConfig | null = null;
|
|
13
|
-
|
|
14
|
-
constructor() {
|
|
15
|
-
const configDir = process.env.PORCHESTRA_CONFIG_DIR || DEFAULT_CONFIG_DIR;
|
|
16
|
-
this.configPath = path.join(configDir, CONFIG_FILE);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async load(): Promise<CliConfig> {
|
|
20
|
-
try {
|
|
21
|
-
const data = await fs.readFile(this.configPath, 'utf-8');
|
|
22
|
-
const parsed = JSON.parse(data);
|
|
23
|
-
|
|
24
|
-
// STRICT VALIDATION - throws on error
|
|
25
|
-
const validated = CliConfigSchema.parse(parsed);
|
|
26
|
-
this.config = validated;
|
|
27
|
-
return validated;
|
|
28
|
-
} catch (error) {
|
|
29
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
30
|
-
// Return default config
|
|
31
|
-
const defaultConfig = { version: '1.0.0' } as const;
|
|
32
|
-
this.config = CliConfigSchema.parse(defaultConfig);
|
|
33
|
-
return this.config;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// THROW on validation errors
|
|
37
|
-
if (error instanceof z.ZodError) {
|
|
38
|
-
console.error('❌ Config validation failed:');
|
|
39
|
-
error.errors.forEach((err) => {
|
|
40
|
-
console.error(` - ${err.path.join('.')}: ${err.message}`);
|
|
41
|
-
});
|
|
42
|
-
throw new Error(`Invalid config file at ${this.configPath}. Please run 'porchestra config reset' to fix.`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
throw error;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async save(config: CliConfig): Promise<void> {
|
|
50
|
-
try {
|
|
51
|
-
// STRICT VALIDATION before save
|
|
52
|
-
const validated = CliConfigSchema.parse(config);
|
|
53
|
-
|
|
54
|
-
// Ensure directory exists
|
|
55
|
-
const configDir = path.dirname(this.configPath);
|
|
56
|
-
await fs.mkdir(configDir, { recursive: true });
|
|
57
|
-
|
|
58
|
-
// Atomic write (write to temp, then rename)
|
|
59
|
-
const tempPath = `${this.configPath}.tmp`;
|
|
60
|
-
await fs.writeFile(
|
|
61
|
-
tempPath,
|
|
62
|
-
JSON.stringify(validated, null, 2),
|
|
63
|
-
'utf-8'
|
|
64
|
-
);
|
|
65
|
-
await fs.rename(tempPath, this.configPath);
|
|
66
|
-
|
|
67
|
-
this.config = validated;
|
|
68
|
-
} catch (error) {
|
|
69
|
-
if (error instanceof z.ZodError) {
|
|
70
|
-
console.error('❌ Config validation failed on save:');
|
|
71
|
-
error.errors.forEach((err) => {
|
|
72
|
-
console.error(` - ${err.path.join('.')}: ${err.message}`);
|
|
73
|
-
});
|
|
74
|
-
throw new Error('Failed to save config: validation error');
|
|
75
|
-
}
|
|
76
|
-
throw error;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async get(): Promise<CliConfig> {
|
|
81
|
-
if (!this.config) {
|
|
82
|
-
return this.load();
|
|
83
|
-
}
|
|
84
|
-
return this.config;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async update(updater: (config: CliConfig) => CliConfig): Promise<void> {
|
|
88
|
-
const config = await this.get();
|
|
89
|
-
const updated = updater({ ...config }); // Create new object
|
|
90
|
-
await this.save(updated);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async clear(): Promise<void> {
|
|
94
|
-
try {
|
|
95
|
-
await fs.unlink(this.configPath);
|
|
96
|
-
} catch (error) {
|
|
97
|
-
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
98
|
-
throw error;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
this.config = null;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
getConfigPath(): string {
|
|
105
|
-
return this.configPath;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
|
|
3
|
-
// Simplified datetime validation - just accepts any string
|
|
4
|
-
const DateTimeStringSchema = z.string().optional();
|
|
5
|
-
|
|
6
|
-
export const TrackedAgentSchema = z.object({
|
|
7
|
-
agentId: z.string().uuid(),
|
|
8
|
-
agentName: z.string(),
|
|
9
|
-
agentSlug: z.string(),
|
|
10
|
-
folderPath: z.string(),
|
|
11
|
-
selectedAt: DateTimeStringSchema,
|
|
12
|
-
lastPulledAt: DateTimeStringSchema.optional(),
|
|
13
|
-
lastPulledVersion: z.string().optional(),
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
export const TrackedProjectSchema = z.object({
|
|
17
|
-
projectId: z.string().uuid(),
|
|
18
|
-
projectName: z.string(),
|
|
19
|
-
projectSlug: z.string(),
|
|
20
|
-
selectedAt: DateTimeStringSchema,
|
|
21
|
-
agents: z.array(TrackedAgentSchema),
|
|
22
|
-
lastPulledAt: DateTimeStringSchema.optional(),
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
export const CliConfigSchema = z.object({
|
|
26
|
-
auth: z.object({
|
|
27
|
-
token: z.string().optional(),
|
|
28
|
-
tokenId: z.string().uuid().optional(),
|
|
29
|
-
expiresAt: DateTimeStringSchema.optional(),
|
|
30
|
-
deviceName: z.string().optional(),
|
|
31
|
-
}).optional(),
|
|
32
|
-
|
|
33
|
-
api: z.object({
|
|
34
|
-
baseUrl: z.string().url().default('https://api.porchestra.io/v1'),
|
|
35
|
-
skipTlsVerify: z.boolean().default(false),
|
|
36
|
-
}).default({}),
|
|
37
|
-
|
|
38
|
-
trackedProjects: z.array(TrackedProjectSchema).default([]),
|
|
39
|
-
|
|
40
|
-
output: z.object({
|
|
41
|
-
baseDir: z.string().default('./src/agents'),
|
|
42
|
-
createIndexFiles: z.boolean().default(true),
|
|
43
|
-
}).default({}),
|
|
44
|
-
|
|
45
|
-
cli: z.object({
|
|
46
|
-
lastLoginAt: DateTimeStringSchema.optional(),
|
|
47
|
-
lastVersionCheck: DateTimeStringSchema.optional(),
|
|
48
|
-
latestKnownVersion: z.string().optional(),
|
|
49
|
-
}).optional(),
|
|
50
|
-
|
|
51
|
-
version: z.literal('1.0.0'),
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
export type TrackedAgent = z.infer<typeof TrackedAgentSchema>;
|
|
55
|
-
export type TrackedProject = z.infer<typeof TrackedProjectSchema>;
|
|
56
|
-
export type CliConfig = z.infer<typeof CliConfigSchema>;
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { ConfigManager } from './config-manager.js';
|
|
2
|
-
import { TrackedProject, TrackedAgent } from './config-schema.js';
|
|
3
|
-
|
|
4
|
-
export interface ProjectBrief {
|
|
5
|
-
id: string;
|
|
6
|
-
name: string;
|
|
7
|
-
slug: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface AgentBrief {
|
|
11
|
-
id: string;
|
|
12
|
-
name: string;
|
|
13
|
-
slug: string;
|
|
14
|
-
folderPath: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class ProjectTracker {
|
|
18
|
-
constructor(private configManager: ConfigManager) {}
|
|
19
|
-
|
|
20
|
-
async getTrackedProjects(): Promise<TrackedProject[]> {
|
|
21
|
-
const config = await this.configManager.get();
|
|
22
|
-
return config.trackedProjects || [];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async getTrackedAgents(projectId: string): Promise<TrackedAgent[]> {
|
|
26
|
-
const config = await this.configManager.get();
|
|
27
|
-
const project = config.trackedProjects?.find(p => p.projectId === projectId);
|
|
28
|
-
return project?.agents || [];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async listTrackedAgents(): Promise<Array<{ project: TrackedProject; agent: TrackedAgent }>> {
|
|
32
|
-
const projects = await this.getTrackedProjects();
|
|
33
|
-
return projects.flatMap(project =>
|
|
34
|
-
project.agents.map(agent => ({ project, agent }))
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async trackProject(project: ProjectBrief, agents: AgentBrief[]): Promise<void> {
|
|
39
|
-
await this.configManager.update((config) => {
|
|
40
|
-
const existingIndex = config.trackedProjects.findIndex(
|
|
41
|
-
p => p.projectId === project.id
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
const trackedAgents: TrackedAgent[] = agents.map(agent => ({
|
|
45
|
-
agentId: agent.id,
|
|
46
|
-
agentName: agent.name,
|
|
47
|
-
agentSlug: agent.slug,
|
|
48
|
-
folderPath: agent.folderPath,
|
|
49
|
-
selectedAt: new Date().toISOString(),
|
|
50
|
-
}));
|
|
51
|
-
|
|
52
|
-
const trackedProject: TrackedProject = {
|
|
53
|
-
projectId: project.id,
|
|
54
|
-
projectName: project.name,
|
|
55
|
-
projectSlug: project.slug,
|
|
56
|
-
selectedAt: new Date().toISOString(),
|
|
57
|
-
agents: trackedAgents,
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const newProjects = [...config.trackedProjects];
|
|
61
|
-
if (existingIndex >= 0) {
|
|
62
|
-
newProjects[existingIndex] = trackedProject;
|
|
63
|
-
} else {
|
|
64
|
-
newProjects.push(trackedProject);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return { ...config, trackedProjects: newProjects };
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async untrackProject(projectId: string): Promise<void> {
|
|
72
|
-
await this.configManager.update((config) => ({
|
|
73
|
-
...config,
|
|
74
|
-
trackedProjects: config.trackedProjects.filter(
|
|
75
|
-
p => p.projectId !== projectId
|
|
76
|
-
),
|
|
77
|
-
}));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async untrackAgent(projectId: string, agentId: string): Promise<boolean> {
|
|
81
|
-
let removed = false;
|
|
82
|
-
|
|
83
|
-
await this.configManager.update((config) => {
|
|
84
|
-
const projectIndex = config.trackedProjects.findIndex(
|
|
85
|
-
p => p.projectId === projectId
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
if (projectIndex === -1) return config;
|
|
89
|
-
|
|
90
|
-
const project = config.trackedProjects[projectIndex];
|
|
91
|
-
const remainingAgents = project.agents.filter(
|
|
92
|
-
agent => agent.agentId !== agentId
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
if (remainingAgents.length === project.agents.length) {
|
|
96
|
-
return config;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
removed = true;
|
|
100
|
-
|
|
101
|
-
const newProjects = [...config.trackedProjects];
|
|
102
|
-
|
|
103
|
-
if (remainingAgents.length === 0) {
|
|
104
|
-
newProjects.splice(projectIndex, 1);
|
|
105
|
-
} else {
|
|
106
|
-
newProjects[projectIndex] = { ...project, agents: remainingAgents };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return { ...config, trackedProjects: newProjects };
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
return removed;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async updateLastPulled(
|
|
116
|
-
projectId: string,
|
|
117
|
-
agentId: string,
|
|
118
|
-
version: string
|
|
119
|
-
): Promise<void> {
|
|
120
|
-
await this.configManager.update((config) => {
|
|
121
|
-
const projectIndex = config.trackedProjects.findIndex(
|
|
122
|
-
p => p.projectId === projectId
|
|
123
|
-
);
|
|
124
|
-
if (projectIndex === -1) return config;
|
|
125
|
-
|
|
126
|
-
const project = config.trackedProjects[projectIndex];
|
|
127
|
-
const agentIndex = project.agents.findIndex(
|
|
128
|
-
a => a.agentId === agentId
|
|
129
|
-
);
|
|
130
|
-
if (agentIndex === -1) return config;
|
|
131
|
-
|
|
132
|
-
const now = new Date().toISOString();
|
|
133
|
-
const newAgents = [...project.agents];
|
|
134
|
-
newAgents[agentIndex] = {
|
|
135
|
-
...newAgents[agentIndex],
|
|
136
|
-
lastPulledAt: now,
|
|
137
|
-
lastPulledVersion: version,
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const newProjects = [...config.trackedProjects];
|
|
141
|
-
newProjects[projectIndex] = {
|
|
142
|
-
...project,
|
|
143
|
-
agents: newAgents,
|
|
144
|
-
lastPulledAt: now,
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
return { ...config, trackedProjects: newProjects };
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async getSummary(): Promise<{ projectCount: number; agentCount: number }> {
|
|
152
|
-
const projects = await this.getTrackedProjects();
|
|
153
|
-
return {
|
|
154
|
-
projectCount: projects.length,
|
|
155
|
-
agentCount: projects.reduce((sum, p) => sum + p.agents.length, 0),
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
}
|
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import Handlebars from 'handlebars';
|
|
4
|
-
import { calculateAgentOutputPath } from '../../utils/path-utils.js';
|
|
5
|
-
import { AgentToolsResponse } from '../../types/index.js';
|
|
6
|
-
import { logger } from '../../utils/logger.js';
|
|
7
|
-
|
|
8
|
-
interface GenerateOptions {
|
|
9
|
-
agent: AgentToolsResponse['agent'];
|
|
10
|
-
tools: AgentToolsResponse['tools'];
|
|
11
|
-
components: AgentToolsResponse['components'];
|
|
12
|
-
outputDir: string;
|
|
13
|
-
forceOverwrite: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface ToolData {
|
|
17
|
-
name: string;
|
|
18
|
-
description: string;
|
|
19
|
-
parameters: object;
|
|
20
|
-
returns: object | null;
|
|
21
|
-
isBuiltin?: boolean;
|
|
22
|
-
builtinType?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface TemplateData {
|
|
26
|
-
agentName: string;
|
|
27
|
-
version: string;
|
|
28
|
-
generatedAt: string;
|
|
29
|
-
tools: ToolData[];
|
|
30
|
-
variableSchema: object | null;
|
|
31
|
-
inputSchema: object | null;
|
|
32
|
-
responseSchema: object | null;
|
|
33
|
-
responseJsonSchema: object;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Register Handlebars helpers
|
|
37
|
-
Handlebars.registerHelper('pascalCase', function(str: string) {
|
|
38
|
-
return str
|
|
39
|
-
.replace(/[-_\s](.)/g, (_, char) => char.toUpperCase())
|
|
40
|
-
.replace(/^(.)/, (_, char) => char.toUpperCase());
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
Handlebars.registerHelper('camelCase', function(str: string) {
|
|
44
|
-
return str
|
|
45
|
-
.replace(/[-_\s](.)/g, (_, char) => char.toUpperCase())
|
|
46
|
-
.replace(/^(.)/, (_, char) => char.toLowerCase());
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
Handlebars.registerHelper('zodType', function(schema: any) {
|
|
50
|
-
return jsonSchemaToZodType(schema);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
Handlebars.registerHelper('json', function(value: any) {
|
|
54
|
-
return JSON.stringify(value ?? {}, null, 2);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
function jsonSchemaToZodType(schema: any): string {
|
|
58
|
-
if (!schema) return 'z.any()';
|
|
59
|
-
|
|
60
|
-
switch (schema.type) {
|
|
61
|
-
case 'string': {
|
|
62
|
-
let zod = 'z.string()';
|
|
63
|
-
if (schema.minLength) zod += `.min(${schema.minLength})`;
|
|
64
|
-
if (schema.maxLength) zod += `.max(${schema.maxLength})`;
|
|
65
|
-
if (schema.pattern) zod += `.regex(/${schema.pattern}/)`;
|
|
66
|
-
if (schema.enum) zod = `z.enum([${schema.enum.map((e: string) => `'${e}'`).join(', ')}])`;
|
|
67
|
-
if (schema.nullable) zod += '.nullable()';
|
|
68
|
-
return zod;
|
|
69
|
-
}
|
|
70
|
-
case 'number':
|
|
71
|
-
case 'integer': {
|
|
72
|
-
let zod = schema.type === 'integer' ? 'z.number().int()' : 'z.number()';
|
|
73
|
-
if (schema.minimum !== undefined) zod += `.min(${schema.minimum})`;
|
|
74
|
-
if (schema.maximum !== undefined) zod += `.max(${schema.maximum})`;
|
|
75
|
-
if (schema.nullable) zod += '.nullable()';
|
|
76
|
-
return zod;
|
|
77
|
-
}
|
|
78
|
-
case 'boolean': {
|
|
79
|
-
let zod = 'z.boolean()';
|
|
80
|
-
if (schema.nullable) zod += '.nullable()';
|
|
81
|
-
return zod;
|
|
82
|
-
}
|
|
83
|
-
case 'array': {
|
|
84
|
-
const itemType = jsonSchemaToZodType(schema.items);
|
|
85
|
-
let zod = `z.array(${itemType})`;
|
|
86
|
-
if (schema.minItems) zod += `.min(${schema.minItems})`;
|
|
87
|
-
if (schema.maxItems) zod += `.max(${schema.maxItems})`;
|
|
88
|
-
if (schema.nullable) zod += '.nullable()';
|
|
89
|
-
return zod;
|
|
90
|
-
}
|
|
91
|
-
case 'object': {
|
|
92
|
-
if (!schema.properties) return 'z.record(z.any())';
|
|
93
|
-
|
|
94
|
-
const required = new Set(schema.required || []);
|
|
95
|
-
const props = Object.entries(schema.properties).map(([key, prop]: [string, any]) => {
|
|
96
|
-
const propType = jsonSchemaToZodType(prop);
|
|
97
|
-
const isRequired = required.has(key);
|
|
98
|
-
const formattedKey = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
|
|
99
|
-
return ` ${formattedKey}: ${isRequired ? propType : `${propType}.optional()`}`;
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
let zod = `z.object({\n${props.join(',\n')}\n})`;
|
|
103
|
-
if (schema.additionalProperties === false) zod += '.strict()';
|
|
104
|
-
if (schema.nullable) zod += '.nullable()';
|
|
105
|
-
return zod;
|
|
106
|
-
}
|
|
107
|
-
default:
|
|
108
|
-
return 'z.any()';
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export class CodeGenerator {
|
|
113
|
-
private schemasTemplate: Handlebars.TemplateDelegate;
|
|
114
|
-
private toolsTemplate: Handlebars.TemplateDelegate;
|
|
115
|
-
private indexTemplate: Handlebars.TemplateDelegate;
|
|
116
|
-
|
|
117
|
-
constructor() {
|
|
118
|
-
this.schemasTemplate = Handlebars.compile(schemasTemplateSource);
|
|
119
|
-
this.toolsTemplate = Handlebars.compile(toolsTemplateSource);
|
|
120
|
-
this.indexTemplate = Handlebars.compile(indexTemplateSource);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async generate(options: GenerateOptions): Promise<void> {
|
|
124
|
-
const { agent, tools, components, outputDir } = options;
|
|
125
|
-
|
|
126
|
-
await fs.mkdir(outputDir, { recursive: true });
|
|
127
|
-
|
|
128
|
-
const responseJsonSchema = (components?.responseSchema?.content as object) || {};
|
|
129
|
-
|
|
130
|
-
const templateData: TemplateData = {
|
|
131
|
-
agentName: agent?.name || 'Unknown Agent',
|
|
132
|
-
version: agent?.version || 'unknown',
|
|
133
|
-
generatedAt: new Date().toISOString(),
|
|
134
|
-
tools: (tools || [])
|
|
135
|
-
.filter(t => t && !t.isBuiltin && typeof t.name === 'string' && t.name.length > 0)
|
|
136
|
-
.map(t => ({
|
|
137
|
-
name: t.name,
|
|
138
|
-
description: t.description || '',
|
|
139
|
-
parameters: t.parameters || {},
|
|
140
|
-
returns: t.returns ?? null,
|
|
141
|
-
isBuiltin: t.isBuiltin,
|
|
142
|
-
builtinType: t.builtinType,
|
|
143
|
-
})),
|
|
144
|
-
variableSchema: (components?.variableSchema?.content as object) || null,
|
|
145
|
-
inputSchema: (components?.inputSchema?.content as object) || null,
|
|
146
|
-
responseSchema: (components?.responseSchema?.content as object) || null,
|
|
147
|
-
responseJsonSchema,
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
// Generate ast.json (always overwrite)
|
|
151
|
-
const astPath = path.join(outputDir, 'ast.json');
|
|
152
|
-
const astContent = JSON.stringify(components?.ast?.content ?? {}, null, 2);
|
|
153
|
-
await fs.writeFile(astPath, astContent, 'utf-8');
|
|
154
|
-
|
|
155
|
-
// Generate config.ts (always overwrite)
|
|
156
|
-
const configPath = path.join(outputDir, 'config.ts');
|
|
157
|
-
const configContent = `/**
|
|
158
|
-
* Auto-generated model config for ${templateData.agentName}
|
|
159
|
-
* Generated: ${templateData.generatedAt}
|
|
160
|
-
* Version: ${templateData.version}
|
|
161
|
-
* DO NOT MODIFY - This file is auto-generated by Porchestra CLI
|
|
162
|
-
*/
|
|
163
|
-
|
|
164
|
-
export const ModelConfig = ${JSON.stringify(components?.modelConfig?.content ?? {}, null, 2)} as const;
|
|
165
|
-
`;
|
|
166
|
-
await fs.writeFile(configPath, configContent, 'utf-8');
|
|
167
|
-
|
|
168
|
-
// Generate schemas.ts (always overwrite)
|
|
169
|
-
const schemasPath = path.join(outputDir, 'schemas.ts');
|
|
170
|
-
const schemasContent = this.schemasTemplate(templateData);
|
|
171
|
-
await fs.writeFile(schemasPath, schemasContent, 'utf-8');
|
|
172
|
-
|
|
173
|
-
// Generate tools.ts (always overwrite)
|
|
174
|
-
const toolsPath = path.join(outputDir, 'tools.ts');
|
|
175
|
-
const toolsContent = this.toolsTemplate(templateData);
|
|
176
|
-
await fs.writeFile(toolsPath, toolsContent, 'utf-8');
|
|
177
|
-
|
|
178
|
-
// Generate index.ts (always overwrite)
|
|
179
|
-
const indexPath = path.join(outputDir, 'index.ts');
|
|
180
|
-
const indexContent = this.indexTemplate(templateData);
|
|
181
|
-
await fs.writeFile(indexPath, indexContent, 'utf-8');
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
calculateOutputPath(baseDir: string, folderPath: string, agentName: string): string {
|
|
185
|
-
return calculateAgentOutputPath(baseDir, folderPath, agentName);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Template sources
|
|
190
|
-
const schemasTemplateSource = `/**
|
|
191
|
-
* Auto-generated Zod schemas for {{agentName}}
|
|
192
|
-
* Generated: {{generatedAt}}
|
|
193
|
-
* Version: {{version}}
|
|
194
|
-
* DO NOT MODIFY - This file is auto-generated by Porchestra CLI
|
|
195
|
-
*/
|
|
196
|
-
|
|
197
|
-
import { z } from 'zod';
|
|
198
|
-
|
|
199
|
-
// 1. System Prompt Variables
|
|
200
|
-
export const VariableSchema = {{{zodType variableSchema}}};
|
|
201
|
-
export type AgentVariables = z.infer<typeof VariableSchema>;
|
|
202
|
-
|
|
203
|
-
// 2. User Input
|
|
204
|
-
export const InputSchema = {{{zodType inputSchema}}};
|
|
205
|
-
export type AgentInput = z.infer<typeof InputSchema>;
|
|
206
|
-
|
|
207
|
-
// 3. Model Response
|
|
208
|
-
export const ResponseSchema = {{{zodType responseSchema}}};
|
|
209
|
-
export type AgentResponse = z.infer<typeof ResponseSchema>;
|
|
210
|
-
|
|
211
|
-
// 4. Raw JSON Schema (for LLM Provider 'response_format')
|
|
212
|
-
export const ResponseJsonSchema = {{{json responseJsonSchema}}} as const;
|
|
213
|
-
`;
|
|
214
|
-
|
|
215
|
-
const toolsTemplateSource = `/**
|
|
216
|
-
* Auto-generated tools for {{agentName}}
|
|
217
|
-
* Generated: {{generatedAt}}
|
|
218
|
-
* Version: {{version}}
|
|
219
|
-
* DO NOT MODIFY - This file is auto-generated by Porchestra CLI
|
|
220
|
-
*/
|
|
221
|
-
|
|
222
|
-
import { z } from 'zod';
|
|
223
|
-
|
|
224
|
-
{{#each tools}}
|
|
225
|
-
// Schema for {{name}}
|
|
226
|
-
export const {{pascalCase name}}Params = {{{zodType parameters}}};
|
|
227
|
-
export type {{pascalCase name}}ParamsType = z.infer<typeof {{pascalCase name}}Params>;
|
|
228
|
-
|
|
229
|
-
{{/each}}
|
|
230
|
-
// The Interface the user MUST implement
|
|
231
|
-
export interface ToolImplementation {
|
|
232
|
-
{{#each tools}}
|
|
233
|
-
{{camelCase name}}: (args: {{pascalCase name}}ParamsType) => Promise<unknown>;
|
|
234
|
-
{{/each}}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Raw Tool Definitions for provider SDKs
|
|
238
|
-
export const ToolDefinitions = [
|
|
239
|
-
{{#each tools}}
|
|
240
|
-
{
|
|
241
|
-
name: '{{name}}',
|
|
242
|
-
description: {{{json description}}},
|
|
243
|
-
parameters: {{{json parameters}}},
|
|
244
|
-
},
|
|
245
|
-
{{/each}}
|
|
246
|
-
] as const;
|
|
247
|
-
|
|
248
|
-
// Dispatcher with validation
|
|
249
|
-
export async function dispatch(
|
|
250
|
-
name: string,
|
|
251
|
-
args: unknown,
|
|
252
|
-
impl: ToolImplementation
|
|
253
|
-
): Promise<unknown> {
|
|
254
|
-
switch (name) {
|
|
255
|
-
{{#each tools}}
|
|
256
|
-
case '{{name}}':
|
|
257
|
-
return await impl.{{camelCase name}}({{pascalCase name}}Params.parse(args));
|
|
258
|
-
{{/each}}
|
|
259
|
-
default:
|
|
260
|
-
throw new Error(\`Unknown tool: \${name}\`);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
`;
|
|
264
|
-
|
|
265
|
-
const indexTemplateSource = `/**
|
|
266
|
-
* Auto-generated Agent class for {{agentName}}
|
|
267
|
-
* Generated: {{generatedAt}}
|
|
268
|
-
* Version: {{version}}
|
|
269
|
-
* DO NOT MODIFY - This file is auto-generated by Porchestra CLI
|
|
270
|
-
*/
|
|
271
|
-
|
|
272
|
-
import { PorchestraRuntime } from '@porchestra/core';
|
|
273
|
-
import rawAst from './ast.json';
|
|
274
|
-
import { ModelConfig } from './config.js';
|
|
275
|
-
import * as Schemas from './schemas.js';
|
|
276
|
-
import * as Tools from './tools.js';
|
|
277
|
-
|
|
278
|
-
export class {{pascalCase agentName}}Agent {
|
|
279
|
-
/**
|
|
280
|
-
* @param tools - The implementation of the tool logic. Required if the agent uses tools.
|
|
281
|
-
*/
|
|
282
|
-
constructor(private readonly tools?: Tools.ToolImplementation) {}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Renders the System Prompt.
|
|
286
|
-
* Throws ZodError if variables are invalid.
|
|
287
|
-
*/
|
|
288
|
-
public renderPrompt(variables: Schemas.AgentVariables): string {
|
|
289
|
-
const safeVars = Schemas.VariableSchema.parse(variables);
|
|
290
|
-
return PorchestraRuntime.render(rawAst as any, safeVars);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Routes an LLM Tool Call to your implementation.
|
|
295
|
-
* Validates arguments automatically.
|
|
296
|
-
*/
|
|
297
|
-
public async handleTool(name: string, args: unknown): Promise<unknown> {
|
|
298
|
-
if (!this.tools) {
|
|
299
|
-
throw new Error('No tool implementation provided to Agent constructor.');
|
|
300
|
-
}
|
|
301
|
-
return Tools.dispatch(name, args, this.tools);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/** Validates User Input (e.g. from API Request) */
|
|
305
|
-
public static validateInput(data: unknown): Schemas.AgentInput {
|
|
306
|
-
return Schemas.InputSchema.parse(data);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/** Validates LLM Output (Post-generation check) */
|
|
310
|
-
public static validateResponse(data: unknown): Schemas.AgentResponse {
|
|
311
|
-
return Schemas.ResponseSchema.parse(data);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/** Get Provider Configuration */
|
|
315
|
-
public static get config() {
|
|
316
|
-
return ModelConfig;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/** Get Tool Definitions for the LLM Provider */
|
|
320
|
-
public static get toolDefinitions() {
|
|
321
|
-
return Tools.ToolDefinitions;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/** Get JSON Schema for Structured Outputs */
|
|
325
|
-
public static get responseFormat() {
|
|
326
|
-
return Schemas.ResponseJsonSchema;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
`;
|