subconscious-cli 0.1.1 → 0.2.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/bin/cli.js CHANGED
@@ -1,400 +1,53 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Subconscious CLI — `login`, `logout`, and `whoami` commands.
4
+ * Subconscious CLI — log in, then launch coding agents on your hosted models.
5
5
  *
6
- * Login flow (localhost callback pattern, similar to Vercel/Supabase CLIs):
7
- * 1. CLI generates a random `state` token (CSRF protection) and starts
8
- * an ephemeral HTTP server on a random port bound to 127.0.0.1.
9
- * 2. Opens the browser to {PLATFORM_URL}/cli/auth?port=...&state=...
10
- * 3. The web app authenticates the user, generates an API key, and
11
- * delivers it back to the CLI via a cross-origin fetch to
12
- * localhost:{port}/callback?token=...&state=...
13
- * 4. CLI verifies the `state` matches, saves the key to ~/.subcon/config.json.
6
+ * subconscious login | logout | whoami — manage your API key
7
+ * subconscious <agent> [...args] — launch a coding agent
14
8
  *
15
- * Override SUBCONSCIOUS_URL env var for local development.
9
+ * Auth lives in ./auth.js, the agent launcher + registry in ./agents.js.
16
10
  */
17
11
 
18
- import http from 'node:http';
19
- import crypto from 'node:crypto';
20
- import { exec } from 'node:child_process';
21
12
  import fs from 'node:fs/promises';
22
- import os from 'node:os';
23
- import path from 'node:path';
24
-
25
- const CONFIG_DIR = path.join(os.homedir(), '.subcon');
26
- const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
27
- // Defaults to production. Developers set SUBCONSCIOUS_URL=http://localhost:3000 for local dev.
28
- const PLATFORM_URL = process.env.SUBCONSCIOUS_URL || 'https://www.subconscious.dev';
29
-
30
- const c = {
31
- reset: '\x1b[0m',
32
- bold: '\x1b[1m',
33
- dim: '\x1b[2m',
34
- cyan: '\x1b[36m',
35
- green: '\x1b[32m',
36
- red: '\x1b[31m',
37
- yellow: '\x1b[33m',
38
- magenta: '\x1b[35m',
39
- underline: '\x1b[4m',
40
- };
41
-
42
- // ── Config helpers ──────────────────────────────────────────────────────
43
-
44
- async function loadConfig() {
45
- try {
46
- const content = await fs.readFile(CONFIG_FILE, 'utf-8');
47
- return JSON.parse(content);
48
- } catch {
49
- return {};
50
- }
51
- }
52
-
53
- async function saveConfig(config) {
54
- await fs.mkdir(CONFIG_DIR, { recursive: true });
55
- await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
56
- // 0o600 = owner read/write only — the file contains an API key
57
- await fs.chmod(CONFIG_FILE, 0o600);
58
- }
59
-
60
- // ── Browser opener ──────────────────────────────────────────────────────
61
-
62
- function openBrowser(url) {
63
- const cmd =
64
- process.platform === 'darwin'
65
- ? `open "${url}"`
66
- : process.platform === 'win32'
67
- ? `start "" "${url}"`
68
- : `xdg-open "${url}"`;
69
- exec(cmd, (err) => {
70
- if (err) {
71
- console.log(
72
- `\n${c.yellow}Could not open browser automatically.${c.reset}`,
73
- );
74
- console.log(`Please open this URL manually:\n`);
75
- console.log(` ${c.underline}${c.cyan}${url}${c.reset}\n`);
76
- }
77
- });
78
- }
79
-
80
- // ── Localhost callback server ───────────────────────────────────────────
81
-
82
- function startCallbackServer(expectedState) {
83
- return new Promise((resolveSetup) => {
84
- let resolveToken, rejectToken;
85
- const tokenPromise = new Promise((resolve, reject) => {
86
- resolveToken = resolve;
87
- rejectToken = reject;
88
- });
89
-
90
- const server = http.createServer((req, res) => {
91
- // CORS: only allow the web app's origin (production or localhost dev).
92
- // This prevents arbitrary websites from hitting this callback.
93
- const origin = req.headers.origin || '';
94
- const allowed =
95
- origin === PLATFORM_URL ||
96
- origin.startsWith('http://localhost:');
97
- res.setHeader(
98
- 'Access-Control-Allow-Origin',
99
- allowed ? origin : PLATFORM_URL,
100
- );
101
- res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
102
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
103
- res.setHeader('Connection', 'close');
104
-
105
- if (req.method === 'OPTIONS') {
106
- res.writeHead(204);
107
- res.end();
108
- return;
109
- }
110
-
111
- const url = new URL(req.url, 'http://localhost');
112
-
113
- if (url.pathname !== '/callback') {
114
- res.writeHead(404);
115
- res.end();
116
- return;
117
- }
118
-
119
- const token = url.searchParams.get('token');
120
- const state = url.searchParams.get('state');
121
- const error = url.searchParams.get('error');
122
-
123
- // The web app sends `Accept: application/json` via fetch(); direct
124
- // browser visits get the HTML fallback page.
125
- const wantsJson = (req.headers.accept || '').includes('application/json');
126
- const respond = (ok, msg) => {
127
- if (wantsJson) {
128
- res.writeHead(ok ? 200 : 400, { 'Content-Type': 'application/json' });
129
- res.end(JSON.stringify({ ok, error: msg || undefined }));
130
- } else {
131
- res.writeHead(200, { 'Content-Type': 'text/html' });
132
- res.end(resultPage(ok, msg));
133
- }
134
- };
135
-
136
- if (error) {
137
- respond(false, error);
138
- server.close();
139
- rejectToken(new Error(error));
140
- return;
141
- }
142
-
143
- // CSRF check: state token must match what the CLI generated
144
- if (state !== expectedState) {
145
- respond(false, 'State mismatch — possible CSRF attack. Please try again.');
146
- server.close();
147
- rejectToken(new Error('State mismatch'));
148
- return;
149
- }
150
-
151
- if (!token) {
152
- respond(false, 'No API key received.');
153
- server.close();
154
- rejectToken(new Error('No API key received'));
155
- return;
156
- }
157
-
158
- respond(true);
159
- server.close();
160
- resolveToken({ token });
161
- });
162
-
163
- // Port 0 = OS assigns a random available port. Bound to 127.0.0.1 only.
164
- server.listen(0, '127.0.0.1', () => {
165
- const port = server.address().port;
166
-
167
- const timeout = setTimeout(() => {
168
- server.close();
169
- rejectToken(new Error('Authentication timed out (5 min). Please try again.'));
170
- }, 5 * 60 * 1000);
171
-
172
- tokenPromise.finally(() => clearTimeout(timeout));
173
-
174
- resolveSetup({ port, promise: tokenPromise });
175
- });
176
- });
177
- }
178
-
179
- function resultPage(success, message) {
180
- const title = success ? 'Authenticated' : 'Authentication failed';
181
- const subtitle = success
182
- ? 'You can close this tab and return to your terminal.'
183
- : message || 'Something went wrong.';
184
-
185
- const logoSvg = `<svg viewBox="0 0 205 199" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M-4.33e-06 99.93C-4.96e-06 85.57 11.64 73.93 26 73.93c7.88 0 14.94 3.51 19.71 9.04 5.25 6.09 11.36 12.6 19.37 13.33l3.92.36v-.01c.03.01.06.01.09.01L72 96.93c12.69 0 23.98-10.28 24-22.96-.01-7.36-3.48-13.91-8.87-18.11l-1.08-.76c-6.52-4.6-15.3-3.65-23.19-2.46-7.28 1.1-14.97-.88-20.96-6.08C31.06 37.13 29.91 20.71 39.33 9.87 48.75-.96 65.18-2.11 76.01 7.31c5.99 5.21 9.02 12.55 8.94 19.91-.09 7.97.2 16.8 5.66 22.62l.94 1 .11.1.14.12c.22.19.45.37.68.55l.17.13c.25.18.5.36.75.53.64.42 1.31.8 2.01 1.14.48.23.98.44 1.49.62.21.07.42.14.63.21.32.1.64.19.97.27.4.1.8.18 1.2.25.34.06.69.11 1.03.14.58.06 1.17.09 1.77.09 4.2 0 8.03-1.57 10.95-4.16l.94-1c5.46-5.81 5.75-14.65 5.66-22.62-.08-7.36 2.95-14.71 8.94-19.91C139.82-2.11 156.25-.96 165.67 9.87c9.42 10.84 8.27 27.26-2.56 36.68-5.99 5.21-13.69 7.18-20.96 6.08-7.88-1.19-16.67-2.14-23.19 2.46l-1.08.76c-5.39 4.2-8.86 10.75-8.87 18.11.02 12.69 11.31 22.96 24 22.96l2.91-.26.09-.02v.01l3.93-.36c8.01-.73 14.12-7.23 19.37-13.33 4.77-5.54 11.83-9.04 19.71-9.04C193.36 73.93 205 85.57 205 99.93v.07c0 .01 0 .02 0 .03 0 14.36-11.64 26-26 26-7.88 0-14.94-3.51-19.71-9.04-5.25-6.09-11.36-12.6-19.37-13.33l-3.93-.36v.01c-.03-.01-.06-.01-.09-.01L133 103c-12.69 0-23.98 10.28-24 22.97.01 7.36 3.48 13.91 8.87 18.11l1.08.76c6.52 4.6 15.3 3.65 23.19 2.46 7.28-1.1 14.97.88 20.96 6.08 10.84 9.42 11.98 25.84 2.56 36.68-9.42 10.84-25.84 11.98-36.68 2.56-5.99-5.21-9.02-12.55-8.94-19.91.09-7.97-.2-16.8-5.66-22.62l-.94-1c-2.91-2.59-6.75-4.16-10.95-4.16-.6 0-1.19.03-1.77.09-.35.04-.69.09-1.03.14-.34.07-.69.15-1.03.25-.33.08-.65.17-.97.28-.21.07-.42.14-.62.21-.51.18-1.01.39-1.49.62-.7.33-1.37.71-2.01 1.14-.26.17-.5.35-.75.53l-.17.13a11 11 0 0 0-.68.55l-.14.12-.11.1-.94 1.01c-5.46 5.81-5.75 14.65-5.66 22.62.08 7.36-2.95 14.71-8.94 19.91-10.84 9.42-27.26 8.27-36.68-2.56-9.42-10.84-8.27-27.26 2.56-36.68 5.99-5.21 13.69-7.18 20.96-6.08 7.88 1.19 16.67 2.14 23.19-2.46l1.08-.76c5.39-4.2 8.86-10.75 8.87-18.11-.02-12.69-11.31-22.97-24-22.97l-2.91.27c-.03 0-.06.01-.09.01v-.01l-3.93.36c-8.01.73-14.12 7.23-19.37 13.33C40.94 122.49 33.88 126 26 126 11.64 126 0 114.36 0 100l-4.33e-06-.07Z" fill="#FF5C28"/></svg>`;
186
-
187
- const statusIcon = success
188
- ? `<div class="icon success"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#16a34a" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg></div>`
189
- : `<div class="icon error"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg></div>`;
190
-
191
- return `<!DOCTYPE html><html lang="en"><head>
192
- <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
193
- <title>Subconscious CLI</title>
194
- <link rel="preconnect" href="https://fonts.googleapis.com">
195
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
196
- <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600&display=swap" rel="stylesheet">
197
- <style>
198
- *{margin:0;padding:0;box-sizing:border-box}
199
- body{font-family:'Manrope',system-ui,sans-serif;min-height:100vh;background:#F6F3EF;color:#111}
200
- header{display:flex;align-items:center;gap:10px;padding:20px 24px}
201
- header svg{width:24px;height:24px}
202
- header span{font-size:15px;font-weight:600;letter-spacing:-.01em}
203
- main{display:flex;align-items:center;justify-content:center;min-height:calc(100vh - 160px)}
204
- .wrap{width:100%;max-width:400px;padding:0 24px}
205
- .label{text-align:center;font-size:11px;font-weight:500;text-transform:uppercase;
206
- letter-spacing:.1em;color:#9ca3af;margin-bottom:16px}
207
- .card{background:#fff;border:1px solid rgba(0,0,0,.08);border-radius:12px;
208
- padding:32px;text-align:center}
209
- .icon{width:40px;height:40px;border-radius:50%;display:flex;align-items:center;
210
- justify-content:center;margin:0 auto 16px}
211
- .icon.success{background:#f0fdf4}
212
- .icon.error{background:#fef2f2}
213
- h1{font-size:15px;font-weight:600;margin-bottom:4px;letter-spacing:-.01em}
214
- .sub{color:#6b7280;font-size:13px;line-height:1.6;max-width:280px;margin:0 auto}
215
- .footer{text-align:center;margin-top:16px;font-size:11px;color:#9ca3af}
216
- </style></head><body>
217
- <header>${logoSvg}<span>Subconscious</span></header>
218
- <main><div class="wrap">
219
- <div class="label">CLI Authentication</div>
220
- <div class="card">
221
- ${statusIcon}
222
- <h1>${title}</h1>
223
- <p class="sub">${subtitle}</p>
224
- </div>
225
- <div class="footer">subconscious.dev</div>
226
- </div></main>
227
- </body></html>`;
228
- }
229
-
230
- // ── Commands ────────────────────────────────────────────────────────────
231
-
232
- async function loginCommand() {
233
- // Environment variable takes precedence over config file
234
- const existingConfig = await loadConfig();
235
- const existingKey =
236
- process.env.SUBCONSCIOUS_API_KEY || existingConfig.subconscious_api_key;
237
-
238
- if (existingKey) {
239
- const masked = existingKey.slice(0, 8) + '...' + existingKey.slice(-4);
240
- console.log(`\n${c.yellow}Already logged in.${c.reset}`);
241
- console.log(` Key: ${c.dim}${masked}${c.reset}`);
242
- console.log(
243
- `\n Run ${c.cyan}subconscious-cli logout${c.reset} first to switch accounts.\n`,
244
- );
245
- return;
246
- }
247
-
248
- console.log();
249
- console.log(
250
- ` ${c.magenta}${c.bold}Subconscious${c.reset} ${c.dim}— CLI Login${c.reset}`,
251
- );
252
- console.log();
253
-
254
- const state = crypto.randomBytes(16).toString('hex');
255
- const { port, promise } = await startCallbackServer(state);
256
-
257
- const authUrl = `${PLATFORM_URL}/cli/auth?port=${port}&state=${state}`;
258
-
259
- console.log(` ${c.dim}Opening browser to sign in...${c.reset}`);
260
- console.log();
261
- console.log(` ${c.dim}If it doesn't open, visit:${c.reset}`);
262
- console.log(` ${c.underline}${c.cyan}${authUrl}${c.reset}`);
263
- console.log();
264
-
265
- openBrowser(authUrl);
266
-
267
- // Spinner while waiting
268
- const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
269
- let i = 0;
270
- const spinner = setInterval(() => {
271
- process.stdout.write(
272
- `\r ${c.cyan}${frames[i++ % frames.length]}${c.reset} Waiting for authentication...`,
273
- );
274
- }, 80);
275
-
276
- process.on('SIGINT', () => {
277
- clearInterval(spinner);
278
- process.stdout.write('\r' + ' '.repeat(50) + '\r');
279
- console.log(`\n ${c.dim}Login cancelled.${c.reset}\n`);
280
- process.exit(0);
281
- });
282
- try {
283
- const result = await promise;
284
- clearInterval(spinner);
285
- process.stdout.write('\r' + ' '.repeat(50) + '\r');
286
-
287
- const config = await loadConfig();
288
- config.subconscious_api_key = result.token;
289
- await saveConfig(config);
290
-
291
- const masked = result.token.slice(0, 8) + '...' + result.token.slice(-4);
292
- console.log(` ${c.green}${c.bold}✓ Logged in successfully!${c.reset}`);
293
- console.log(` ${c.dim}Key: ${masked}${c.reset}`);
294
- console.log(` ${c.dim}Saved to ~/.subcon/config.json${c.reset}`);
295
- console.log();
296
- } catch (error) {
297
- clearInterval(spinner);
298
- process.stdout.write('\r' + ' '.repeat(50) + '\r');
299
- console.error(` ${c.red}✗ ${error.message}${c.reset}\n`);
300
- process.exit(1);
301
- }
302
- }
303
-
304
- async function logoutCommand() {
305
- const config = await loadConfig();
306
-
307
- if (!config.subconscious_api_key) {
308
- console.log(`\n ${c.dim}Not logged in.${c.reset}\n`);
309
- return;
310
- }
311
-
312
- delete config.subconscious_api_key;
313
- await saveConfig(config);
314
-
315
- console.log(
316
- `\n ${c.green}✓${c.reset} Logged out. API key removed from ${c.dim}~/.subcon/config.json${c.reset}\n`,
317
- );
318
- }
319
-
320
- async function whoamiCommand() {
321
- const config = await loadConfig();
322
- const envKey = process.env.SUBCONSCIOUS_API_KEY;
323
- const savedKey = config.subconscious_api_key;
324
- const key = envKey || savedKey;
325
-
326
- if (!key) {
327
- console.log(`\n ${c.dim}Not logged in.${c.reset}`);
328
- console.log(
329
- ` Run ${c.cyan}subconscious-cli login${c.reset} to get started.\n`,
330
- );
331
- return;
332
- }
333
-
334
- const masked = key.slice(0, 8) + '...' + key.slice(-4);
335
- const source = envKey
336
- ? 'SUBCONSCIOUS_API_KEY env var'
337
- : '~/.subcon/config.json';
338
-
339
- console.log();
340
-
341
- // Validate the key against the server; falls back to offline display if unreachable
342
- try {
343
- const res = await fetch(`${PLATFORM_URL}/api/cli/whoami`, {
344
- headers: { Authorization: `Bearer ${key}` },
345
- signal: AbortSignal.timeout(5000),
346
- });
347
-
348
- if (res.ok) {
349
- const data = await res.json();
350
- console.log(` ${c.green}✓ Authenticated${c.reset}`);
351
- if (data.organization) {
352
- console.log(` ${c.dim}Org: ${c.reset}${data.organization}`);
353
- }
354
- console.log(` ${c.dim}Key: ${masked}${c.reset}`);
355
- console.log(` ${c.dim}Source: ${source}${c.reset}`);
356
- } else {
357
- console.log(` ${c.red}✗ Key is invalid or revoked${c.reset}`);
358
- console.log(` ${c.dim}Key: ${masked}${c.reset}`);
359
- console.log(` ${c.dim}Source: ${source}${c.reset}`);
360
- console.log();
361
- console.log(
362
- ` Run ${c.cyan}subconscious-cli logout${c.reset} then ${c.cyan}subconscious-cli login${c.reset} to re-authenticate.`,
363
- );
364
- }
365
- } catch {
366
- console.log(` ${c.green}✓ Authenticated${c.reset} ${c.dim}(offline — key not verified)${c.reset}`);
367
- console.log(` ${c.dim}Key: ${masked}${c.reset}`);
368
- console.log(` ${c.dim}Source: ${source}${c.reset}`);
369
- }
370
-
371
- console.log();
372
- }
373
-
374
- // ── Help & entry ────────────────────────────────────────────────────────
13
+ import { c } from './colors.js';
14
+ import { loginCommand, logoutCommand, whoamiCommand } from './auth.js';
15
+ import { resolveAgent, runAgent, agentList } from './agents.js';
375
16
 
376
17
  function printHelp() {
18
+ const agents = agentList()
19
+ .map(({ name, alias }) => ` ${c.cyan}${alias.padEnd(13)}${c.reset}${c.dim}Launch ${name}${c.reset}`)
20
+ .join('\n');
21
+
377
22
  console.log(`
378
23
  ${c.magenta}${c.bold}Subconscious CLI${c.reset}
379
24
 
380
25
  ${c.bold}Usage${c.reset}
381
- ${c.cyan}subconscious-cli${c.reset} <command>
26
+ ${c.cyan}subconscious${c.reset} <command> [...args]
27
+
28
+ ${c.bold}Auth${c.reset}
29
+ ${c.cyan}login${c.reset} Authenticate and save your API key
30
+ ${c.cyan}logout${c.reset} Remove saved credentials
31
+ ${c.cyan}whoami${c.reset} Show current authentication status
382
32
 
383
- ${c.bold}Commands${c.reset}
384
- ${c.cyan}login${c.reset} Authenticate and save your API key
385
- ${c.cyan}logout${c.reset} Remove saved credentials
386
- ${c.cyan}whoami${c.reset} Show current authentication status
33
+ ${c.bold}Coding agents${c.reset}
34
+ ${agents}
387
35
 
388
36
  ${c.bold}Options${c.reset}
37
+ ${c.dim}--model <id>${c.reset} Model to use (default subconscious/tim-qwen3.6-27b)
389
38
  ${c.dim}-h, --help${c.reset} Show this help
390
39
  ${c.dim}-v, --version${c.reset} Show version
391
40
 
392
- ${c.bold}Quick start${c.reset}
393
- ${c.dim}$${c.reset} npx subconscious-cli login
41
+ ${c.bold}Examples${c.reset}
42
+ ${c.dim}$${c.reset} subconscious login
43
+ ${c.dim}$${c.reset} subconscious claude-code
44
+ ${c.dim}$${c.reset} subconscious open-code --model subconscious/tim-qwen3.6-27b
45
+
46
+ ${c.dim}Anything after the agent name is forwarded to the underlying CLI.${c.reset}
394
47
  `);
395
48
  }
396
49
 
397
- const commands = { login: loginCommand, logout: logoutCommand, whoami: whoamiCommand };
50
+ const authCommands = { login: loginCommand, logout: logoutCommand, whoami: whoamiCommand };
398
51
 
399
52
  async function main() {
400
53
  const args = process.argv.slice(2);
@@ -412,14 +65,21 @@ async function main() {
412
65
  return;
413
66
  }
414
67
 
415
- const handler = commands[command];
416
- if (!handler) {
417
- console.error(`\n ${c.red}Unknown command: ${command}${c.reset}`);
418
- printHelp();
419
- process.exit(1);
68
+ const authHandler = authCommands[command];
69
+ if (authHandler) {
70
+ await authHandler(args.slice(1));
71
+ return;
420
72
  }
421
73
 
422
- await handler(args.slice(1));
74
+ const agent = resolveAgent(command);
75
+ if (agent) {
76
+ await runAgent(agent, args.slice(1));
77
+ return;
78
+ }
79
+
80
+ console.error(`\n ${c.red}Unknown command: ${command}${c.reset}`);
81
+ printHelp();
82
+ process.exit(1);
423
83
  }
424
84
 
425
85
  main().catch((error) => {
package/bin/colors.js ADDED
@@ -0,0 +1,12 @@
1
+ // ANSI color helpers shared across the CLI.
2
+ export const c = {
3
+ reset: '\x1b[0m',
4
+ bold: '\x1b[1m',
5
+ dim: '\x1b[2m',
6
+ cyan: '\x1b[36m',
7
+ green: '\x1b[32m',
8
+ red: '\x1b[31m',
9
+ yellow: '\x1b[33m',
10
+ magenta: '\x1b[35m',
11
+ underline: '\x1b[4m',
12
+ };
@@ -0,0 +1,158 @@
1
+ {
2
+ "_generated": "Source of truth: agents/registry.json. Do not edit by hand.",
3
+ "defaults": {
4
+ "model": "subconscious/tim-qwen3.6-27b",
5
+ "baseUrl": "https://api.subconscious.dev",
6
+ "baseUrlV1": "https://api.subconscious.dev/v1",
7
+ "keyPlaceholder": "your_key",
8
+ "platformUrl": "https://subconscious.dev/platform"
9
+ },
10
+ "agents": [
11
+ {
12
+ "id": "claude-code",
13
+ "exampleDir": "claude_code",
14
+ "name": "Claude Code",
15
+ "aliases": [
16
+ "claude",
17
+ "claudecode"
18
+ ],
19
+ "displayName": "Claude Code + Subconscious",
20
+ "description": "Run Claude Code on your hosted Subconscious model — no code changes, just env vars.",
21
+ "framework": "Claude Code",
22
+ "language": "shell",
23
+ "category": "coding-agent",
24
+ "tags": [
25
+ "local",
26
+ "cli",
27
+ "agents"
28
+ ],
29
+ "protocol": "anthropic",
30
+ "homepage": "https://docs.anthropic.com/en/docs/claude-code",
31
+ "bin": "claude",
32
+ "install": {
33
+ "darwin": "curl -fsSL https://claude.ai/install.sh | bash",
34
+ "linux": "curl -fsSL https://claude.ai/install.sh | bash",
35
+ "win32": "powershell -ExecutionPolicy Bypass -Command \"irm https://claude.ai/install.ps1 | iex\"",
36
+ "fallback": "npm i -g @anthropic-ai/claude-code"
37
+ },
38
+ "launch": "claude",
39
+ "env": {
40
+ "ANTHROPIC_BASE_URL": "{baseUrl}",
41
+ "ANTHROPIC_AUTH_TOKEN": "{apiKey}",
42
+ "ANTHROPIC_MODEL": "{model}",
43
+ "ANTHROPIC_SMALL_FAST_MODEL": "{model}",
44
+ "DISABLE_AUTO_COMPACT": "true"
45
+ }
46
+ },
47
+ {
48
+ "id": "opencode",
49
+ "exampleDir": "opencode",
50
+ "name": "OpenCode",
51
+ "aliases": [
52
+ "open-code"
53
+ ],
54
+ "displayName": "OpenCode + Subconscious",
55
+ "description": "Run OpenCode on your hosted Subconscious model — registered as a custom provider, injected non-invasively (nothing written to your OpenCode config).",
56
+ "framework": "OpenCode",
57
+ "language": "shell",
58
+ "category": "coding-agent",
59
+ "tags": [
60
+ "local",
61
+ "cli",
62
+ "agents"
63
+ ],
64
+ "protocol": "openai",
65
+ "homepage": "https://opencode.ai",
66
+ "bin": "opencode",
67
+ "install": {
68
+ "darwin": "npm i -g opencode-ai",
69
+ "linux": "npm i -g opencode-ai",
70
+ "win32": "npm i -g opencode-ai"
71
+ },
72
+ "launch": "opencode",
73
+ "configEnv": "OPENCODE_CONFIG_CONTENT",
74
+ "env": {
75
+ "SUBCONSCIOUS_API_KEY": "{apiKey}",
76
+ "OPENCODE_CONFIG_CONTENT": {
77
+ "$json": {
78
+ "$schema": "https://opencode.ai/config.json",
79
+ "provider": {
80
+ "subconscious": {
81
+ "npm": "@ai-sdk/openai-compatible",
82
+ "name": "Subconscious",
83
+ "options": {
84
+ "baseURL": "{baseUrlV1}",
85
+ "apiKey": "{env:SUBCONSCIOUS_API_KEY}"
86
+ },
87
+ "models": {
88
+ "{model}": {
89
+ "name": "Subconscious",
90
+ "tools": true
91
+ }
92
+ }
93
+ }
94
+ },
95
+ "model": "subconscious/{model}"
96
+ }
97
+ }
98
+ }
99
+ },
100
+ {
101
+ "id": "aider",
102
+ "exampleDir": "aider",
103
+ "name": "Aider",
104
+ "aliases": [],
105
+ "displayName": "Aider + Subconscious",
106
+ "description": "Run Aider on your hosted Subconscious model — no code changes, just env vars.",
107
+ "framework": "Aider",
108
+ "language": "shell",
109
+ "category": "coding-agent",
110
+ "tags": [
111
+ "local",
112
+ "cli",
113
+ "agents"
114
+ ],
115
+ "protocol": "openai",
116
+ "homepage": "https://aider.chat",
117
+ "bin": "aider",
118
+ "install": {
119
+ "darwin": "python3 -m pip install aider-install && aider-install",
120
+ "linux": "python3 -m pip install aider-install && aider-install",
121
+ "win32": "python -m pip install aider-install && aider-install"
122
+ },
123
+ "launch": "aider --model openai/{model}",
124
+ "env": {
125
+ "OPENAI_API_BASE": "{baseUrlV1}",
126
+ "OPENAI_API_KEY": "{apiKey}"
127
+ }
128
+ },
129
+ {
130
+ "id": "codex",
131
+ "exampleDir": "codex",
132
+ "name": "Codex CLI",
133
+ "aliases": [],
134
+ "displayName": "Codex + Subconscious",
135
+ "description": "Run the Codex CLI on your hosted Subconscious model via a custom OpenAI-compatible provider.",
136
+ "framework": "Codex",
137
+ "language": "shell",
138
+ "category": "coding-agent",
139
+ "tags": [
140
+ "local",
141
+ "cli",
142
+ "agents"
143
+ ],
144
+ "protocol": "openai",
145
+ "homepage": "https://developers.openai.com/codex",
146
+ "bin": "codex",
147
+ "install": {
148
+ "darwin": "npm i -g @openai/codex",
149
+ "linux": "npm i -g @openai/codex",
150
+ "win32": "npm i -g @openai/codex"
151
+ },
152
+ "launch": "codex -c model_providers.subconscious.name=Subconscious -c model_providers.subconscious.base_url={baseUrlV1} -c model_providers.subconscious.env_key=SUBCONSCIOUS_API_KEY -c model_provider=subconscious -c model={model}",
153
+ "env": {
154
+ "SUBCONSCIOUS_API_KEY": "{apiKey}"
155
+ }
156
+ }
157
+ ]
158
+ }
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "subconscious-cli",
3
- "version": "0.1.1",
4
- "description": "CLI for Subconscious — authenticate and manage your API keys",
3
+ "version": "0.2.1",
4
+ "description": "CLI for Subconscious — log in and launch coding agents (Claude Code, OpenCode, Aider, Codex) on your hosted models",
5
5
  "bin": {
6
+ "subconscious": "./bin/cli.js",
6
7
  "subconscious-cli": "./bin/cli.js"
7
8
  },
8
9
  "files": [
@@ -22,7 +23,13 @@
22
23
  "ai",
23
24
  "cli",
24
25
  "api-key",
25
- "authentication"
26
+ "authentication",
27
+ "coding-agent",
28
+ "claude-code",
29
+ "opencode",
30
+ "aider",
31
+ "codex",
32
+ "launcher"
26
33
  ],
27
34
  "author": "Subconscious Systems",
28
35
  "license": "MIT"