@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
package/src/commands/agents.ts
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
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
|
-
}
|
package/src/commands/config.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
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
|
-
}
|
package/src/commands/explore.ts
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { select, checkbox, confirm, Separator } from '@inquirer/prompts';
|
|
3
|
-
import pc from 'picocolors';
|
|
4
|
-
import ora from 'ora';
|
|
5
|
-
import { ApiClient } from '../core/api/client.js';
|
|
6
|
-
import { ConfigManager } from '../core/config/config-manager.js';
|
|
7
|
-
import { ProjectTracker } from '../core/config/project-tracker.js';
|
|
8
|
-
import { formatDate } from '../utils/date.js';
|
|
9
|
-
|
|
10
|
-
interface ExploreOptions {
|
|
11
|
-
project?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface ProjectChoice {
|
|
15
|
-
name: string;
|
|
16
|
-
value: any;
|
|
17
|
-
short?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function createExploreCommand(
|
|
21
|
-
configManager: ConfigManager,
|
|
22
|
-
apiClient: ApiClient
|
|
23
|
-
): Command {
|
|
24
|
-
const projectTracker = new ProjectTracker(configManager);
|
|
25
|
-
|
|
26
|
-
return new Command('explore')
|
|
27
|
-
.description('Explore and select projects/agents to track')
|
|
28
|
-
.option('-p, --project <id>', 'Start with specific project')
|
|
29
|
-
.action(async (options: ExploreOptions) => {
|
|
30
|
-
const spinner = ora('Fetching projects...').start();
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
// Fetch all projects
|
|
34
|
-
const response = await apiClient.getProjectsBrief();
|
|
35
|
-
const projects = response.projects;
|
|
36
|
-
spinner.stop();
|
|
37
|
-
|
|
38
|
-
if (projects.length === 0) {
|
|
39
|
-
console.log(pc.yellow('No projects found. Create one in the web app first.'));
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Project selection loop
|
|
44
|
-
let exploring = true;
|
|
45
|
-
while (exploring) {
|
|
46
|
-
const projectChoices: ProjectChoice[] = projects.map((p) => ({
|
|
47
|
-
name: `${p.name} ${pc.gray(`(${p.agentCount} agents, last modified ${formatDate(p.lastModifiedAt)})`)}`,
|
|
48
|
-
value: p,
|
|
49
|
-
short: p.name
|
|
50
|
-
}));
|
|
51
|
-
|
|
52
|
-
let selectedProject: any;
|
|
53
|
-
if (options.project) {
|
|
54
|
-
selectedProject = projects.find((p) => p.id === options.project || p.slug === options.project);
|
|
55
|
-
} else {
|
|
56
|
-
const choices: any[] = [
|
|
57
|
-
...projectChoices,
|
|
58
|
-
new Separator(),
|
|
59
|
-
{ name: pc.yellow('Done exploring'), value: 'DONE' }
|
|
60
|
-
];
|
|
61
|
-
selectedProject = await select({
|
|
62
|
-
message: 'Select a project to explore:',
|
|
63
|
-
choices
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (selectedProject === 'DONE') {
|
|
68
|
-
exploring = false;
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (!selectedProject) {
|
|
73
|
-
console.log(pc.red('Project not found'));
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Fetch agents for selected project
|
|
78
|
-
const agentSpinner = ora(`Fetching agents for ${selectedProject.name}...`).start();
|
|
79
|
-
const agentsResponse = await apiClient.getProjectAgents(selectedProject.id);
|
|
80
|
-
const agents = agentsResponse;
|
|
81
|
-
agentSpinner.succeed(`Found ${agents.agents.length} agents`);
|
|
82
|
-
|
|
83
|
-
// Display agents with folder paths
|
|
84
|
-
console.log(pc.bold(`\nš ${selectedProject.name}\n`));
|
|
85
|
-
|
|
86
|
-
agents.agents.forEach((agent) => {
|
|
87
|
-
const isPublished = agent.isPublished
|
|
88
|
-
? pc.green('ā')
|
|
89
|
-
: pc.gray('ā');
|
|
90
|
-
const versionInfo = `${agent.version} ${pc.gray(`(${agents.versionResolution.source})`)}`;
|
|
91
|
-
|
|
92
|
-
console.log(` ${isPublished} ${pc.cyan(agent.name)}`);
|
|
93
|
-
console.log(` ${pc.gray('Path:')} ${agent.folderPath}`);
|
|
94
|
-
console.log(` ${pc.gray('Version:')} ${versionInfo}`);
|
|
95
|
-
console.log(` ${pc.gray('Tools:')} ${agent.toolCount}`);
|
|
96
|
-
if (agents.versionResolution.source !== 'PRODUCTION') {
|
|
97
|
-
console.log(` ${pc.yellow('ā ')} ${agents.versionResolution.note}`);
|
|
98
|
-
}
|
|
99
|
-
console.log();
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// Agent selection
|
|
103
|
-
const currentlyTracked = await projectTracker.getTrackedAgents(selectedProject.id);
|
|
104
|
-
const trackedIds = new Set(currentlyTracked.map(a => a.agentId));
|
|
105
|
-
|
|
106
|
-
const selectedAgentIds = await checkbox({
|
|
107
|
-
message: 'Select agents to track (space to toggle, enter to confirm):',
|
|
108
|
-
choices: agents.agents.map((agent) => ({
|
|
109
|
-
name: `${agent.name} ${pc.gray(`(${agent.folderPath})`)}`,
|
|
110
|
-
value: agent.id,
|
|
111
|
-
checked: trackedIds.has(agent.id)
|
|
112
|
-
}))
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// Update tracking - IMMEDIATE AUTO-SAVE
|
|
116
|
-
if (selectedAgentIds.length > 0) {
|
|
117
|
-
const selectedAgents = agents.agents.filter((a) => selectedAgentIds.includes(a.id));
|
|
118
|
-
try {
|
|
119
|
-
await projectTracker.trackProject(selectedProject, selectedAgents);
|
|
120
|
-
console.log(pc.green(`\nā Auto-saved: Tracking ${selectedAgents.length} agents from ${selectedProject.name}`));
|
|
121
|
-
} catch (error) {
|
|
122
|
-
console.error(pc.red(`\nā Failed to save tracking: ${(error as Error).message}`));
|
|
123
|
-
throw error; // RETHROW - don't silently fail
|
|
124
|
-
}
|
|
125
|
-
} else {
|
|
126
|
-
try {
|
|
127
|
-
await projectTracker.untrackProject(selectedProject.id);
|
|
128
|
-
console.log(pc.yellow(`\nā Auto-saved: No longer tracking ${selectedProject.name}`));
|
|
129
|
-
} catch (error) {
|
|
130
|
-
console.error(pc.red(`\nā Failed to untrack: ${(error as Error).message}`));
|
|
131
|
-
throw error; // RETHROW - don't silently fail
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Continue?
|
|
136
|
-
if (!options.project) {
|
|
137
|
-
const continueExploring = await confirm({
|
|
138
|
-
message: 'Explore another project?',
|
|
139
|
-
default: true
|
|
140
|
-
});
|
|
141
|
-
if (!continueExploring) exploring = false;
|
|
142
|
-
} else {
|
|
143
|
-
exploring = false;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Summary
|
|
148
|
-
const summary = await projectTracker.getSummary();
|
|
149
|
-
console.log(pc.bold('\nš Tracking Summary\n'));
|
|
150
|
-
console.log(` Projects: ${summary.projectCount}`);
|
|
151
|
-
console.log(` Agents: ${summary.agentCount}`);
|
|
152
|
-
console.log(pc.gray('\nRun `porchestra pull` to generate code for tracked agents\n'));
|
|
153
|
-
|
|
154
|
-
} catch (error) {
|
|
155
|
-
spinner.fail('Failed to fetch projects');
|
|
156
|
-
console.error(pc.red(`\nā ${(error as Error).message}`));
|
|
157
|
-
process.exit(1);
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
}
|
package/src/commands/login.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import pc from 'picocolors';
|
|
3
|
-
import { input, password } from '@inquirer/prompts';
|
|
4
|
-
import { ConfigManager } from '../core/config/config-manager.js';
|
|
5
|
-
import { AuthService } from '../core/auth/auth-service.js';
|
|
6
|
-
|
|
7
|
-
interface LoginOptions {
|
|
8
|
-
apiUrl?: string;
|
|
9
|
-
skipTlsVerify?: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function createLoginCommand(
|
|
13
|
-
configManager: ConfigManager,
|
|
14
|
-
authService: AuthService
|
|
15
|
-
): Command {
|
|
16
|
-
return new Command('login')
|
|
17
|
-
.description('Authenticate with your Porchestra account')
|
|
18
|
-
.option('--api-url <url>', 'Override API URL')
|
|
19
|
-
.option('--skip-tls-verify', 'Skip TLS certificate verification (insecure)')
|
|
20
|
-
.action(async (options: LoginOptions) => {
|
|
21
|
-
console.log(pc.bold('\n \uD83D\uDD10 Porchestra CLI Login\n'));
|
|
22
|
-
|
|
23
|
-
const apiUrl = options.apiUrl || process.env.PORCHESTRA_API_URL;
|
|
24
|
-
|
|
25
|
-
if (options.skipTlsVerify) {
|
|
26
|
-
console.log(pc.yellow('\n \u26A0\uFE0F WARNING: TLS verification disabled. Not recommended for production.\n'));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const email = await input({
|
|
30
|
-
message: ' Email:',
|
|
31
|
-
validate: (value) => value.includes('@') || 'Please enter a valid email'
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const pwd = await password({
|
|
35
|
-
message: ' Password:',
|
|
36
|
-
mask: '*'
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
const result = await authService.login({
|
|
41
|
-
email,
|
|
42
|
-
password: pwd,
|
|
43
|
-
apiUrl,
|
|
44
|
-
skipTlsVerify: options.skipTlsVerify
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// DEBUG: Log raw API response
|
|
48
|
-
console.log(pc.yellow('\n [DEBUG] Raw API response:'));
|
|
49
|
-
console.log(pc.yellow(` token: ${result.token?.substring(0, 20)}...`));
|
|
50
|
-
console.log(pc.yellow(` tokenId: ${result.tokenId}`));
|
|
51
|
-
console.log(pc.yellow(` expiresAt: ${result.expiresAt}`));
|
|
52
|
-
console.log(pc.yellow(` issuedAt: ${result.issuedAt}`));
|
|
53
|
-
console.log(pc.yellow(` deviceName: ${result.deviceName}`));
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
await configManager.update((cfg) => ({
|
|
57
|
-
...cfg,
|
|
58
|
-
auth: {
|
|
59
|
-
token: result.token,
|
|
60
|
-
tokenId: result.tokenId,
|
|
61
|
-
expiresAt: result.expiresAt,
|
|
62
|
-
deviceName: result.deviceName
|
|
63
|
-
},
|
|
64
|
-
api: {
|
|
65
|
-
baseUrl: apiUrl || cfg.api?.baseUrl || 'https://api.porchestra.io/v1',
|
|
66
|
-
skipTlsVerify: options.skipTlsVerify || false
|
|
67
|
-
},
|
|
68
|
-
cli: {
|
|
69
|
-
...cfg.cli,
|
|
70
|
-
lastLoginAt: new Date().toISOString()
|
|
71
|
-
}
|
|
72
|
-
}));
|
|
73
|
-
|
|
74
|
-
// DEBUG: Verify config was saved
|
|
75
|
-
const savedConfig = await configManager.get();
|
|
76
|
-
console.log(pc.yellow('\n [DEBUG] Saved config auth:'));
|
|
77
|
-
console.log(pc.yellow(` ${JSON.stringify(savedConfig.auth, null, 2)}`));
|
|
78
|
-
} catch (saveError) {
|
|
79
|
-
console.error(pc.red('\n [DEBUG] Save error:'), saveError);
|
|
80
|
-
throw saveError;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const expiresAt = new Date(result.expiresAt);
|
|
84
|
-
const daysUntilExpiry = Math.ceil((expiresAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
|
|
85
|
-
|
|
86
|
-
console.log(pc.green(`
|
|
87
|
-
ā Login successful
|
|
88
|
-
`));
|
|
89
|
-
console.log(pc.gray(` Token expires in ${daysUntilExpiry} days (${result.expiresAt})`));
|
|
90
|
-
console.log(pc.gray(` Device: ${result.deviceName}`));
|
|
91
|
-
if (apiUrl) {
|
|
92
|
-
console.log(pc.gray(` API: ${apiUrl}`));
|
|
93
|
-
}
|
|
94
|
-
console.log(pc.gray(` Auto-refresh: 7 days before expiry`));
|
|
95
|
-
console.log();
|
|
96
|
-
} catch (error) {
|
|
97
|
-
console.error(pc.red(`\n \u2717 Login failed: ${(error as Error).message}`));
|
|
98
|
-
process.exit(1);
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
}
|
package/src/commands/logout.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import pc from 'picocolors';
|
|
3
|
-
import { confirm } from '@inquirer/prompts';
|
|
4
|
-
import { AuthService } from '../core/auth/auth-service.js';
|
|
5
|
-
import { ConfigManager } from '../core/config/config-manager.js';
|
|
6
|
-
|
|
7
|
-
interface LogoutOptions {
|
|
8
|
-
all?: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function createLogoutCommand(
|
|
12
|
-
configManager: ConfigManager,
|
|
13
|
-
authService: AuthService
|
|
14
|
-
): Command {
|
|
15
|
-
return new Command('logout')
|
|
16
|
-
.description('Logout and revoke CLI token')
|
|
17
|
-
.option('--all', 'Revoke all device tokens (logout everywhere)')
|
|
18
|
-
.action(async (options: LogoutOptions) => {
|
|
19
|
-
const config = await configManager.get();
|
|
20
|
-
|
|
21
|
-
if (!config.auth?.token) {
|
|
22
|
-
console.log(pc.yellow('Not currently logged in'));
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (options.all) {
|
|
27
|
-
const confirmed = await confirm({
|
|
28
|
-
message: 'This will revoke ALL your CLI tokens on ALL devices. Continue?',
|
|
29
|
-
default: false
|
|
30
|
-
});
|
|
31
|
-
if (!confirmed) return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
// Revoke on server
|
|
36
|
-
await authService.revokeToken(config.auth.token, options.all);
|
|
37
|
-
|
|
38
|
-
// Clear local config
|
|
39
|
-
await configManager.clear();
|
|
40
|
-
|
|
41
|
-
if (options.all) {
|
|
42
|
-
console.log(pc.green('ā Revoked all tokens and logged out all devices'));
|
|
43
|
-
} else {
|
|
44
|
-
console.log(pc.green('ā Logged out successfully'));
|
|
45
|
-
}
|
|
46
|
-
} catch (error) {
|
|
47
|
-
// Even if server revoke fails, clear local config
|
|
48
|
-
await configManager.clear();
|
|
49
|
-
console.log(pc.yellow('ā Cleared local credentials (server revoke may have failed)'));
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
}
|