@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.
Files changed (37) hide show
  1. package/README.md +625 -0
  2. package/bin/porchestra.js +2 -0
  3. package/package.json +51 -0
  4. package/src/agents/testPrompt/ast.json +71 -0
  5. package/src/agents/testPrompt/config.ts +18 -0
  6. package/src/agents/testPrompt/index.ts +64 -0
  7. package/src/agents/testPrompt/schemas.ts +45 -0
  8. package/src/agents/testPrompt/tools.ts +88 -0
  9. package/src/commands/agents.ts +173 -0
  10. package/src/commands/config.ts +97 -0
  11. package/src/commands/explore.ts +160 -0
  12. package/src/commands/login.ts +101 -0
  13. package/src/commands/logout.ts +52 -0
  14. package/src/commands/pull.ts +220 -0
  15. package/src/commands/status.ts +78 -0
  16. package/src/commands/whoami.ts +56 -0
  17. package/src/core/api/client.ts +133 -0
  18. package/src/core/auth/auth-service.ts +176 -0
  19. package/src/core/auth/token-manager.ts +47 -0
  20. package/src/core/config/config-manager.ts +107 -0
  21. package/src/core/config/config-schema.ts +56 -0
  22. package/src/core/config/project-tracker.ts +158 -0
  23. package/src/core/generators/code-generator.ts +329 -0
  24. package/src/core/generators/schema-generator.ts +59 -0
  25. package/src/index.ts +85 -0
  26. package/src/types/index.ts +214 -0
  27. package/src/utils/date.ts +23 -0
  28. package/src/utils/errors.ts +38 -0
  29. package/src/utils/logger.ts +11 -0
  30. package/src/utils/path-utils.ts +47 -0
  31. package/tests/unit/config-manager.test.ts +74 -0
  32. package/tests/unit/config-schema.test.ts +61 -0
  33. package/tests/unit/path-utils.test.ts +53 -0
  34. package/tests/unit/schema-generator.test.ts +82 -0
  35. package/tsconfig.json +30 -0
  36. package/tsup.config.ts +19 -0
  37. 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
+ }