@porchestra/cli 1.0.0
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/README.md +625 -0
- package/bin/porchestra.js +2 -0
- package/package.json +51 -0
- package/src/agents/testPrompt/ast.json +71 -0
- package/src/agents/testPrompt/config.ts +18 -0
- package/src/agents/testPrompt/index.ts +64 -0
- package/src/agents/testPrompt/schemas.ts +45 -0
- package/src/agents/testPrompt/tools.ts +88 -0
- package/src/commands/agents.ts +173 -0
- package/src/commands/config.ts +97 -0
- package/src/commands/explore.ts +160 -0
- package/src/commands/login.ts +101 -0
- package/src/commands/logout.ts +52 -0
- package/src/commands/pull.ts +220 -0
- package/src/commands/status.ts +78 -0
- package/src/commands/whoami.ts +56 -0
- package/src/core/api/client.ts +133 -0
- package/src/core/auth/auth-service.ts +176 -0
- package/src/core/auth/token-manager.ts +47 -0
- package/src/core/config/config-manager.ts +107 -0
- package/src/core/config/config-schema.ts +56 -0
- package/src/core/config/project-tracker.ts +158 -0
- package/src/core/generators/code-generator.ts +329 -0
- package/src/core/generators/schema-generator.ts +59 -0
- package/src/index.ts +85 -0
- package/src/types/index.ts +214 -0
- package/src/utils/date.ts +23 -0
- package/src/utils/errors.ts +38 -0
- package/src/utils/logger.ts +11 -0
- package/src/utils/path-utils.ts +47 -0
- package/tests/unit/config-manager.test.ts +74 -0
- package/tests/unit/config-schema.test.ts +61 -0
- package/tests/unit/path-utils.test.ts +53 -0
- package/tests/unit/schema-generator.test.ts +82 -0
- package/tsconfig.json +30 -0
- package/tsup.config.ts +19 -0
- package/vitest.config.ts +20 -0
|
@@ -0,0 +1,329 @@
|
|
|
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
|
+
`;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export function jsonSchemaToZod(schema: any): z.ZodTypeAny {
|
|
4
|
+
switch (schema.type) {
|
|
5
|
+
case 'string': {
|
|
6
|
+
let stringSchema = z.string();
|
|
7
|
+
if (schema.minLength) stringSchema = stringSchema.min(schema.minLength);
|
|
8
|
+
if (schema.maxLength) stringSchema = stringSchema.max(schema.maxLength);
|
|
9
|
+
if (schema.pattern) stringSchema = stringSchema.regex(new RegExp(schema.pattern));
|
|
10
|
+
if (schema.enum) return z.enum(schema.enum);
|
|
11
|
+
return schema.nullable ? stringSchema.nullable() : stringSchema;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
case 'number':
|
|
15
|
+
case 'integer': {
|
|
16
|
+
let numberSchema = schema.type === 'integer' ? z.number().int() : z.number();
|
|
17
|
+
if (schema.minimum !== undefined) numberSchema = numberSchema.min(schema.minimum);
|
|
18
|
+
if (schema.maximum !== undefined) numberSchema = numberSchema.max(schema.maximum);
|
|
19
|
+
return schema.nullable ? numberSchema.nullable() : numberSchema;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
case 'boolean': {
|
|
23
|
+
const boolSchema = z.boolean();
|
|
24
|
+
return schema.nullable ? boolSchema.nullable() : boolSchema;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
case 'array': {
|
|
28
|
+
const itemSchema = jsonSchemaToZod(schema.items);
|
|
29
|
+
let arraySchema = z.array(itemSchema);
|
|
30
|
+
if (schema.minItems) arraySchema = arraySchema.min(schema.minItems);
|
|
31
|
+
if (schema.maxItems) arraySchema = arraySchema.max(schema.maxItems);
|
|
32
|
+
return schema.nullable ? arraySchema.nullable() : arraySchema;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
case 'object': {
|
|
36
|
+
const shape: Record<string, z.ZodTypeAny> = {};
|
|
37
|
+
const required = new Set(schema.required || []);
|
|
38
|
+
|
|
39
|
+
for (const [key, propSchema] of Object.entries(schema.properties || {})) {
|
|
40
|
+
let fieldSchema = jsonSchemaToZod(propSchema as any);
|
|
41
|
+
if (!required.has(key)) {
|
|
42
|
+
fieldSchema = fieldSchema.optional();
|
|
43
|
+
}
|
|
44
|
+
shape[key] = fieldSchema;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let objectSchema = z.object(shape);
|
|
48
|
+
// Note: strict() is not applied to keep the schema flexible
|
|
49
|
+
return schema.nullable ? objectSchema.nullable() : objectSchema;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
default:
|
|
53
|
+
if (schema.anyOf || schema.oneOf) {
|
|
54
|
+
const schemas = (schema.anyOf || schema.oneOf).map(jsonSchemaToZod);
|
|
55
|
+
return z.union([schemas[0], schemas[1], ...schemas.slice(2)]);
|
|
56
|
+
}
|
|
57
|
+
return z.any();
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import { ConfigManager } from './core/config/config-manager.js';
|
|
5
|
+
import { AuthService } from './core/auth/auth-service.js';
|
|
6
|
+
import { TokenManager } from './core/auth/token-manager.js';
|
|
7
|
+
import { ApiClient } from './core/api/client.js';
|
|
8
|
+
import { CodeGenerator } from './core/generators/code-generator.js';
|
|
9
|
+
import { createLoginCommand } from './commands/login.js';
|
|
10
|
+
import { createLogoutCommand } from './commands/logout.js';
|
|
11
|
+
import { createWhoamiCommand } from './commands/whoami.js';
|
|
12
|
+
import { createExploreCommand } from './commands/explore.js';
|
|
13
|
+
import { createPullCommand } from './commands/pull.js';
|
|
14
|
+
import { createConfigCommand } from './commands/config.js';
|
|
15
|
+
import { createStatusCommand } from './commands/status.js';
|
|
16
|
+
import { createAgentsCommand } from './commands/agents.js';
|
|
17
|
+
import { PorchestraError } from './utils/errors.js';
|
|
18
|
+
|
|
19
|
+
const packageJson = { version: '1.0.0' };
|
|
20
|
+
|
|
21
|
+
// Global error handlers
|
|
22
|
+
process.on('unhandledRejection', (error) => {
|
|
23
|
+
console.error(pc.red('\n✗ Unexpected error:'));
|
|
24
|
+
console.error(error);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
process.on('uncaughtException', (error) => {
|
|
29
|
+
console.error(pc.red('\n✗ Fatal error:'));
|
|
30
|
+
console.error(error);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
async function main() {
|
|
35
|
+
// Initialize core services
|
|
36
|
+
const configManager = new ConfigManager();
|
|
37
|
+
const authService = new AuthService(configManager);
|
|
38
|
+
// TokenManager is available for future token operations
|
|
39
|
+
new TokenManager(configManager);
|
|
40
|
+
const apiClient = new ApiClient(configManager);
|
|
41
|
+
const codeGenerator = new CodeGenerator();
|
|
42
|
+
|
|
43
|
+
// Check token expiration and refresh if needed
|
|
44
|
+
await authService.checkAndRefreshTokenIfNeeded();
|
|
45
|
+
|
|
46
|
+
// Create CLI program
|
|
47
|
+
const program = new Command()
|
|
48
|
+
.name('porchestra')
|
|
49
|
+
.description('CLI for Porchestra - Generate LLM tool handlers')
|
|
50
|
+
.version(packageJson.version)
|
|
51
|
+
.configureOutput({
|
|
52
|
+
writeErr: (str) => process.stderr.write(str),
|
|
53
|
+
outputError: (str, write) => write(pc.red(str))
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Add global options
|
|
57
|
+
program
|
|
58
|
+
.option('--api-url <url>', 'Override API URL')
|
|
59
|
+
.option('--config-dir <dir>', 'Override config directory');
|
|
60
|
+
|
|
61
|
+
// Register commands
|
|
62
|
+
program.addCommand(createLoginCommand(configManager, authService));
|
|
63
|
+
program.addCommand(createLogoutCommand(configManager, authService));
|
|
64
|
+
program.addCommand(createWhoamiCommand(configManager, authService));
|
|
65
|
+
program.addCommand(createExploreCommand(configManager, apiClient));
|
|
66
|
+
program.addCommand(createPullCommand(configManager, apiClient, codeGenerator));
|
|
67
|
+
program.addCommand(createConfigCommand(configManager));
|
|
68
|
+
program.addCommand(createStatusCommand(configManager));
|
|
69
|
+
program.addCommand(createAgentsCommand(configManager));
|
|
70
|
+
|
|
71
|
+
// Parse arguments
|
|
72
|
+
await program.parseAsync(process.argv);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
main().catch((error) => {
|
|
76
|
+
if (error instanceof PorchestraError) {
|
|
77
|
+
console.error(pc.red(`\n✗ ${error.message}`));
|
|
78
|
+
if (process.env.DEBUG) {
|
|
79
|
+
console.error(pc.gray(error.stack));
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
console.error(pc.red(`\n✗ Unexpected error: ${(error as Error).message}`));
|
|
83
|
+
}
|
|
84
|
+
process.exit(1);
|
|
85
|
+
});
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// Authentication Types
|
|
2
|
+
export interface ApiResponse<T> {
|
|
3
|
+
statusCode: number;
|
|
4
|
+
message: string;
|
|
5
|
+
data: T;
|
|
6
|
+
timestamp: string;
|
|
7
|
+
path: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CliTokenRequest {
|
|
11
|
+
deviceName?: string;
|
|
12
|
+
deviceInfo?: {
|
|
13
|
+
os: string;
|
|
14
|
+
version: string;
|
|
15
|
+
cliVersion: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CliTokenResponse {
|
|
20
|
+
token: string;
|
|
21
|
+
tokenId: string;
|
|
22
|
+
expiresAt: string;
|
|
23
|
+
issuedAt: string;
|
|
24
|
+
deviceName: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CliTokenRefreshRequest {
|
|
28
|
+
tokenId: string;
|
|
29
|
+
currentToken: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CliTokenRefreshResponse {
|
|
33
|
+
token: string;
|
|
34
|
+
tokenId: string;
|
|
35
|
+
expiresAt: string;
|
|
36
|
+
refreshedAt: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CliTokenRevokeRequest {
|
|
40
|
+
revokeAll?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface CliTokenRevokeResponse {
|
|
44
|
+
revoked: boolean;
|
|
45
|
+
revokedCount?: number;
|
|
46
|
+
revokedAt: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface CliTokensListResponse {
|
|
50
|
+
tokens: Array<{
|
|
51
|
+
tokenId: string;
|
|
52
|
+
deviceName: string;
|
|
53
|
+
deviceInfo: {
|
|
54
|
+
os: string;
|
|
55
|
+
version: string;
|
|
56
|
+
cliVersion: string;
|
|
57
|
+
};
|
|
58
|
+
issuedAt: string;
|
|
59
|
+
expiresAt: string;
|
|
60
|
+
lastUsedAt: string | null;
|
|
61
|
+
lastUsedIp: string | null;
|
|
62
|
+
isCurrent: boolean;
|
|
63
|
+
}>;
|
|
64
|
+
total: number;
|
|
65
|
+
maxAllowed: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Project Types
|
|
69
|
+
export interface ProjectsBriefResponse {
|
|
70
|
+
projects: Array<{
|
|
71
|
+
id: string;
|
|
72
|
+
name: string;
|
|
73
|
+
slug: string;
|
|
74
|
+
description: string | null;
|
|
75
|
+
agentCount: number;
|
|
76
|
+
environments: Array<{
|
|
77
|
+
name: 'PRODUCTION' | 'STAGING' | 'DEVELOPMENT';
|
|
78
|
+
hasPublishedVersion: boolean;
|
|
79
|
+
}>;
|
|
80
|
+
lastModifiedAt: string;
|
|
81
|
+
}>;
|
|
82
|
+
total: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface AgentsListQuery {
|
|
86
|
+
environment?: 'PRODUCTION' | 'STAGING' | 'DEVELOPMENT';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface AgentsListResponse {
|
|
90
|
+
project: {
|
|
91
|
+
id: string;
|
|
92
|
+
name: string;
|
|
93
|
+
slug: string;
|
|
94
|
+
};
|
|
95
|
+
environment: 'PRODUCTION' | 'STAGING' | 'DEVELOPMENT';
|
|
96
|
+
agents: Array<{
|
|
97
|
+
id: string;
|
|
98
|
+
name: string;
|
|
99
|
+
slug: string;
|
|
100
|
+
folderPath: string;
|
|
101
|
+
description: string | null;
|
|
102
|
+
version: string;
|
|
103
|
+
toolCount: number;
|
|
104
|
+
lastUpdatedAt: string;
|
|
105
|
+
isPublished: boolean;
|
|
106
|
+
}>;
|
|
107
|
+
versionResolution: {
|
|
108
|
+
source: 'PRODUCTION' | 'STAGING' | 'DEVELOPMENT';
|
|
109
|
+
note: string;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface AgentDetailQuery {
|
|
114
|
+
environment?: 'PRODUCTION' | 'STAGING' | 'DEVELOPMENT';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface AgentDetailResponse {
|
|
118
|
+
agent: {
|
|
119
|
+
id: string;
|
|
120
|
+
name: string;
|
|
121
|
+
slug: string;
|
|
122
|
+
folderPath: string;
|
|
123
|
+
description: string | null;
|
|
124
|
+
instructions: string | null;
|
|
125
|
+
model: string;
|
|
126
|
+
temperature: number;
|
|
127
|
+
version: string;
|
|
128
|
+
versionId: string;
|
|
129
|
+
environment: 'PRODUCTION' | 'STAGING' | 'DEVELOPMENT';
|
|
130
|
+
createdAt: string;
|
|
131
|
+
updatedAt: string;
|
|
132
|
+
publishedAt: string | null;
|
|
133
|
+
};
|
|
134
|
+
versionResolution: {
|
|
135
|
+
source: 'PRODUCTION' | 'STAGING' | 'DEVELOPMENT';
|
|
136
|
+
note: string;
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Tools Types
|
|
141
|
+
export interface AgentToolsQuery {
|
|
142
|
+
environment?: 'PRODUCTION' | 'STAGING' | 'DEVELOPMENT';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface AgentToolsResponse {
|
|
146
|
+
agent: {
|
|
147
|
+
id: string;
|
|
148
|
+
name: string;
|
|
149
|
+
folderPath: string;
|
|
150
|
+
version: string;
|
|
151
|
+
};
|
|
152
|
+
tools?: Array<{
|
|
153
|
+
id: string;
|
|
154
|
+
name: string;
|
|
155
|
+
description: string;
|
|
156
|
+
parameters: object;
|
|
157
|
+
returns: object | null;
|
|
158
|
+
isBuiltin: boolean;
|
|
159
|
+
builtinType?: string;
|
|
160
|
+
}>;
|
|
161
|
+
// Legacy/alternate API shape where tools are provided under `toolset`
|
|
162
|
+
toolset?: Array<{
|
|
163
|
+
name: string;
|
|
164
|
+
description?: string;
|
|
165
|
+
parameters?: object;
|
|
166
|
+
returns?: object | null;
|
|
167
|
+
isBuiltin?: boolean;
|
|
168
|
+
builtinType?: string;
|
|
169
|
+
id?: string;
|
|
170
|
+
}>;
|
|
171
|
+
components: {
|
|
172
|
+
ast: { id: string; content: unknown } | null;
|
|
173
|
+
modelConfig: { id: string; content: unknown } | null;
|
|
174
|
+
inputSchema: { id: string; content: unknown } | null;
|
|
175
|
+
variableSchema: { id: string; content: unknown } | null;
|
|
176
|
+
responseSchema: { id: string; content: unknown } | null;
|
|
177
|
+
toolConfig: { id: string; content: unknown } | null;
|
|
178
|
+
};
|
|
179
|
+
total: number;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// User Types
|
|
183
|
+
export interface UserInfo {
|
|
184
|
+
email: string;
|
|
185
|
+
name: string;
|
|
186
|
+
organization: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// CLI Config Types
|
|
190
|
+
export interface CliConfigResponse {
|
|
191
|
+
api: {
|
|
192
|
+
version: string;
|
|
193
|
+
minCliVersion: string;
|
|
194
|
+
recommendedCliVersion: string;
|
|
195
|
+
deprecatedCliVersions: string[];
|
|
196
|
+
};
|
|
197
|
+
features: {
|
|
198
|
+
codeGeneration: boolean;
|
|
199
|
+
multiAgent: boolean;
|
|
200
|
+
onPremise: boolean;
|
|
201
|
+
customPaths: boolean;
|
|
202
|
+
};
|
|
203
|
+
limits: {
|
|
204
|
+
maxTokensPerUser: number;
|
|
205
|
+
tokenExpiryDays: number;
|
|
206
|
+
exportCacheHours: number;
|
|
207
|
+
maxToolsPerAgent: number;
|
|
208
|
+
};
|
|
209
|
+
endpoints: {
|
|
210
|
+
webApp: string;
|
|
211
|
+
documentation: string;
|
|
212
|
+
support: string;
|
|
213
|
+
};
|
|
214
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function formatDistanceToNow(date: Date): string {
|
|
2
|
+
const now = new Date();
|
|
3
|
+
const diffMs = now.getTime() - date.getTime();
|
|
4
|
+
const diffSecs = Math.floor(diffMs / 1000);
|
|
5
|
+
const diffMins = Math.floor(diffSecs / 60);
|
|
6
|
+
const diffHours = Math.floor(diffMins / 60);
|
|
7
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
8
|
+
|
|
9
|
+
if (diffSecs < 60) return 'just now';
|
|
10
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
11
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
12
|
+
if (diffDays < 30) return `${diffDays}d ago`;
|
|
13
|
+
return date.toISOString().split('T')[0];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function formatDate(dateStr: string): string {
|
|
17
|
+
const date = new Date(dateStr);
|
|
18
|
+
return date.toLocaleDateString('en-US', {
|
|
19
|
+
year: 'numeric',
|
|
20
|
+
month: 'short',
|
|
21
|
+
day: 'numeric',
|
|
22
|
+
});
|
|
23
|
+
}
|