@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,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"root": {
|
|
3
|
+
"id": "EvcRFNShH3",
|
|
4
|
+
"type": "block",
|
|
5
|
+
"children": [
|
|
6
|
+
{
|
|
7
|
+
"id": "QgKHml42sL",
|
|
8
|
+
"type": "text",
|
|
9
|
+
"content": "system prompt test"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"id": "KRvYYUqy7J",
|
|
13
|
+
"type": "block",
|
|
14
|
+
"children": [
|
|
15
|
+
{
|
|
16
|
+
"id": "YlHnTwSkEi",
|
|
17
|
+
"else": {
|
|
18
|
+
"id": "i2pRTTZQlM",
|
|
19
|
+
"type": "block",
|
|
20
|
+
"children": [
|
|
21
|
+
{
|
|
22
|
+
"id": "Bd9VwLpfQO",
|
|
23
|
+
"type": "text",
|
|
24
|
+
"content": "val 1 not provided"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"then": {
|
|
29
|
+
"id": "Z30x3K8DQG",
|
|
30
|
+
"type": "block",
|
|
31
|
+
"children": [
|
|
32
|
+
{
|
|
33
|
+
"id": "d1v8PEValw",
|
|
34
|
+
"type": "text",
|
|
35
|
+
"content": "val 1 provided"
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"type": "conditional",
|
|
40
|
+
"condition": {
|
|
41
|
+
"op": "eq",
|
|
42
|
+
"left": {
|
|
43
|
+
"path": "var",
|
|
44
|
+
"type": "variable"
|
|
45
|
+
},
|
|
46
|
+
"right": {
|
|
47
|
+
"type": "constant",
|
|
48
|
+
"value": "val1"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
"version": "2.1.0",
|
|
57
|
+
"variables": [
|
|
58
|
+
{
|
|
59
|
+
"id": "mdMpwnrBKG",
|
|
60
|
+
"path": "var",
|
|
61
|
+
"type": "enum",
|
|
62
|
+
"validation": {
|
|
63
|
+
"options": [
|
|
64
|
+
"val1",
|
|
65
|
+
"val2"
|
|
66
|
+
],
|
|
67
|
+
"required": true
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-generated model config for testPrompt
|
|
3
|
+
* Generated: 2026-02-01T11:57:10.473Z
|
|
4
|
+
* Version: 1.0.0
|
|
5
|
+
* DO NOT MODIFY - This file is auto-generated by Porchestra CLI
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const ModelConfig = {
|
|
9
|
+
"model": "gemini-3-flash-preview",
|
|
10
|
+
"provider": "GoogleGenAI",
|
|
11
|
+
"overrides": {},
|
|
12
|
+
"parameters": {
|
|
13
|
+
"thinking_config": {
|
|
14
|
+
"thinkingLevel": "LOW"
|
|
15
|
+
},
|
|
16
|
+
"response_mime_type": "application/json"
|
|
17
|
+
}
|
|
18
|
+
} as const;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-generated Agent class for testPrompt
|
|
3
|
+
* Generated: 2026-02-01T11:57:10.473Z
|
|
4
|
+
* Version: 1.0.0
|
|
5
|
+
* DO NOT MODIFY - This file is auto-generated by Porchestra CLI
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { PorchestraRuntime } from '@porchestra/core';
|
|
9
|
+
import rawAst from './ast.json';
|
|
10
|
+
import { ModelConfig } from './config.js';
|
|
11
|
+
import * as Schemas from './schemas.js';
|
|
12
|
+
import * as Tools from './tools.js';
|
|
13
|
+
|
|
14
|
+
export class TestPromptAgent {
|
|
15
|
+
/**
|
|
16
|
+
* @param tools - The implementation of the tool logic. Required if the agent uses tools.
|
|
17
|
+
*/
|
|
18
|
+
constructor(private readonly tools?: Tools.ToolImplementation) {}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Renders the System Prompt.
|
|
22
|
+
* Throws ZodError if variables are invalid.
|
|
23
|
+
*/
|
|
24
|
+
public renderPrompt(variables: Schemas.AgentVariables): string {
|
|
25
|
+
const safeVars = Schemas.VariableSchema.parse(variables);
|
|
26
|
+
return PorchestraRuntime.render(rawAst as any, safeVars);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Routes an LLM Tool Call to your implementation.
|
|
31
|
+
* Validates arguments automatically.
|
|
32
|
+
*/
|
|
33
|
+
public async handleTool(name: string, args: unknown): Promise<unknown> {
|
|
34
|
+
if (!this.tools) {
|
|
35
|
+
throw new Error('No tool implementation provided to Agent constructor.');
|
|
36
|
+
}
|
|
37
|
+
return Tools.dispatch(name, args, this.tools);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Validates User Input (e.g. from API Request) */
|
|
41
|
+
public static validateInput(data: unknown): Schemas.AgentInput {
|
|
42
|
+
return Schemas.InputSchema.parse(data);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Validates LLM Output (Post-generation check) */
|
|
46
|
+
public static validateResponse(data: unknown): Schemas.AgentResponse {
|
|
47
|
+
return Schemas.ResponseSchema.parse(data);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Get Provider Configuration */
|
|
51
|
+
public static get config() {
|
|
52
|
+
return ModelConfig;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Get Tool Definitions for the LLM Provider */
|
|
56
|
+
public static get toolDefinitions() {
|
|
57
|
+
return Tools.ToolDefinitions;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Get JSON Schema for Structured Outputs */
|
|
61
|
+
public static get responseFormat() {
|
|
62
|
+
return Schemas.ResponseJsonSchema;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-generated Zod schemas for testPrompt
|
|
3
|
+
* Generated: 2026-02-01T11:57:10.473Z
|
|
4
|
+
* Version: 1.0.0
|
|
5
|
+
* DO NOT MODIFY - This file is auto-generated by Porchestra CLI
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
|
|
10
|
+
// 1. System Prompt Variables
|
|
11
|
+
export const VariableSchema = z.any();
|
|
12
|
+
export type AgentVariables = z.infer<typeof VariableSchema>;
|
|
13
|
+
|
|
14
|
+
// 2. User Input
|
|
15
|
+
export const InputSchema = z.object({
|
|
16
|
+
"user-age": z.number().min(18)
|
|
17
|
+
});
|
|
18
|
+
export type AgentInput = z.infer<typeof InputSchema>;
|
|
19
|
+
|
|
20
|
+
// 3. Model Response
|
|
21
|
+
export const ResponseSchema = z.object({
|
|
22
|
+
"is-allowed": z.any()
|
|
23
|
+
});
|
|
24
|
+
export type AgentResponse = z.infer<typeof ResponseSchema>;
|
|
25
|
+
|
|
26
|
+
// 4. Raw JSON Schema (for LLM Provider 'response_format')
|
|
27
|
+
export const ResponseJsonSchema = {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"required": [
|
|
30
|
+
"is-allowed"
|
|
31
|
+
],
|
|
32
|
+
"properties": {
|
|
33
|
+
"is-allowed": {
|
|
34
|
+
"enum": [
|
|
35
|
+
"yes",
|
|
36
|
+
"no"
|
|
37
|
+
],
|
|
38
|
+
"type": "enum",
|
|
39
|
+
"description": ""
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"propertyOrdering": [
|
|
43
|
+
"is-allowed"
|
|
44
|
+
]
|
|
45
|
+
} as const;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-generated tools for testPrompt
|
|
3
|
+
* Generated: 2026-02-01T11:57:10.473Z
|
|
4
|
+
* Version: 1.0.0
|
|
5
|
+
* DO NOT MODIFY - This file is auto-generated by Porchestra CLI
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
|
|
10
|
+
// Schema for test-tool-1
|
|
11
|
+
export const TestTool1Params = z.object({
|
|
12
|
+
"test-param-1": z.string(),
|
|
13
|
+
"test-param-2": z.number().int()
|
|
14
|
+
});
|
|
15
|
+
export type TestTool1ParamsType = z.infer<typeof TestTool1Params>;
|
|
16
|
+
|
|
17
|
+
// Schema for test-tool-2
|
|
18
|
+
export const TestTool2Params = z.object({
|
|
19
|
+
"test-param-1": z.string()
|
|
20
|
+
});
|
|
21
|
+
export type TestTool2ParamsType = z.infer<typeof TestTool2Params>;
|
|
22
|
+
|
|
23
|
+
// The Interface the user MUST implement
|
|
24
|
+
export interface ToolImplementation {
|
|
25
|
+
testTool1: (args: TestTool1ParamsType) => Promise<unknown>;
|
|
26
|
+
testTool2: (args: TestTool2ParamsType) => Promise<unknown>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Raw Tool Definitions for provider SDKs
|
|
30
|
+
export const ToolDefinitions = [
|
|
31
|
+
{
|
|
32
|
+
name: 'test-tool-1',
|
|
33
|
+
description: "test tool",
|
|
34
|
+
parameters: {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"required": [
|
|
37
|
+
"test-param-1",
|
|
38
|
+
"test-param-2"
|
|
39
|
+
],
|
|
40
|
+
"properties": {
|
|
41
|
+
"test-param-1": {
|
|
42
|
+
"type": "string"
|
|
43
|
+
},
|
|
44
|
+
"test-param-2": {
|
|
45
|
+
"type": "integer"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"propertyOrdering": [
|
|
49
|
+
"test-param-1",
|
|
50
|
+
"test-param-2"
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'test-tool-2',
|
|
56
|
+
description: "test tool 2",
|
|
57
|
+
parameters: {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"required": [
|
|
60
|
+
"test-param-1"
|
|
61
|
+
],
|
|
62
|
+
"properties": {
|
|
63
|
+
"test-param-1": {
|
|
64
|
+
"type": "string"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"propertyOrdering": [
|
|
68
|
+
"test-param-1"
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
] as const;
|
|
73
|
+
|
|
74
|
+
// Dispatcher with validation
|
|
75
|
+
export async function dispatch(
|
|
76
|
+
name: string,
|
|
77
|
+
args: unknown,
|
|
78
|
+
impl: ToolImplementation
|
|
79
|
+
): Promise<unknown> {
|
|
80
|
+
switch (name) {
|
|
81
|
+
case 'test-tool-1':
|
|
82
|
+
return await impl.testTool1(TestTool1Params.parse(args));
|
|
83
|
+
case 'test-tool-2':
|
|
84
|
+
return await impl.testTool2(TestTool2Params.parse(args));
|
|
85
|
+
default:
|
|
86
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { select, confirm } from '@inquirer/prompts';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import { ConfigManager } from '../core/config/config-manager.js';
|
|
5
|
+
import { ProjectTracker } from '../core/config/project-tracker.js';
|
|
6
|
+
import { formatDistanceToNow } from '../utils/date.js';
|
|
7
|
+
|
|
8
|
+
interface ListOptions {
|
|
9
|
+
project?: string;
|
|
10
|
+
json?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface RemoveOptions {
|
|
14
|
+
project?: string;
|
|
15
|
+
force?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createAgentsCommand(configManager: ConfigManager): Command {
|
|
19
|
+
const projectTracker = new ProjectTracker(configManager);
|
|
20
|
+
|
|
21
|
+
const listAction = async (options: ListOptions = {}) => {
|
|
22
|
+
let projects = await projectTracker.getTrackedProjects();
|
|
23
|
+
|
|
24
|
+
if (options.project) {
|
|
25
|
+
projects = projects.filter(
|
|
26
|
+
project => project.projectId === options.project || project.projectSlug === options.project
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (projects.length === 0) {
|
|
30
|
+
console.log(pc.red(`No tracked project found for "${options.project}"`));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (options.json) {
|
|
36
|
+
console.log(JSON.stringify(projects, null, 2));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(pc.bold('\n📁 Tracked Agents\n'));
|
|
41
|
+
|
|
42
|
+
if (projects.length === 0) {
|
|
43
|
+
console.log(pc.gray(' No agents tracked. Run `porchestra explore` to add agents.'));
|
|
44
|
+
console.log();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let totalAgents = 0;
|
|
49
|
+
|
|
50
|
+
projects.forEach((project) => {
|
|
51
|
+
totalAgents += project.agents.length;
|
|
52
|
+
console.log(pc.bold(` ${project.projectName}`));
|
|
53
|
+
|
|
54
|
+
project.agents.forEach((agent) => {
|
|
55
|
+
const lastPulled = agent.lastPulledAt
|
|
56
|
+
? formatDistanceToNow(new Date(agent.lastPulledAt))
|
|
57
|
+
: 'pending';
|
|
58
|
+
const version = agent.lastPulledVersion ? ` @ ${agent.lastPulledVersion}` : '';
|
|
59
|
+
|
|
60
|
+
console.log(` └─ ${pc.cyan(agent.agentName)}${version ? pc.gray(version) : ''}`);
|
|
61
|
+
console.log(pc.gray(` ${agent.folderPath} (${lastPulled})`));
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
console.log();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
console.log(pc.gray(`Total: ${projects.length} project(s), ${totalAgents} agent(s)\n`));
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const command = new Command('agents')
|
|
71
|
+
.description('Manage tracked agents')
|
|
72
|
+
.action(async () => listAction());
|
|
73
|
+
|
|
74
|
+
command
|
|
75
|
+
.command('list')
|
|
76
|
+
.description('List tracked agents')
|
|
77
|
+
.option('-p, --project <id>', 'Filter by project id or slug')
|
|
78
|
+
.option('--json', 'Output raw JSON for scripting')
|
|
79
|
+
.action(listAction);
|
|
80
|
+
|
|
81
|
+
command
|
|
82
|
+
.command('remove [agent]')
|
|
83
|
+
.description('Stop tracking a specific agent')
|
|
84
|
+
.option('-p, --project <id>', 'Project id or slug (disambiguates agents with the same slug)')
|
|
85
|
+
.option('-f, --force', 'Skip confirmation prompt')
|
|
86
|
+
.action(async (agentArg: string | undefined, options: RemoveOptions) => {
|
|
87
|
+
let projects = await projectTracker.getTrackedProjects();
|
|
88
|
+
|
|
89
|
+
if (projects.length === 0) {
|
|
90
|
+
console.log(pc.gray('No agents tracked. Run `porchestra explore` first.'));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (options.project) {
|
|
95
|
+
projects = projects.filter(
|
|
96
|
+
project => project.projectId === options.project || project.projectSlug === options.project
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (projects.length === 0) {
|
|
100
|
+
console.log(pc.red(`No tracked project found for "${options.project}"`));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const candidates = projects.flatMap((project) =>
|
|
106
|
+
project.agents.map((agent) => ({
|
|
107
|
+
project,
|
|
108
|
+
agent,
|
|
109
|
+
value: `${project.projectId}:${agent.agentId}`,
|
|
110
|
+
}))
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
let filtered = candidates;
|
|
114
|
+
|
|
115
|
+
if (agentArg) {
|
|
116
|
+
filtered = candidates.filter(({ agent }) =>
|
|
117
|
+
agent.agentId === agentArg || agent.agentSlug === agentArg
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (filtered.length === 0) {
|
|
122
|
+
const message = agentArg
|
|
123
|
+
? `No tracked agent found for "${agentArg}".`
|
|
124
|
+
: 'No tracked agents found.';
|
|
125
|
+
console.log(pc.red(message));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let target = filtered[0];
|
|
130
|
+
|
|
131
|
+
if (filtered.length > 1) {
|
|
132
|
+
const choice = await select({
|
|
133
|
+
message: 'Select an agent to stop tracking:',
|
|
134
|
+
choices: filtered.map(({ project, agent, value }) => ({
|
|
135
|
+
name: `${agent.agentName} ${pc.gray(`(${project.projectName}) ${agent.folderPath}`)}`,
|
|
136
|
+
value,
|
|
137
|
+
})),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
target = filtered.find(candidate => candidate.value === choice)!;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!options.force) {
|
|
144
|
+
const confirmed = await confirm({
|
|
145
|
+
message: `Remove ${target.agent.agentName} from ${target.project.projectName}?`,
|
|
146
|
+
default: false,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (!confirmed) return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const removed = await projectTracker.untrackAgent(
|
|
153
|
+
target.project.projectId,
|
|
154
|
+
target.agent.agentId
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
if (!removed) {
|
|
158
|
+
console.log(pc.red('Agent was not removed (not found).'));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const projectRemoved = target.project.agents.length === 1;
|
|
163
|
+
|
|
164
|
+
console.log(pc.green(`✓ Stopped tracking ${target.agent.agentName}`));
|
|
165
|
+
console.log(pc.gray(` Project: ${target.project.projectName}`));
|
|
166
|
+
|
|
167
|
+
if (projectRemoved) {
|
|
168
|
+
console.log(pc.gray(' Project removed from tracking because it has no remaining agents.'));
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return command;
|
|
173
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { ConfigManager } from '../core/config/config-manager.js';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import { confirm } from '@inquirer/prompts';
|
|
5
|
+
|
|
6
|
+
export function createConfigCommand(configManager: ConfigManager): Command {
|
|
7
|
+
const config = new Command('config')
|
|
8
|
+
.description('Manage CLI configuration');
|
|
9
|
+
|
|
10
|
+
config
|
|
11
|
+
.command('get <key>')
|
|
12
|
+
.description('Get a configuration value')
|
|
13
|
+
.action(async (key: string) => {
|
|
14
|
+
const cfg = await configManager.get();
|
|
15
|
+
const value = getNestedValue(cfg, key);
|
|
16
|
+
console.log(value !== undefined ? value : pc.gray('(not set)'));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
config
|
|
20
|
+
.command('set <key> <value>')
|
|
21
|
+
.description('Set a configuration value')
|
|
22
|
+
.action(async (key: string, value: string) => {
|
|
23
|
+
await configManager.update((cfg) => {
|
|
24
|
+
return setNestedValue(cfg, key, parseValue(value));
|
|
25
|
+
});
|
|
26
|
+
console.log(pc.green(`✓ Set ${key} = ${value}`));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
config
|
|
30
|
+
.command('list')
|
|
31
|
+
.description('List all configuration values')
|
|
32
|
+
.action(async () => {
|
|
33
|
+
const cfg = await configManager.get();
|
|
34
|
+
console.log(pc.bold('\nConfiguration:'));
|
|
35
|
+
printConfig(cfg);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
config
|
|
39
|
+
.command('reset')
|
|
40
|
+
.description('Reset all configuration (WARNING: clears auth)')
|
|
41
|
+
.option('--force', 'Skip confirmation')
|
|
42
|
+
.action(async (options) => {
|
|
43
|
+
if (!options.force) {
|
|
44
|
+
const confirmed = await confirm({
|
|
45
|
+
message: 'This will clear all config including login. Continue?',
|
|
46
|
+
default: false
|
|
47
|
+
});
|
|
48
|
+
if (!confirmed) return;
|
|
49
|
+
}
|
|
50
|
+
await configManager.clear();
|
|
51
|
+
console.log(pc.green('✓ Configuration reset'));
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return config;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getNestedValue(obj: any, key: string): any {
|
|
58
|
+
return key.split('.').reduce((o, k) => o?.[k], obj);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function setNestedValue(obj: any, key: string, value: any): any {
|
|
62
|
+
const keys = key.split('.');
|
|
63
|
+
const last = keys.pop()!;
|
|
64
|
+
const target = keys.reduce((o, k) => {
|
|
65
|
+
if (!o[k]) o[k] = {};
|
|
66
|
+
return o[k];
|
|
67
|
+
}, obj);
|
|
68
|
+
target[last] = value;
|
|
69
|
+
return obj;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function parseValue(value: string): any {
|
|
73
|
+
if (value === 'true') return true;
|
|
74
|
+
if (value === 'false') return false;
|
|
75
|
+
if (value === 'null') return null;
|
|
76
|
+
if (!isNaN(Number(value))) return Number(value);
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(value);
|
|
79
|
+
} catch {
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function printConfig(obj: any, prefix = ''): void {
|
|
85
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
86
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
87
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
88
|
+
console.log(`\n${pc.cyan(fullKey)}:`);
|
|
89
|
+
printConfig(value, fullKey);
|
|
90
|
+
} else {
|
|
91
|
+
const displayValue = key.includes('token')
|
|
92
|
+
? pc.gray('***hidden***')
|
|
93
|
+
: value;
|
|
94
|
+
console.log(` ${fullKey} = ${displayValue}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|