@porchestra/cli 1.0.0 → 1.0.2
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 +1666 -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,59 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
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
|
-
});
|
package/src/types/index.ts
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
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
|
-
}
|
package/src/utils/date.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
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
|
-
}
|
package/src/utils/errors.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
export class PorchestraError extends Error {
|
|
2
|
-
constructor(message: string, public code: string) {
|
|
3
|
-
super(message);
|
|
4
|
-
this.name = 'PorchestraError';
|
|
5
|
-
}
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export class AuthenticationError extends PorchestraError {
|
|
9
|
-
constructor(message: string, public retryable = false) {
|
|
10
|
-
super(message, 'AUTH_ERROR');
|
|
11
|
-
this.name = 'AuthenticationError';
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class NetworkError extends PorchestraError {
|
|
16
|
-
constructor(
|
|
17
|
-
message: string,
|
|
18
|
-
public statusCode?: number,
|
|
19
|
-
public retryable = true
|
|
20
|
-
) {
|
|
21
|
-
super(message, 'NETWORK_ERROR');
|
|
22
|
-
this.name = 'NetworkError';
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export class ValidationError extends PorchestraError {
|
|
27
|
-
constructor(message: string, public field?: string) {
|
|
28
|
-
super(message, 'VALIDATION_ERROR');
|
|
29
|
-
this.name = 'ValidationError';
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export class FileSystemError extends PorchestraError {
|
|
34
|
-
constructor(message: string, public path: string) {
|
|
35
|
-
super(message, 'FS_ERROR');
|
|
36
|
-
this.name = 'FileSystemError';
|
|
37
|
-
}
|
|
38
|
-
}
|
package/src/utils/logger.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import pc from 'picocolors';
|
|
2
|
-
|
|
3
|
-
export const logger = {
|
|
4
|
-
info: (msg: string) => console.log(msg),
|
|
5
|
-
success: (msg: string) => console.log(pc.green(msg)),
|
|
6
|
-
error: (msg: string) => console.error(pc.red(msg)),
|
|
7
|
-
warn: (msg: string) => console.log(pc.yellow(msg)),
|
|
8
|
-
gray: (msg: string) => console.log(pc.gray(msg)),
|
|
9
|
-
bold: (msg: string) => console.log(pc.bold(msg)),
|
|
10
|
-
cyan: (msg: string) => console.log(pc.cyan(msg)),
|
|
11
|
-
};
|
package/src/utils/path-utils.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import { promises as fs } from 'fs';
|
|
3
|
-
|
|
4
|
-
export function normalizeFolderPath(folderPath: string): string {
|
|
5
|
-
let normalized = folderPath.replace(/^\//, '');
|
|
6
|
-
normalized = normalized.replace(/\/+/g, '/');
|
|
7
|
-
normalized = normalized
|
|
8
|
-
.split('/')
|
|
9
|
-
.map(segment => sanitizePathSegment(segment))
|
|
10
|
-
.join('/');
|
|
11
|
-
normalized = normalized.replace(/\/$/, '');
|
|
12
|
-
return normalized;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function sanitizePathSegment(segment: string): string {
|
|
16
|
-
return segment
|
|
17
|
-
.replace(/[^a-zA-Z0-9-_]/g, '-')
|
|
18
|
-
.replace(/^-+|-+$/g, '')
|
|
19
|
-
.replace(/-+/g, '-');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function calculateOutputPath(
|
|
23
|
-
baseDir: string,
|
|
24
|
-
folderPath: string,
|
|
25
|
-
filename?: string
|
|
26
|
-
): string {
|
|
27
|
-
const normalized = normalizeFolderPath(folderPath);
|
|
28
|
-
const fullPath = path.join(baseDir, normalized);
|
|
29
|
-
return filename ? path.join(fullPath, filename) : fullPath;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function calculateAgentOutputPath(
|
|
33
|
-
baseDir: string,
|
|
34
|
-
folderPath: string,
|
|
35
|
-
agentName: string,
|
|
36
|
-
filename?: string
|
|
37
|
-
): string {
|
|
38
|
-
const normalizedFolder = normalizeFolderPath(folderPath);
|
|
39
|
-
const normalizedAgent = sanitizePathSegment(agentName);
|
|
40
|
-
const fullPath = path.join(baseDir, normalizedFolder, normalizedAgent);
|
|
41
|
-
return filename ? path.join(fullPath, filename) : fullPath;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export async function ensureDirectory(filePath: string): Promise<void> {
|
|
45
|
-
const dir = path.dirname(filePath);
|
|
46
|
-
await fs.mkdir(dir, { recursive: true });
|
|
47
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { promises as fs } from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import os from 'os';
|
|
5
|
-
import { ConfigManager } from '../../src/core/config/config-manager.js';
|
|
6
|
-
|
|
7
|
-
describe('ConfigManager', () => {
|
|
8
|
-
let tempDir: string;
|
|
9
|
-
let configManager: ConfigManager;
|
|
10
|
-
|
|
11
|
-
beforeEach(async () => {
|
|
12
|
-
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'porchestra-test-'));
|
|
13
|
-
process.env.PORCHESTRA_CONFIG_DIR = tempDir;
|
|
14
|
-
configManager = new ConfigManager();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
afterEach(async () => {
|
|
18
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
19
|
-
delete process.env.PORCHESTRA_CONFIG_DIR;
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should create default config when none exists', async () => {
|
|
23
|
-
const config = await configManager.load();
|
|
24
|
-
expect(config.version).toBe('1.0.0');
|
|
25
|
-
expect(config.trackedProjects).toEqual([]);
|
|
26
|
-
expect(config.api?.baseUrl).toBe('https://api.porchestra.io/v1');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should save and load config', async () => {
|
|
30
|
-
const config = await configManager.load();
|
|
31
|
-
config.auth = {
|
|
32
|
-
token: 'test-token',
|
|
33
|
-
tokenId: '550e8400-e29b-41d4-a716-446655440000',
|
|
34
|
-
expiresAt: '2025-12-31T23:59:59Z',
|
|
35
|
-
deviceName: 'Test Device'
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
await configManager.save(config);
|
|
39
|
-
|
|
40
|
-
// Create new manager to test loading from disk
|
|
41
|
-
const newManager = new ConfigManager();
|
|
42
|
-
const loaded = await newManager.load();
|
|
43
|
-
|
|
44
|
-
expect(loaded.auth?.token).toBe('test-token');
|
|
45
|
-
expect(loaded.auth?.deviceName).toBe('Test Device');
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('should update config with updater function', async () => {
|
|
49
|
-
await configManager.load();
|
|
50
|
-
|
|
51
|
-
await configManager.update((cfg) => ({
|
|
52
|
-
...cfg,
|
|
53
|
-
api: {
|
|
54
|
-
...cfg.api,
|
|
55
|
-
baseUrl: 'https://custom.api.com/v1'
|
|
56
|
-
}
|
|
57
|
-
}));
|
|
58
|
-
|
|
59
|
-
const config = await configManager.get();
|
|
60
|
-
expect(config.api?.baseUrl).toBe('https://custom.api.com/v1');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should clear config', async () => {
|
|
64
|
-
const config = await configManager.load();
|
|
65
|
-
config.auth = { token: 'test' };
|
|
66
|
-
await configManager.save(config);
|
|
67
|
-
|
|
68
|
-
await configManager.clear();
|
|
69
|
-
|
|
70
|
-
// After clear, should return default config
|
|
71
|
-
const newConfig = await configManager.load();
|
|
72
|
-
expect(newConfig.auth).toBeUndefined();
|
|
73
|
-
});
|
|
74
|
-
});
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { CliConfigSchema, TrackedProjectSchema } from '../../src/core/config/config-schema.js';
|
|
3
|
-
|
|
4
|
-
describe('config-schema', () => {
|
|
5
|
-
it('should validate a valid config', () => {
|
|
6
|
-
const validConfig = {
|
|
7
|
-
version: '1.0.0',
|
|
8
|
-
auth: {
|
|
9
|
-
token: 'test-token',
|
|
10
|
-
tokenId: '550e8400-e29b-41d4-a716-446655440000',
|
|
11
|
-
expiresAt: '2025-12-31T23:59:59Z',
|
|
12
|
-
deviceName: 'Test Device'
|
|
13
|
-
},
|
|
14
|
-
api: {
|
|
15
|
-
baseUrl: 'https://api.porchestra.io/v1',
|
|
16
|
-
skipTlsVerify: false
|
|
17
|
-
},
|
|
18
|
-
trackedProjects: [],
|
|
19
|
-
output: {
|
|
20
|
-
baseDir: './src/agents',
|
|
21
|
-
createIndexFiles: true
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const result = CliConfigSchema.safeParse(validConfig);
|
|
26
|
-
expect(result.success).toBe(true);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should apply defaults for missing fields', () => {
|
|
30
|
-
const minimalConfig = {
|
|
31
|
-
version: '1.0.0'
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const result = CliConfigSchema.parse(minimalConfig);
|
|
35
|
-
expect(result.api?.baseUrl).toBe('https://api.porchestra.io/v1');
|
|
36
|
-
expect(result.output?.baseDir).toBe('./src/agents');
|
|
37
|
-
expect(result.trackedProjects).toEqual([]);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('should reject invalid version', () => {
|
|
41
|
-
const invalidConfig = {
|
|
42
|
-
version: '2.0.0'
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const result = CliConfigSchema.safeParse(invalidConfig);
|
|
46
|
-
expect(result.success).toBe(false);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should validate tracked project schema', () => {
|
|
50
|
-
const validProject = {
|
|
51
|
-
projectId: '550e8400-e29b-41d4-a716-446655440000',
|
|
52
|
-
projectName: 'Test Project',
|
|
53
|
-
projectSlug: 'test-project',
|
|
54
|
-
selectedAt: '2025-01-30T12:00:00Z',
|
|
55
|
-
agents: []
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const result = TrackedProjectSchema.safeParse(validProject);
|
|
59
|
-
expect(result.success).toBe(true);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
normalizeFolderPath,
|
|
4
|
-
calculateOutputPath,
|
|
5
|
-
} from '../../src/utils/path-utils.js';
|
|
6
|
-
|
|
7
|
-
describe('path-utils', () => {
|
|
8
|
-
describe('normalizeFolderPath', () => {
|
|
9
|
-
it('should remove leading slash', () => {
|
|
10
|
-
expect(normalizeFolderPath('/main/x-agent/')).toBe('main/x-agent');
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('should handle paths without leading slash', () => {
|
|
14
|
-
expect(normalizeFolderPath('y-agent')).toBe('y-agent');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('should replace multiple slashes', () => {
|
|
18
|
-
expect(normalizeFolderPath('//legacy//agents//')).toBe('/legacy/agents');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('should sanitize special characters', () => {
|
|
22
|
-
expect(normalizeFolderPath('My Agent (v2)')).toBe('My-Agent-v2');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('should handle empty path', () => {
|
|
26
|
-
expect(normalizeFolderPath('/')).toBe('');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should prevent directory traversal', () => {
|
|
30
|
-
expect(normalizeFolderPath('../malicious')).toBe('/malicious');
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe('calculateOutputPath', () => {
|
|
35
|
-
it('should calculate correct output path', () => {
|
|
36
|
-
const result = calculateOutputPath(
|
|
37
|
-
'./src/agents',
|
|
38
|
-
'main/x-agent',
|
|
39
|
-
'tool-schemas.ts'
|
|
40
|
-
);
|
|
41
|
-
expect(result).toMatch(/src[\\/]agents[\\/]main[\\/]x-agent[\\/]tool-schemas\.ts/);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should handle empty folder path', () => {
|
|
45
|
-
const result = calculateOutputPath(
|
|
46
|
-
'./src/agents',
|
|
47
|
-
'/',
|
|
48
|
-
'tool-schemas.ts'
|
|
49
|
-
);
|
|
50
|
-
expect(result).toMatch(/src[\\/]agents[\\/]tool-schemas\.ts$/);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
});
|