echelon-dev 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.
@@ -0,0 +1,286 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Echelon Dev - Multi-Agent Pair Programming CLI
5
+ *
6
+ * Usage:
7
+ * npx echelon-dev # Interactive setup (first time)
8
+ * npx echelon-dev --backend wss://... # Connect to specific backend
9
+ * npx echelon-dev --status # Check connection status
10
+ * npx echelon-dev pair --task "add auth" # Start pair programming session
11
+ * npx echelon-dev sessions # List recorded sessions
12
+ * npx echelon-dev session <id> # View session details
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const os = require('os');
18
+ const readline = require('readline');
19
+
20
+ const CONFIG_DIR = path.join(os.homedir(), '.echelon');
21
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
22
+ const DEFAULT_BACKEND = 'wss://voice-app-v2.graymushroom-5a5599fe.centralus.azurecontainerapps.io';
23
+
24
+ function loadConfig() {
25
+ try {
26
+ if (fs.existsSync(CONFIG_FILE)) {
27
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
28
+ }
29
+ } catch (e) {}
30
+ return {};
31
+ }
32
+
33
+ function saveConfig(config) {
34
+ if (!fs.existsSync(CONFIG_DIR)) {
35
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
36
+ }
37
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
38
+ }
39
+
40
+ function parseArgs() {
41
+ const args = {};
42
+ const positional = [];
43
+ for (let i = 2; i < process.argv.length; i++) {
44
+ const arg = process.argv[i];
45
+ if (arg === '--status') { args.status = true; continue; }
46
+ if (arg === '--reset') { args.reset = true; continue; }
47
+ if (arg.startsWith('--')) {
48
+ const key = arg.slice(2);
49
+ const next = process.argv[i + 1];
50
+ if (next && !next.startsWith('--')) {
51
+ args[key] = next;
52
+ i++;
53
+ } else {
54
+ args[key] = true;
55
+ }
56
+ } else {
57
+ positional.push(arg);
58
+ }
59
+ }
60
+ args._positional = positional;
61
+ return args;
62
+ }
63
+
64
+ function ask(rl, question) {
65
+ return new Promise(resolve => rl.question(question, resolve));
66
+ }
67
+
68
+ async function firstTimeSetup(args) {
69
+ const config = loadConfig();
70
+
71
+ console.log('');
72
+ console.log(' Echelon Dev - First Time Setup');
73
+ console.log(' ==============================');
74
+ console.log('');
75
+ console.log(' This connects your machine to the Echelon dev team');
76
+ console.log(' (Knoxis, Solan, Astrahelm) for pair programming.');
77
+ console.log('');
78
+
79
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
80
+
81
+ let backend = args.backend || config.backendWsUrl;
82
+ if (!backend) {
83
+ const answer = await ask(rl, ` Backend URL [${DEFAULT_BACKEND}]: `);
84
+ backend = answer.trim() || DEFAULT_BACKEND;
85
+ }
86
+
87
+ if (backend.startsWith('http://')) {
88
+ backend = backend.replace('http://', 'ws://');
89
+ } else if (backend.startsWith('https://')) {
90
+ backend = backend.replace('https://', 'wss://');
91
+ } else if (!backend.startsWith('ws://') && !backend.startsWith('wss://')) {
92
+ backend = 'wss://' + backend;
93
+ }
94
+
95
+ let userId = args.userId || args.user || config.userId;
96
+ if (!userId) {
97
+ console.log('');
98
+ console.log(' Your user ID is on your profile page at qig.ai/settings.');
99
+ const answer = await ask(rl, ' User ID: ');
100
+ userId = answer.trim();
101
+
102
+ if (!userId) {
103
+ console.error(' User ID is required. Find it at qig.ai/settings.');
104
+ rl.close();
105
+ process.exit(1);
106
+ }
107
+ }
108
+
109
+ rl.close();
110
+
111
+ config.backendWsUrl = backend;
112
+ config.userId = userId;
113
+ config.configuredAt = new Date().toISOString();
114
+ saveConfig(config);
115
+
116
+ console.log('');
117
+ console.log(' Configuration saved to ~/.echelon/config.json');
118
+ console.log(` Backend: ${backend}`);
119
+ console.log(` User: ${userId}`);
120
+ console.log('');
121
+
122
+ return config;
123
+ }
124
+
125
+ function showSessions() {
126
+ const { listSessions } = require('../lib/session-recorder');
127
+ const sessions = listSessions(20);
128
+
129
+ if (sessions.length === 0) {
130
+ console.log('No recorded sessions yet.');
131
+ console.log('Run a pair programming session to start recording.');
132
+ return;
133
+ }
134
+
135
+ console.log('');
136
+ console.log(' Recent Sessions');
137
+ console.log(' ===============');
138
+ console.log('');
139
+
140
+ for (const session of sessions) {
141
+ const date = new Date(session.startedAt).toLocaleString();
142
+ const duration = session.totalDurationMs
143
+ ? `${Math.round(session.totalDurationMs / 1000)}s`
144
+ : '?';
145
+ const agents = session.agents ? session.agents.join(', ') : '?';
146
+ const steps = `${session.completedSteps || 0}/${session.totalSteps || 0} steps`;
147
+
148
+ console.log(` ${session.sessionId}`);
149
+ console.log(` Task: ${session.task}`);
150
+ console.log(` Date: ${date} Duration: ${duration} Agents: ${agents} ${steps}`);
151
+ console.log('');
152
+ }
153
+ }
154
+
155
+ function showSessionDetail(sessionId) {
156
+ const { loadSession } = require('../lib/session-recorder');
157
+ const session = loadSession(sessionId);
158
+
159
+ if (!session) {
160
+ console.error(`Session not found: ${sessionId}`);
161
+ process.exit(1);
162
+ }
163
+
164
+ console.log('');
165
+ console.log(` Session: ${session.sessionId}`);
166
+ console.log(` Task: ${session.task}`);
167
+ console.log(` Workspace: ${session.workspace}`);
168
+ console.log(` Agents: ${session.agents.join(', ')}`);
169
+ console.log(` AI: ${session.aiProvider}`);
170
+ console.log(` Duration: ${Math.round(session.totalDurationMs / 1000)}s`);
171
+ console.log(` Steps: ${session.completedSteps}/${session.totalSteps} completed`);
172
+ console.log('');
173
+
174
+ for (const step of session.steps) {
175
+ const status = step.error ? 'FAILED' : 'OK';
176
+ const duration = step.durationMs ? `${Math.round(step.durationMs / 1000)}s` : '?';
177
+ const changed = step.codeChanged ? ' [code changed]' : '';
178
+
179
+ console.log(` [${status}] ${step.agentId} - ${step.stepKey} (${duration})${changed}`);
180
+ if (step.error) {
181
+ console.log(` Error: ${step.error}`);
182
+ }
183
+ }
184
+
185
+ if (session.git?.final?.commit) {
186
+ console.log('');
187
+ console.log(` Git: ${session.git.initial?.commit || '?'} -> ${session.git.final.commit}`);
188
+ }
189
+ console.log('');
190
+ }
191
+
192
+ async function main() {
193
+ const args = parseArgs();
194
+ const command = args._positional[0];
195
+
196
+ // Reset
197
+ if (args.reset) {
198
+ try { fs.unlinkSync(CONFIG_FILE); } catch (e) {}
199
+ console.log('Configuration cleared. Run again to set up.');
200
+ process.exit(0);
201
+ }
202
+
203
+ // Status
204
+ if (args.status) {
205
+ const config = loadConfig();
206
+ if (!config.backendWsUrl || !config.userId) {
207
+ console.log('Not configured. Run: npx echelon-dev');
208
+ } else {
209
+ console.log('Configured:');
210
+ console.log(` Backend: ${config.backendWsUrl}`);
211
+ console.log(` User: ${config.userId}`);
212
+ }
213
+ process.exit(0);
214
+ }
215
+
216
+ // Sessions listing
217
+ if (command === 'sessions') {
218
+ showSessions();
219
+ process.exit(0);
220
+ }
221
+
222
+ // Session detail
223
+ if (command === 'session') {
224
+ const sessionId = args._positional[1];
225
+ if (!sessionId) {
226
+ console.error('Usage: echelon-dev session <session-id>');
227
+ process.exit(1);
228
+ }
229
+ showSessionDetail(sessionId);
230
+ process.exit(0);
231
+ }
232
+
233
+ // Pair programming (direct CLI mode)
234
+ if (command === 'pair') {
235
+ const task = args.task || args.prompt;
236
+ if (!task) {
237
+ console.error('Usage: echelon-dev pair --task "your task description" [--workspace /path] [--agents knoxis,solan]');
238
+ process.exit(1);
239
+ }
240
+
241
+ // Build args for the pair programming script
242
+ const pairArgs = ['node', path.join(__dirname, '..', 'lib', 'echelon-pair-program.js')];
243
+ pairArgs.push('--workspace', args.workspace || process.cwd());
244
+ pairArgs.push('--prompt', task);
245
+ if (args.agents) pairArgs.push('--agents', args.agents);
246
+ if (args.provider) pairArgs.push('--ai-provider', args.provider);
247
+ if (args['context-file']) pairArgs.push('--context-file', args['context-file']);
248
+ if (args['no-record']) pairArgs.push('--no-record');
249
+
250
+ // Set process.argv and require the script
251
+ process.argv = pairArgs;
252
+ require('../lib/echelon-pair-program');
253
+ return;
254
+ }
255
+
256
+ // Default: setup + launch local agent
257
+ let config = loadConfig();
258
+ const needsSetup = !config.backendWsUrl || !config.userId || args.backend || args.userId;
259
+
260
+ if (needsSetup) {
261
+ config = await firstTimeSetup(args);
262
+ } else {
263
+ console.log('');
264
+ console.log(` Echelon Dev - Connecting to ${config.backendWsUrl}`);
265
+ console.log(` User: ${config.userId}`);
266
+ console.log('');
267
+ }
268
+
269
+ // Set environment variables for the local agent
270
+ process.env.ECHELON_BACKEND_WS_URL = config.backendWsUrl;
271
+ process.env.ECHELON_USER_ID = config.userId;
272
+
273
+ // Launch the local agent
274
+ const agentPath = path.join(__dirname, '..', 'lib', 'echelon-local-agent.js');
275
+ if (!fs.existsSync(agentPath)) {
276
+ console.error('Could not find echelon-local-agent.js at:', agentPath);
277
+ process.exit(1);
278
+ }
279
+
280
+ require(agentPath);
281
+ }
282
+
283
+ main().catch(err => {
284
+ console.error('Error:', err.message);
285
+ process.exit(1);
286
+ });
package/lib/agents.js ADDED
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Echelon Dev Team - Agent Persona Definitions
3
+ *
4
+ * These are three senior engineers on a team. Each has a specialty,
5
+ * but all are competent full-stack developers who can handle anything.
6
+ * They don't force their specialty where it doesn't apply.
7
+ */
8
+
9
+ const AGENTS = {
10
+ knoxis: {
11
+ id: 'knoxis',
12
+ name: 'Knoxis',
13
+ color: '\x1b[36m', // cyan
14
+ specialty: 'architecture and systems logic',
15
+ description: 'Senior engineer. Thinks in systems — data flow, APIs, state management, infrastructure. Strong across the full stack but naturally gravitates to the structural side of things.',
16
+ persona: `You are Knoxis, a senior software engineer on a small dev team.
17
+
18
+ Your strengths: Systems thinking, architecture, API design, data modeling, backend logic, infrastructure.
19
+ But you're a well-rounded engineer — you're comfortable with frontend, DevOps, databases, whatever the task needs.
20
+
21
+ Your style:
22
+ - You think about how pieces fit together before writing code
23
+ - You prefer clean, simple solutions over clever ones
24
+ - You speak plainly about tradeoffs — no hand-waving
25
+ - If something is over-engineered, you'll say so
26
+ - You don't add complexity unless it earns its keep
27
+
28
+ You are NOT a "backend-only" bot. You work on whatever the task requires.
29
+ If the task is purely frontend, you handle it competently without deferring to someone else.
30
+ If there's nothing architectural to say, you just get the work done.`
31
+ },
32
+
33
+ solan: {
34
+ id: 'solan',
35
+ name: 'Solan',
36
+ color: '\x1b[35m', // magenta
37
+ specialty: 'design thinking and user experience',
38
+ description: 'Senior engineer with a design eye. Thinks about how things feel to use — component structure, accessibility, responsive behavior, state that touches the UI. Practical, not precious.',
39
+ persona: `You are Solan, a senior software engineer on a small dev team.
40
+
41
+ Your strengths: UI/UX thinking, component architecture, accessibility, responsive design, frontend state management, making things feel good to use.
42
+ But you're a full-stack engineer — you can write backend code, work with databases, set up infrastructure, whatever is needed.
43
+
44
+ Your style:
45
+ - You think about the person using the thing, not just the code
46
+ - You only bring up design considerations when they're actually relevant
47
+ - You don't redesign things that work fine — if it ain't broke, leave it alone
48
+ - You're practical, not precious — shipping beats perfection
49
+ - You push back on unnecessary UI complexity just as hard as unnecessary backend complexity
50
+
51
+ You are NOT a "frontend-only" bot or a "design system" generator.
52
+ If the task is backend-only, you handle it without forcing a design angle.
53
+ If there's nothing UX-relevant to contribute, you just do good engineering work.
54
+ Do NOT suggest design improvements, new components, or styling changes unless the task specifically calls for it.`
55
+ },
56
+
57
+ astrahelm: {
58
+ id: 'astrahelm',
59
+ name: 'Astrahelm',
60
+ color: '\x1b[33m', // yellow
61
+ specialty: 'security and risk assessment',
62
+ description: 'Senior engineer with a security mindset. Thinks about what could go wrong — auth, input validation, data exposure, dependencies. Pragmatic about risk, not paranoid.',
63
+ persona: `You are Astrahelm, a senior software engineer on a small dev team.
64
+
65
+ Your strengths: Security thinking, auth patterns, input validation, dependency auditing, threat modeling, hardening code against real-world attacks.
66
+ But you're a full-stack engineer — you build features, fix bugs, write tests, whatever the team needs.
67
+
68
+ Your style:
69
+ - You think about what could go wrong, but you're realistic about what WILL go wrong
70
+ - You only flag security issues that are genuine risks for this codebase and context
71
+ - You do NOT manufacture security concerns to justify your existence
72
+ - You don't add auth, validation, or hardening that isn't needed
73
+ - If the code is a local dev tool or internal script, you calibrate your security advice accordingly
74
+ - You're allergic to security theater — if it doesn't actually make things safer, skip it
75
+
76
+ You are NOT a "security scanner" or "compliance checker."
77
+ If the task has no security implications, you just write good code.
78
+ Do NOT force OWASP checklists, threat models, or security reviews on code that doesn't need them.
79
+ Only flag real risks that a reasonable senior engineer would actually worry about.`
80
+ }
81
+ };
82
+
83
+ // Color reset
84
+ const RESET = '\x1b[0m';
85
+
86
+ function getAgent(id) {
87
+ const normalized = (id || '').toLowerCase().trim();
88
+ return AGENTS[normalized] || null;
89
+ }
90
+
91
+ function getAllAgents() {
92
+ return Object.values(AGENTS);
93
+ }
94
+
95
+ function getAgentIds() {
96
+ return Object.keys(AGENTS);
97
+ }
98
+
99
+ function colorize(agentId, text) {
100
+ const agent = getAgent(agentId);
101
+ if (!agent) return text;
102
+ return `${agent.color}${text}${RESET}`;
103
+ }
104
+
105
+ /**
106
+ * Build the team system prompt for multi-agent sessions.
107
+ * This is the shared context all agents operate under.
108
+ */
109
+ function buildTeamSystemPrompt(agents) {
110
+ const teamList = agents
111
+ .map(a => `- ${a.name}: ${a.description}`)
112
+ .join('\n');
113
+
114
+ return `You are a senior software engineer pair programming as part of a small dev team.
115
+
116
+ The team:
117
+ ${teamList}
118
+
119
+ How this works:
120
+ - You'll be given a specific agent persona for each step
121
+ - The conversation history shows what your teammates said in previous steps
122
+ - Build on their work — don't repeat what's already been established
123
+ - If a teammate made a good call, just run with it
124
+ - If you disagree with something, say so briefly and move on
125
+
126
+ Team rules:
127
+ - Be direct and concise — no fluff, no excessive politeness
128
+ - Do NOT ask clarifying questions. Make your best engineering judgment and proceed.
129
+ - If you need to choose between approaches, pick the most standard one and briefly note why.
130
+ - Only contribute from your specialty when it's actually relevant to the task
131
+ - If your specialty doesn't apply, just be a good engineer
132
+ - Don't over-engineer. Don't add things that weren't asked for.
133
+ - Work autonomously. Make decisions and implement.
134
+ - Only work inside the provided workspace and preserve user data.`;
135
+ }
136
+
137
+ module.exports = {
138
+ AGENTS,
139
+ getAgent,
140
+ getAllAgents,
141
+ getAgentIds,
142
+ colorize,
143
+ buildTeamSystemPrompt,
144
+ RESET
145
+ };