recoder-code 2.4.7 ā 2.5.1
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/dist/src/commands/agents/create.d.ts +4 -0
- package/dist/src/commands/agents/create.js +175 -0
- package/dist/src/commands/agents/marketplace.d.ts +5 -0
- package/dist/src/commands/agents/marketplace.js +151 -0
- package/dist/src/commands/agents.js +49 -1
- package/dist/src/commands/configure.js +8 -0
- package/dist/src/commands/connect-cmd.js +8 -0
- package/dist/src/commands/hints.js +12 -0
- package/dist/src/commands/models/compare.d.ts +4 -0
- package/dist/src/commands/models/compare.js +92 -0
- package/dist/src/commands/models/select.d.ts +4 -0
- package/dist/src/commands/models/select.js +62 -0
- package/dist/src/commands/models-cmd.js +35 -0
- package/dist/src/commands/providers/config.d.ts +1 -1
- package/dist/src/commands/providers/config.js +37 -19
- package/dist/src/commands/providers/health.d.ts +4 -0
- package/dist/src/commands/providers/health.js +90 -0
- package/dist/src/commands/providers.js +20 -0
- package/dist/src/providers/local-detection.d.ts +6 -0
- package/dist/src/providers/local-detection.js +86 -0
- package/dist/src/providers/registry.js +2 -2
- package/dist/src/utils/secure-storage.d.ts +24 -0
- package/dist/src/utils/secure-storage.js +150 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent creation command - Create custom agents from templates
|
|
3
|
+
*/
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
const AGENTS_DIR_PROJECT = '.recoder/agents';
|
|
10
|
+
const AGENTS_DIR_USER = path.join(os.homedir(), '.recoder-code', 'agents');
|
|
11
|
+
const AGENT_TEMPLATES = [
|
|
12
|
+
{
|
|
13
|
+
name: 'explorer',
|
|
14
|
+
description: 'Code discovery & research specialist',
|
|
15
|
+
systemPrompt: `You are an expert code explorer and researcher. Your role is to:
|
|
16
|
+
- Search and analyze codebases to understand structure and patterns
|
|
17
|
+
- Find relevant code examples and implementations
|
|
18
|
+
- Identify dependencies and relationships between components
|
|
19
|
+
- Discover best practices and conventions used in the project
|
|
20
|
+
- Provide comprehensive reports on code organization
|
|
21
|
+
|
|
22
|
+
Always be thorough and cite specific file paths and line numbers.`,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'coder',
|
|
26
|
+
description: 'Implementation specialist',
|
|
27
|
+
systemPrompt: `You are an expert software engineer focused on implementation. Your role is to:
|
|
28
|
+
- Write clean, production-ready code following best practices
|
|
29
|
+
- Implement features with proper error handling and edge cases
|
|
30
|
+
- Follow the project's existing patterns and conventions
|
|
31
|
+
- Write self-documenting code with clear variable names
|
|
32
|
+
- Consider performance and maintainability
|
|
33
|
+
|
|
34
|
+
Always write complete, working implementations - no placeholders or TODOs.`,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'reviewer',
|
|
38
|
+
description: 'Code quality & security specialist',
|
|
39
|
+
systemPrompt: `You are an expert code reviewer focused on quality and security. Your role is to:
|
|
40
|
+
- Review code for bugs, security vulnerabilities, and performance issues
|
|
41
|
+
- Check adherence to best practices and design patterns
|
|
42
|
+
- Identify potential edge cases and error scenarios
|
|
43
|
+
- Suggest improvements for readability and maintainability
|
|
44
|
+
- Verify proper error handling and input validation
|
|
45
|
+
|
|
46
|
+
Provide specific, actionable feedback with examples.`,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'tester',
|
|
50
|
+
description: 'Test creation specialist',
|
|
51
|
+
systemPrompt: `You are an expert test engineer. Your role is to:
|
|
52
|
+
- Write comprehensive unit, integration, and e2e tests
|
|
53
|
+
- Cover edge cases and error scenarios
|
|
54
|
+
- Follow testing best practices (AAA pattern, clear assertions)
|
|
55
|
+
- Create meaningful test descriptions
|
|
56
|
+
- Ensure high code coverage
|
|
57
|
+
|
|
58
|
+
Write tests that are maintainable and serve as documentation.`,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'documenter',
|
|
62
|
+
description: 'Documentation specialist',
|
|
63
|
+
systemPrompt: `You are an expert technical writer. Your role is to:
|
|
64
|
+
- Create clear, comprehensive documentation
|
|
65
|
+
- Write API documentation with examples
|
|
66
|
+
- Document architecture and design decisions
|
|
67
|
+
- Create user guides and tutorials
|
|
68
|
+
- Maintain README files and changelogs
|
|
69
|
+
|
|
70
|
+
Write documentation that is accessible to both beginners and experts.`,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'custom',
|
|
74
|
+
description: 'Start from scratch',
|
|
75
|
+
systemPrompt: `You are a helpful AI assistant. Customize this prompt for your specific needs.`,
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
export async function createAgent() {
|
|
79
|
+
console.log(chalk.bold.cyan('\nš¤ Create Custom Agent\n'));
|
|
80
|
+
const answers = await inquirer.prompt([
|
|
81
|
+
{
|
|
82
|
+
type: 'list',
|
|
83
|
+
name: 'template',
|
|
84
|
+
message: 'Choose a template:',
|
|
85
|
+
choices: AGENT_TEMPLATES.map(t => ({
|
|
86
|
+
name: `${t.name} - ${t.description}`,
|
|
87
|
+
value: t.name,
|
|
88
|
+
})),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
type: 'input',
|
|
92
|
+
name: 'name',
|
|
93
|
+
message: 'Agent name:',
|
|
94
|
+
validate: (input) => {
|
|
95
|
+
if (!input)
|
|
96
|
+
return 'Name is required';
|
|
97
|
+
if (!/^[a-z0-9-]+$/.test(input))
|
|
98
|
+
return 'Use lowercase letters, numbers, and hyphens only';
|
|
99
|
+
return true;
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
type: 'input',
|
|
104
|
+
name: 'description',
|
|
105
|
+
message: 'Description:',
|
|
106
|
+
default: (answers) => {
|
|
107
|
+
const template = AGENT_TEMPLATES.find(t => t.name === answers.template);
|
|
108
|
+
return template?.description || '';
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
type: 'list',
|
|
113
|
+
name: 'location',
|
|
114
|
+
message: 'Save location:',
|
|
115
|
+
choices: [
|
|
116
|
+
{ name: 'Project (.recoder/agents/) - Available in this project only', value: 'project' },
|
|
117
|
+
{ name: 'User (~/.recoder-code/agents/) - Available globally', value: 'user' },
|
|
118
|
+
],
|
|
119
|
+
default: 'project',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: 'confirm',
|
|
123
|
+
name: 'customize',
|
|
124
|
+
message: 'Customize system prompt now?',
|
|
125
|
+
default: false,
|
|
126
|
+
},
|
|
127
|
+
]);
|
|
128
|
+
const template = AGENT_TEMPLATES.find(t => t.name === answers.template);
|
|
129
|
+
let systemPrompt = template?.systemPrompt || '';
|
|
130
|
+
if (answers.customize) {
|
|
131
|
+
const { prompt } = await inquirer.prompt([
|
|
132
|
+
{
|
|
133
|
+
type: 'editor',
|
|
134
|
+
name: 'prompt',
|
|
135
|
+
message: 'Edit system prompt:',
|
|
136
|
+
default: systemPrompt,
|
|
137
|
+
},
|
|
138
|
+
]);
|
|
139
|
+
systemPrompt = prompt;
|
|
140
|
+
}
|
|
141
|
+
const dir = answers.location === 'project' ? AGENTS_DIR_PROJECT : AGENTS_DIR_USER;
|
|
142
|
+
if (!fs.existsSync(dir)) {
|
|
143
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
144
|
+
}
|
|
145
|
+
const filePath = path.join(dir, `${answers.name}.md`);
|
|
146
|
+
if (fs.existsSync(filePath)) {
|
|
147
|
+
const { overwrite } = await inquirer.prompt([
|
|
148
|
+
{
|
|
149
|
+
type: 'confirm',
|
|
150
|
+
name: 'overwrite',
|
|
151
|
+
message: `Agent "${answers.name}" already exists. Overwrite?`,
|
|
152
|
+
default: false,
|
|
153
|
+
},
|
|
154
|
+
]);
|
|
155
|
+
if (!overwrite) {
|
|
156
|
+
console.log(chalk.yellow('\nā Cancelled'));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const content = `---
|
|
161
|
+
name: ${answers.name}
|
|
162
|
+
description: ${answers.description}
|
|
163
|
+
template: ${answers.template}
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
${systemPrompt}
|
|
167
|
+
`;
|
|
168
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
169
|
+
console.log(chalk.green(`\nā
Agent "${answers.name}" created successfully!`));
|
|
170
|
+
console.log(chalk.gray(` Location: ${filePath}`));
|
|
171
|
+
console.log(chalk.cyan('\nš” Usage:'));
|
|
172
|
+
console.log(chalk.gray(` In chat: "Let the ${answers.name} agent help with this"`));
|
|
173
|
+
console.log(chalk.gray(` Edit: ${filePath}`));
|
|
174
|
+
console.log();
|
|
175
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent marketplace - Share and discover custom agents
|
|
3
|
+
*/
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import os from 'os';
|
|
9
|
+
const AGENTS_DIR_PROJECT = '.recoder/agents';
|
|
10
|
+
const AGENTS_DIR_USER = path.join(os.homedir(), '.recoder-code', 'agents');
|
|
11
|
+
const MARKETPLACE_URL = 'https://recoder.xyz/api/agents';
|
|
12
|
+
export async function browseMarketplace() {
|
|
13
|
+
console.log(chalk.bold.cyan('\nšŖ Agent Marketplace\n'));
|
|
14
|
+
console.log(chalk.gray('Discover and install community agents\n'));
|
|
15
|
+
try {
|
|
16
|
+
// Fetch agents from marketplace
|
|
17
|
+
const response = await fetch(`${MARKETPLACE_URL}/list`);
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
throw new Error('Failed to fetch agents');
|
|
20
|
+
}
|
|
21
|
+
const agents = await response.json();
|
|
22
|
+
if (agents.length === 0) {
|
|
23
|
+
console.log(chalk.yellow('No agents available yet'));
|
|
24
|
+
console.log(chalk.gray('Be the first to share: recoder agents share'));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const choices = agents.map(a => ({
|
|
28
|
+
name: `${a.name} - ${a.description} ${chalk.gray(`(by ${a.author})`)}`,
|
|
29
|
+
value: a.id,
|
|
30
|
+
}));
|
|
31
|
+
const { agentId } = await inquirer.prompt([
|
|
32
|
+
{
|
|
33
|
+
type: 'list',
|
|
34
|
+
name: 'agentId',
|
|
35
|
+
message: 'Select an agent to install:',
|
|
36
|
+
choices,
|
|
37
|
+
pageSize: 10,
|
|
38
|
+
},
|
|
39
|
+
]);
|
|
40
|
+
const agent = agents.find(a => a.id === agentId);
|
|
41
|
+
if (!agent)
|
|
42
|
+
return;
|
|
43
|
+
const { location } = await inquirer.prompt([
|
|
44
|
+
{
|
|
45
|
+
type: 'list',
|
|
46
|
+
name: 'location',
|
|
47
|
+
message: 'Install location:',
|
|
48
|
+
choices: [
|
|
49
|
+
{ name: 'Project (.recoder/agents/)', value: 'project' },
|
|
50
|
+
{ name: 'User (~/.recoder-code/agents/)', value: 'user' },
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
]);
|
|
54
|
+
await installAgent(agent, location);
|
|
55
|
+
console.log(chalk.green(`\nā
Installed: ${agent.name}`));
|
|
56
|
+
console.log(chalk.cyan('š” Usage:'));
|
|
57
|
+
console.log(chalk.gray(` In chat: "Let the ${agent.name} agent help"`));
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
console.log(chalk.red(`\nā Error: ${err.message}`));
|
|
61
|
+
console.log(chalk.gray('Marketplace coming soon!'));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export async function shareAgent() {
|
|
65
|
+
console.log(chalk.bold.cyan('\nš¤ Share Agent to Marketplace\n'));
|
|
66
|
+
// List local agents
|
|
67
|
+
const localAgents = [];
|
|
68
|
+
if (fs.existsSync(AGENTS_DIR_PROJECT)) {
|
|
69
|
+
fs.readdirSync(AGENTS_DIR_PROJECT)
|
|
70
|
+
.filter(f => f.endsWith('.md'))
|
|
71
|
+
.forEach(f => localAgents.push(`project:${f.replace('.md', '')}`));
|
|
72
|
+
}
|
|
73
|
+
if (fs.existsSync(AGENTS_DIR_USER)) {
|
|
74
|
+
fs.readdirSync(AGENTS_DIR_USER)
|
|
75
|
+
.filter(f => f.endsWith('.md'))
|
|
76
|
+
.forEach(f => localAgents.push(`user:${f.replace('.md', '')}`));
|
|
77
|
+
}
|
|
78
|
+
if (localAgents.length === 0) {
|
|
79
|
+
console.log(chalk.yellow('No custom agents found'));
|
|
80
|
+
console.log(chalk.gray('Create one first: recoder agents new'));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const { agent } = await inquirer.prompt([
|
|
84
|
+
{
|
|
85
|
+
type: 'list',
|
|
86
|
+
name: 'agent',
|
|
87
|
+
message: 'Select agent to share:',
|
|
88
|
+
choices: localAgents,
|
|
89
|
+
},
|
|
90
|
+
]);
|
|
91
|
+
const [location, name] = agent.split(':');
|
|
92
|
+
const dir = location === 'project' ? AGENTS_DIR_PROJECT : AGENTS_DIR_USER;
|
|
93
|
+
const filePath = path.join(dir, `${name}.md`);
|
|
94
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
95
|
+
// Parse frontmatter
|
|
96
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
97
|
+
if (!match) {
|
|
98
|
+
console.log(chalk.red('Invalid agent format'));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const frontmatter = match[1];
|
|
102
|
+
const description = frontmatter.match(/description: (.+)/)?.[1] || 'No description';
|
|
103
|
+
const { author, confirm } = await inquirer.prompt([
|
|
104
|
+
{
|
|
105
|
+
type: 'input',
|
|
106
|
+
name: 'author',
|
|
107
|
+
message: 'Your name:',
|
|
108
|
+
validate: (input) => input.length > 0 || 'Name required',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
type: 'confirm',
|
|
112
|
+
name: 'confirm',
|
|
113
|
+
message: `Share "${name}" to marketplace?`,
|
|
114
|
+
default: true,
|
|
115
|
+
},
|
|
116
|
+
]);
|
|
117
|
+
if (!confirm) {
|
|
118
|
+
console.log(chalk.yellow('\nā Cancelled'));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const response = await fetch(`${MARKETPLACE_URL}/share`, {
|
|
123
|
+
method: 'POST',
|
|
124
|
+
headers: { 'Content-Type': 'application/json' },
|
|
125
|
+
body: JSON.stringify({
|
|
126
|
+
name,
|
|
127
|
+
description,
|
|
128
|
+
author,
|
|
129
|
+
content,
|
|
130
|
+
version: '1.0.0',
|
|
131
|
+
}),
|
|
132
|
+
});
|
|
133
|
+
if (!response.ok) {
|
|
134
|
+
throw new Error('Failed to share agent');
|
|
135
|
+
}
|
|
136
|
+
console.log(chalk.green(`\nā
Agent shared successfully!`));
|
|
137
|
+
console.log(chalk.gray(' View at: https://recoder.xyz/agents'));
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
console.log(chalk.red(`\nā Error: ${err.message}`));
|
|
141
|
+
console.log(chalk.gray('Marketplace coming soon!'));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function installAgent(agent, location) {
|
|
145
|
+
const dir = location === 'project' ? AGENTS_DIR_PROJECT : AGENTS_DIR_USER;
|
|
146
|
+
if (!fs.existsSync(dir)) {
|
|
147
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
148
|
+
}
|
|
149
|
+
const filePath = path.join(dir, `${agent.name}.md`);
|
|
150
|
+
fs.writeFileSync(filePath, agent.content, 'utf-8');
|
|
151
|
+
}
|
|
@@ -3,14 +3,53 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { listAgents, createAgentFromTemplate } from './agents/list.js';
|
|
6
|
+
import { createAgent } from './agents/create.js';
|
|
7
|
+
import { browseMarketplace, shareAgent } from './agents/marketplace.js';
|
|
8
|
+
import { RecoderAuthService } from '../services/RecoderAuthService.js';
|
|
9
|
+
async function requireAuth() {
|
|
10
|
+
const authService = new RecoderAuthService();
|
|
11
|
+
const session = await authService.getSession();
|
|
12
|
+
if (!session) {
|
|
13
|
+
console.error('ā Please login first: recoder auth login');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
6
17
|
const listCommand = {
|
|
7
18
|
command: 'list',
|
|
8
19
|
describe: 'List all available agents',
|
|
9
20
|
handler: async () => {
|
|
21
|
+
await requireAuth();
|
|
10
22
|
await listAgents();
|
|
11
23
|
process.exit(0);
|
|
12
24
|
},
|
|
13
25
|
};
|
|
26
|
+
const createInteractiveCommand = {
|
|
27
|
+
command: 'new',
|
|
28
|
+
describe: 'Create a custom agent interactively',
|
|
29
|
+
handler: async () => {
|
|
30
|
+
await requireAuth();
|
|
31
|
+
await createAgent();
|
|
32
|
+
process.exit(0);
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
const marketplaceCommand = {
|
|
36
|
+
command: 'browse',
|
|
37
|
+
describe: 'Browse agent marketplace',
|
|
38
|
+
handler: async () => {
|
|
39
|
+
await requireAuth();
|
|
40
|
+
await browseMarketplace();
|
|
41
|
+
process.exit(0);
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
const shareCommand = {
|
|
45
|
+
command: 'share',
|
|
46
|
+
describe: 'Share your agent to marketplace',
|
|
47
|
+
handler: async () => {
|
|
48
|
+
await requireAuth();
|
|
49
|
+
await shareAgent();
|
|
50
|
+
process.exit(0);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
14
53
|
const createCommand = {
|
|
15
54
|
command: 'create <name>',
|
|
16
55
|
describe: 'Create a new agent from template',
|
|
@@ -29,6 +68,7 @@ const createCommand = {
|
|
|
29
68
|
default: 'project',
|
|
30
69
|
}),
|
|
31
70
|
handler: async (argv) => {
|
|
71
|
+
await requireAuth();
|
|
32
72
|
const location = argv.location === 'user' ? 'user' : 'project';
|
|
33
73
|
const filePath = createAgentFromTemplate(argv.template || 'coder', argv.name, location);
|
|
34
74
|
if (filePath) {
|
|
@@ -47,8 +87,16 @@ const createCommand = {
|
|
|
47
87
|
export const agentsCommand = {
|
|
48
88
|
command: 'agents',
|
|
49
89
|
describe: 'Manage AI agents (built-in and custom)',
|
|
50
|
-
builder: (yargs) => yargs
|
|
90
|
+
builder: (yargs) => yargs
|
|
91
|
+
.command(listCommand)
|
|
92
|
+
.command(createInteractiveCommand)
|
|
93
|
+
.command(marketplaceCommand)
|
|
94
|
+
.command(shareCommand)
|
|
95
|
+
.command(createCommand)
|
|
96
|
+
.demandCommand(0)
|
|
97
|
+
.version(false),
|
|
51
98
|
handler: async () => {
|
|
99
|
+
await requireAuth();
|
|
52
100
|
await listAgents();
|
|
53
101
|
process.exit(0);
|
|
54
102
|
},
|
|
@@ -4,10 +4,18 @@
|
|
|
4
4
|
import * as readline from 'readline';
|
|
5
5
|
import { getProviderRegistry } from '../providers/registry.js';
|
|
6
6
|
import { BUILTIN_PROVIDERS } from '../providers/types.js';
|
|
7
|
+
import { RecoderAuthService } from '../services/RecoderAuthService.js';
|
|
7
8
|
export const configureCommand = {
|
|
8
9
|
command: 'configure',
|
|
9
10
|
describe: 'Interactive configuration wizard',
|
|
10
11
|
handler: async () => {
|
|
12
|
+
// Require authentication
|
|
13
|
+
const authService = new RecoderAuthService();
|
|
14
|
+
const session = await authService.getSession();
|
|
15
|
+
if (!session) {
|
|
16
|
+
console.error('ā Please login first: recoder auth login');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
11
19
|
const rl = readline.createInterface({
|
|
12
20
|
input: process.stdin,
|
|
13
21
|
output: process.stdout,
|
|
@@ -2,10 +2,18 @@
|
|
|
2
2
|
* Connect command module - Add custom providers interactively
|
|
3
3
|
*/
|
|
4
4
|
import { connectProvider } from './connect.js';
|
|
5
|
+
import { RecoderAuthService } from '../services/RecoderAuthService.js';
|
|
5
6
|
export const connectCommand = {
|
|
6
7
|
command: 'connect',
|
|
7
8
|
describe: 'Add a custom AI provider (LM Studio, vLLM, etc.)',
|
|
8
9
|
handler: async () => {
|
|
10
|
+
// Require authentication
|
|
11
|
+
const authService = new RecoderAuthService();
|
|
12
|
+
const session = await authService.getSession();
|
|
13
|
+
if (!session) {
|
|
14
|
+
console.error('ā Please login first: recoder auth login');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
9
17
|
await connectProvider();
|
|
10
18
|
process.exit(0);
|
|
11
19
|
},
|
|
@@ -3,10 +3,20 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { loadHints, createHintsFile, hasHints } from '../utils/hints.js';
|
|
6
|
+
import { RecoderAuthService } from '../services/RecoderAuthService.js';
|
|
7
|
+
async function requireAuth() {
|
|
8
|
+
const authService = new RecoderAuthService();
|
|
9
|
+
const session = await authService.getSession();
|
|
10
|
+
if (!session) {
|
|
11
|
+
console.error('ā Please login first: recoder auth login');
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
6
15
|
const showCommand = {
|
|
7
16
|
command: 'show',
|
|
8
17
|
describe: 'Show current project hints',
|
|
9
18
|
handler: async () => {
|
|
19
|
+
await requireAuth();
|
|
10
20
|
const hints = loadHints();
|
|
11
21
|
if (hints) {
|
|
12
22
|
console.log(chalk.cyan(`\nš Hints from ${hints.filePath}\n`));
|
|
@@ -24,6 +34,7 @@ const initCommand = {
|
|
|
24
34
|
command: 'init',
|
|
25
35
|
describe: 'Create a .recoderhints file',
|
|
26
36
|
handler: async () => {
|
|
37
|
+
await requireAuth();
|
|
27
38
|
if (hasHints()) {
|
|
28
39
|
console.log(chalk.yellow('\nā ļø Hints file already exists'));
|
|
29
40
|
const hints = loadHints();
|
|
@@ -44,6 +55,7 @@ export const hintsCommand = {
|
|
|
44
55
|
describe: 'Manage project hints (.recoderhints)',
|
|
45
56
|
builder: (yargs) => yargs.command(showCommand).command(initCommand).demandCommand(0).version(false),
|
|
46
57
|
handler: async () => {
|
|
58
|
+
await requireAuth();
|
|
47
59
|
const hints = loadHints();
|
|
48
60
|
if (hints) {
|
|
49
61
|
console.log(chalk.cyan(`\nš Project hints: ${hints.filePath}`));
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-model comparison mode
|
|
3
|
+
*/
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { getProviderRegistry } from '../../providers/registry.js';
|
|
7
|
+
export async function compareModels() {
|
|
8
|
+
console.log(chalk.bold.cyan('\nāļø Multi-Model Comparison\n'));
|
|
9
|
+
const registry = getProviderRegistry();
|
|
10
|
+
const providers = registry.getAllProviders();
|
|
11
|
+
// Get available models
|
|
12
|
+
const modelChoices = [];
|
|
13
|
+
for (const provider of providers) {
|
|
14
|
+
try {
|
|
15
|
+
const models = await registry.getModels(provider.id);
|
|
16
|
+
models.forEach(m => {
|
|
17
|
+
modelChoices.push(`${provider.id}/${m.id}`);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Skip
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (modelChoices.length < 2) {
|
|
25
|
+
console.log(chalk.red('ā Need at least 2 models to compare'));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const answers = await inquirer.prompt([
|
|
29
|
+
{
|
|
30
|
+
type: 'checkbox',
|
|
31
|
+
name: 'models',
|
|
32
|
+
message: 'Select models to compare (2-4):',
|
|
33
|
+
choices: modelChoices.slice(0, 20),
|
|
34
|
+
validate: (input) => {
|
|
35
|
+
if (input.length < 2)
|
|
36
|
+
return 'Select at least 2 models';
|
|
37
|
+
if (input.length > 4)
|
|
38
|
+
return 'Select at most 4 models';
|
|
39
|
+
return true;
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: 'input',
|
|
44
|
+
name: 'prompt',
|
|
45
|
+
message: 'Enter prompt to test:',
|
|
46
|
+
validate: (input) => input.length > 0 || 'Prompt required',
|
|
47
|
+
},
|
|
48
|
+
]);
|
|
49
|
+
console.log(chalk.cyan('\nš Running comparison...\n'));
|
|
50
|
+
const results = [];
|
|
51
|
+
for (const model of answers.models) {
|
|
52
|
+
const start = Date.now();
|
|
53
|
+
try {
|
|
54
|
+
// Simulate API call (replace with actual implementation)
|
|
55
|
+
const response = `Response from ${model}`;
|
|
56
|
+
const responseTime = Date.now() - start;
|
|
57
|
+
results.push({
|
|
58
|
+
model,
|
|
59
|
+
response,
|
|
60
|
+
responseTime,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
results.push({
|
|
65
|
+
model,
|
|
66
|
+
response: '',
|
|
67
|
+
responseTime: Date.now() - start,
|
|
68
|
+
error: err.message,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Display results
|
|
73
|
+
console.log(chalk.bold('Comparison Results:'));
|
|
74
|
+
console.log(chalk.gray('ā'.repeat(60)));
|
|
75
|
+
results.forEach((result, i) => {
|
|
76
|
+
console.log(chalk.bold.cyan(`\n${i + 1}. ${result.model}`));
|
|
77
|
+
console.log(chalk.gray(` Response time: ${result.responseTime}ms`));
|
|
78
|
+
if (result.error) {
|
|
79
|
+
console.log(chalk.red(` Error: ${result.error}`));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
console.log(chalk.white(` ${result.response.substring(0, 200)}...`));
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
console.log(chalk.gray('\nā'.repeat(60)));
|
|
86
|
+
// Show fastest
|
|
87
|
+
const fastest = results.filter(r => !r.error).sort((a, b) => a.responseTime - b.responseTime)[0];
|
|
88
|
+
if (fastest) {
|
|
89
|
+
console.log(chalk.green(`\nā” Fastest: ${fastest.model} (${fastest.responseTime}ms)`));
|
|
90
|
+
}
|
|
91
|
+
console.log();
|
|
92
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive model selection with fuzzy search
|
|
3
|
+
*/
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { getProviderRegistry } from '../../providers/registry.js';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
export async function selectModel() {
|
|
8
|
+
console.log(chalk.bold.cyan('\nš¤ Select AI Model\n'));
|
|
9
|
+
const registry = getProviderRegistry();
|
|
10
|
+
const providers = registry.getAllProviders();
|
|
11
|
+
// Gather all models from all providers
|
|
12
|
+
const allModels = [];
|
|
13
|
+
for (const provider of providers) {
|
|
14
|
+
try {
|
|
15
|
+
const models = await registry.getModels(provider.id);
|
|
16
|
+
models.forEach(model => {
|
|
17
|
+
const contextInfo = model.contextLength ? ` (${Math.round(model.contextLength / 1000)}k ctx)` : '';
|
|
18
|
+
const freeTag = model.isFree ? chalk.green(' [FREE]') : '';
|
|
19
|
+
const providerTag = chalk.gray(` - ${provider.name}`);
|
|
20
|
+
allModels.push({
|
|
21
|
+
name: `${model.id}${contextInfo}${freeTag}${providerTag}`,
|
|
22
|
+
value: `${provider.id}/${model.id}`,
|
|
23
|
+
provider: provider.id,
|
|
24
|
+
contextLength: model.contextLength,
|
|
25
|
+
isFree: model.isFree,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
// Skip providers that fail to load models
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (allModels.length === 0) {
|
|
34
|
+
console.log(chalk.red('ā No models available'));
|
|
35
|
+
console.log(chalk.gray(' Run: recoder providers detect'));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// Sort: free models first, then by context length
|
|
39
|
+
allModels.sort((a, b) => {
|
|
40
|
+
if (a.isFree && !b.isFree)
|
|
41
|
+
return -1;
|
|
42
|
+
if (!a.isFree && b.isFree)
|
|
43
|
+
return 1;
|
|
44
|
+
return (b.contextLength || 0) - (a.contextLength || 0);
|
|
45
|
+
});
|
|
46
|
+
const answer = await inquirer.prompt([
|
|
47
|
+
{
|
|
48
|
+
type: 'list',
|
|
49
|
+
name: 'model',
|
|
50
|
+
message: 'Select a model:',
|
|
51
|
+
choices: allModels.slice(0, 30),
|
|
52
|
+
pageSize: 15,
|
|
53
|
+
},
|
|
54
|
+
]);
|
|
55
|
+
const selected = allModels.find(m => m.value === answer.model);
|
|
56
|
+
if (selected) {
|
|
57
|
+
console.log(chalk.green(`\nā
Selected: ${selected.value}`));
|
|
58
|
+
console.log(chalk.cyan('\nš” Usage:'));
|
|
59
|
+
console.log(chalk.gray(` recoder --model ${selected.value}`));
|
|
60
|
+
console.log(chalk.gray(` Or set as default: recoder models set-default ${selected.value}`));
|
|
61
|
+
}
|
|
62
|
+
}
|