knoxis-collab 1.0.0 → 1.1.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.
Files changed (2) hide show
  1. package/knoxis-collab.js +91 -25
  2. package/package.json +4 -1
package/knoxis-collab.js CHANGED
@@ -18,7 +18,9 @@
18
18
  * KNOXIS_TASK_FILE=/tmp/task.txt node knoxis-collab.js
19
19
  *
20
20
  * Env:
21
- * GROQ_API_KEY - Required (or set in ~/.knoxis/config.json)
21
+ * KNOXIS_BACKEND_URL - Backend URL (or backendUrl in ~/.knoxis/config.json)
22
+ * KNOXIS_USER_ID - User UUID (or userId in ~/.knoxis/config.json)
23
+ * GROQ_API_KEY - Direct Groq key (optional — backend proxy is preferred)
22
24
  * KNOXIS_WORKSPACE - Workspace directory (default: cwd)
23
25
  * KNOXIS_TASK_FILE - Path to file containing initial task
24
26
  * KNOXIS_GROQ_MODEL - Groq model (default: llama-3.3-70b-versatile)
@@ -80,6 +82,27 @@ function loadConfig() {
80
82
 
81
83
  const config = loadConfig();
82
84
  const GROQ_API_KEY = process.env.GROQ_API_KEY || config.groqApiKey || '';
85
+ const BACKEND_URL = process.env.KNOXIS_BACKEND_URL || config.backendUrl || '';
86
+ const USER_ID = process.env.KNOXIS_USER_ID || config.userId || '';
87
+
88
+ // Resolve Claude binary — bundled (node_modules) first, then global PATH
89
+ function resolveClaudeBin() {
90
+ // Check local node_modules (when installed as npm package)
91
+ const localBin = path.join(__dirname, 'node_modules', '.bin', 'claude');
92
+ if (fs.existsSync(localBin)) return localBin;
93
+
94
+ // Check parent node_modules (when this is a dep of another package)
95
+ const parentBin = path.join(__dirname, '..', '.bin', 'claude');
96
+ if (fs.existsSync(parentBin)) return parentBin;
97
+
98
+ // Fall back to global PATH
99
+ const { status, stdout } = spawnSync('which', ['claude'], { stdio: 'pipe' });
100
+ if (status === 0 && stdout.toString().trim()) return 'claude';
101
+
102
+ return null;
103
+ }
104
+
105
+ const CLAUDE_BIN = resolveClaudeBin();
83
106
 
84
107
  // ═══════════════════════════════════════════════════════════════
85
108
  // STATE
@@ -135,7 +158,8 @@ function printHeader() {
135
158
  console.log('');
136
159
  console.log(` ${C.dim}Session:${C.reset} ${SESSION_ID.slice(0, 8)}`);
137
160
  console.log(` ${C.dim}Dir:${C.reset} ${WORKSPACE}`);
138
- console.log(` ${C.dim}Knoxis:${C.reset} Groq (${GROQ_MODEL})`);
161
+ const groqMode = (BACKEND_URL && USER_ID) ? `via backend` : 'direct API';
162
+ console.log(` ${C.dim}Knoxis:${C.reset} Groq (${GROQ_MODEL}, ${groqMode})`);
139
163
  console.log(` ${C.dim}Claude:${C.reset} Claude Code (session-persistent)`);
140
164
  console.log(` ${C.dim}Log:${C.reset} ${path.basename(logFile)}`);
141
165
  console.log('');
@@ -293,12 +317,19 @@ PERSONALITY: Direct, technical, efficient. You're a developer talking to a devel
293
317
 
294
318
  function callGroq(messages, projectContext) {
295
319
  return new Promise((resolve, reject) => {
296
- if (!GROQ_API_KEY) {
297
- reject(new Error('GROQ_API_KEY not set'));
320
+ const useBackend = BACKEND_URL && USER_ID;
321
+ const useDirectGroq = GROQ_API_KEY && !useBackend;
322
+
323
+ if (!useBackend && !useDirectGroq) {
324
+ reject(new Error(
325
+ 'No Groq access configured.\n' +
326
+ ' Option 1 (recommended): Set backendUrl + userId in ~/.knoxis/config.json\n' +
327
+ ' Option 2 (direct): Set GROQ_API_KEY env var or groqApiKey in config.json'
328
+ ));
298
329
  return;
299
330
  }
300
331
 
301
- const payload = JSON.stringify({
332
+ const groqPayload = {
302
333
  model: GROQ_MODEL,
303
334
  messages: [
304
335
  { role: 'system', content: buildSystemPrompt(projectContext) },
@@ -307,20 +338,51 @@ function callGroq(messages, projectContext) {
307
338
  temperature: 0.3,
308
339
  max_tokens: 2048,
309
340
  response_format: { type: 'json_object' }
310
- });
311
-
312
- const options = {
313
- hostname: 'api.groq.com',
314
- path: '/openai/v1/chat/completions',
315
- method: 'POST',
316
- headers: {
317
- 'Content-Type': 'application/json',
318
- 'Authorization': `Bearer ${GROQ_API_KEY}`,
319
- 'Content-Length': Buffer.byteLength(payload)
320
- }
321
341
  };
322
342
 
323
- const req = https.request(options, (res) => {
343
+ // If backend is available, route through the proxy (no local key needed)
344
+ if (useBackend) {
345
+ groqPayload.userId = USER_ID;
346
+ }
347
+
348
+ const payload = JSON.stringify(groqPayload);
349
+
350
+ let options;
351
+ if (useBackend) {
352
+ // Route through backend proxy
353
+ const backendClean = BACKEND_URL
354
+ .replace(/^wss:\/\//, 'https://')
355
+ .replace(/^ws:\/\//, 'http://')
356
+ .replace(/\/+$/, '');
357
+ const url = new URL(backendClean + '/api/knoxis/collab/chat');
358
+ options = {
359
+ hostname: url.hostname,
360
+ port: url.port || (url.protocol === 'https:' ? 443 : 80),
361
+ path: url.pathname,
362
+ method: 'POST',
363
+ headers: {
364
+ 'Content-Type': 'application/json',
365
+ 'Content-Length': Buffer.byteLength(payload)
366
+ }
367
+ };
368
+ } else {
369
+ // Direct Groq API call (legacy / developer override)
370
+ options = {
371
+ hostname: 'api.groq.com',
372
+ path: '/openai/v1/chat/completions',
373
+ method: 'POST',
374
+ headers: {
375
+ 'Content-Type': 'application/json',
376
+ 'Authorization': `Bearer ${GROQ_API_KEY}`,
377
+ 'Content-Length': Buffer.byteLength(payload)
378
+ }
379
+ };
380
+ }
381
+
382
+ const transport = options.hostname === 'localhost' || options.port === 80
383
+ ? require('http') : https;
384
+
385
+ const req = transport.request(options, (res) => {
324
386
  let data = '';
325
387
  res.on('data', chunk => data += chunk);
326
388
  res.on('end', () => {
@@ -373,8 +435,7 @@ function callGroq(messages, projectContext) {
373
435
  // ═══════════════════════════════════════════════════════════════
374
436
 
375
437
  function checkClaudeInstalled() {
376
- const result = spawnSync('which', ['claude'], { stdio: 'pipe' });
377
- return result.status === 0;
438
+ return CLAUDE_BIN !== null;
378
439
  }
379
440
 
380
441
  function dispatchToClaude(prompt) {
@@ -392,7 +453,7 @@ function dispatchToClaude(prompt) {
392
453
 
393
454
  log(`DISPATCH #${claudeDispatches}:\n${prompt}`);
394
455
 
395
- const proc = spawn('claude', args, {
456
+ const proc = spawn(CLAUDE_BIN, args, {
396
457
  cwd: WORKSPACE,
397
458
  env: { ...process.env },
398
459
  stdio: ['pipe', 'pipe', 'pipe']
@@ -651,15 +712,20 @@ async function handleUserInput(input, rl) {
651
712
 
652
713
  async function main() {
653
714
  // Preflight checks
654
- if (!GROQ_API_KEY) {
655
- console.error(`\n ${C.red}Error: GROQ_API_KEY not found.${C.reset}`);
656
- console.error(` Set it via environment variable or in ~/.knoxis/config.json\n`);
715
+ const hasBackend = BACKEND_URL && USER_ID;
716
+ const hasDirectKey = !!GROQ_API_KEY;
717
+
718
+ if (!hasBackend && !hasDirectKey) {
719
+ console.error(`\n ${C.red}Error: No Groq access configured.${C.reset}`);
720
+ console.error(` Set ${C.bold}backendUrl${C.reset} + ${C.bold}userId${C.reset} in ~/.knoxis/config.json (recommended)`);
721
+ console.error(` Or set GROQ_API_KEY for direct access.\n`);
657
722
  process.exit(1);
658
723
  }
659
724
 
660
725
  if (!checkClaudeInstalled()) {
661
- console.error(`\n ${C.red}Error: 'claude' CLI not found in PATH.${C.reset}`);
662
- console.error(` Install Claude Code: npm install -g @anthropic-ai/claude-code\n`);
726
+ console.error(`\n ${C.red}Error: 'claude' CLI not found.${C.reset}`);
727
+ console.error(` It should be bundled with this package. Try: npm install`);
728
+ console.error(` Or install globally: npm install -g @anthropic-ai/claude-code\n`);
663
729
  process.exit(1);
664
730
  }
665
731
 
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "knoxis-collab",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Three-way collaborative programming — User + Knoxis + Claude Code in a persistent session",
5
5
  "main": "knoxis-collab.js",
6
6
  "bin": {
7
7
  "knoxis-collab": "./knoxis-collab.js"
8
8
  },
9
+ "dependencies": {
10
+ "@anthropic-ai/claude-code": "^1.0.0"
11
+ },
9
12
  "keywords": [
10
13
  "knoxis",
11
14
  "pair-programming",