subconscious-cli 0.2.0 → 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/README.md CHANGED
@@ -24,15 +24,36 @@ subconscious open-code
24
24
  point the agent at Subconscious, and exec's the real CLI. Nothing is written to
25
25
  the agent's own config — the provider is passed in-memory for that run only.
26
26
 
27
- | Command | Launches | Requires (install) |
28
- | -------------------------- | ----------- | ------------------------------------------------------ |
29
- | `subconscious claude-code` | Claude Code | `npm i -g @anthropic-ai/claude-code` |
30
- | `subconscious open-code` | OpenCode | `npm i -g opencode-ai` |
31
- | `subconscious aider` | Aider | `python -m pip install aider-install && aider-install` |
32
- | `subconscious codex` | Codex CLI | `npm i -g @openai/codex` |
33
-
34
- If the underlying agent isn't installed, the CLI tells you the exact install
35
- command. Anything after the agent name is forwarded straight to it:
27
+ Install commands are **OS-specific** — the CLI picks the right one for your
28
+ platform automatically. The table below shows the macOS/Linux command; on
29
+ Windows the equivalent native installer is used instead.
30
+
31
+ | Command | Launches | Requires (install, macOS/Linux) |
32
+ | -------------------------- | ----------- | ------------------------------------------------------- |
33
+ | `subconscious claude-code` | Claude Code | `curl -fsSL https://claude.ai/install.sh \| bash` |
34
+ | `subconscious open-code` | OpenCode | `npm i -g opencode-ai` |
35
+ | `subconscious aider` | Aider | `python3 -m pip install aider-install && aider-install` |
36
+ | `subconscious codex` | Codex CLI | `npm i -g @openai/codex` |
37
+
38
+ Claude Code uses its **native installer** (the `curl`/`irm` script above), with
39
+ `npm i -g @anthropic-ai/claude-code` kept as an automatic fallback if the native
40
+ installer fails.
41
+
42
+ If the underlying agent isn't installed and you're in an interactive terminal,
43
+ the CLI offers to install it for you (just press Enter), runs the right
44
+ installer for your OS (trying the fallback if the primary fails), and launches
45
+ it once the install succeeds. In a non-interactive context (CI) it instead
46
+ prints the exact install command (and any fallback) and exits without running
47
+ anything.
48
+
49
+ Freshly-installed binaries (e.g. Aider and Claude Code land in `~/.local/bin`,
50
+ npm globals in the npm prefix) often aren't on your current shell's `PATH` yet.
51
+ The CLI looks in those common locations and launches the agent anyway. If it
52
+ still can't find the binary right after install, it tells you to **open a new
53
+ terminal** (or add the printed dir to `PATH`) and re-run the command — the
54
+ install itself succeeded.
55
+
56
+ Anything after the agent name is forwarded straight to it:
36
57
 
37
58
  ```bash
38
59
  subconscious claude-code --resume
@@ -49,6 +70,16 @@ subconscious open-code --model subconscious/tim-qwen3.6-27b
49
70
  export SUBCONSCIOUS_MODEL=subconscious/tim-qwen3.6-27b
50
71
  ```
51
72
 
73
+ ### Pointing at a different endpoint
74
+
75
+ By default the CLI targets `https://api.subconscious.dev`. Override the base URL
76
+ per run (or for a whole session) with `SUBCONSCIOUS_BASE_URL` — it flows to both
77
+ the Anthropic-style base and the OpenAI-compatible `/v1` base:
78
+
79
+ ```bash
80
+ SUBCONSCIOUS_BASE_URL=http://localhost:9999 subconscious claude-code
81
+ ```
82
+
52
83
  ## Auth commands
53
84
 
54
85
  ### `login`
package/bin/agents.js CHANGED
@@ -3,103 +3,84 @@
3
3
  *
4
4
  * `subconscious <agent>` resolves your saved API key, injects the env vars that
5
5
  * point the agent at your hosted Subconscious model, and exec's the real CLI —
6
- * nothing is written to the agent's own config. Each entry here mirrors the
7
- * matching example under `examples/<agent>` (its `subconscious.agent` block),
8
- * which stays the source of truth.
6
+ * nothing is written to the agent's own config.
7
+ *
8
+ * There is NO hardcoded agent data here: everything is read from
9
+ * `registry.generated.json` (shipped under `bin/`), which is generated from the
10
+ * single source of truth `agents/registry.json`. Run `pnpm generate` to update.
9
11
  */
10
12
 
11
- import { spawn } from 'node:child_process';
13
+ import { spawn, execFileSync } from 'node:child_process';
14
+ import { readFileSync } from 'node:fs';
12
15
  import fs from 'node:fs/promises';
13
16
  import { constants as fsConstants } from 'node:fs';
17
+ import os from 'node:os';
14
18
  import path from 'node:path';
19
+ import readline from 'node:readline';
15
20
  import { c } from './colors.js';
16
21
  import { getApiKey } from './auth.js';
17
22
 
18
- // Subconscious endpoints. The Anthropic-style base has no `/v1`; the
19
- // OpenAI-compatible base does.
20
- const API_BASE = 'https://api.subconscious.dev';
21
- const API_BASE_V1 = 'https://api.subconscious.dev/v1';
22
- const DEFAULT_MODEL = 'subconscious/tim-qwen3.6-27b';
23
+ // --- Registry (single source of truth, generated copy shipped in the package).
24
+ const registry = JSON.parse(
25
+ readFileSync(new URL('./registry.generated.json', import.meta.url), 'utf-8'),
26
+ );
27
+ const DEFAULTS = registry.defaults;
28
+
29
+ // --- Token substitution — same rules as scripts/lib/registry.js.
30
+ // Replaces {apiKey}, {model}, {baseUrl}, {baseUrlV1}. NEVER touches {env:...}.
31
+ const TOKENS = ['apiKey', 'model', 'baseUrl', 'baseUrlV1'];
32
+
33
+ function substituteString(str, ctx) {
34
+ let out = str;
35
+ for (const token of TOKENS) {
36
+ if (ctx[token] === undefined) continue;
37
+ out = out.split(`{${token}}`).join(ctx[token]);
38
+ }
39
+ return out;
40
+ }
41
+
42
+ function substitute(value, ctx) {
43
+ if (typeof value === 'string') return substituteString(value, ctx);
44
+ if (Array.isArray(value)) return value.map((v) => substitute(v, ctx));
45
+ if (value && typeof value === 'object') {
46
+ const keys = Object.keys(value);
47
+ if (keys.length === 1 && keys[0] === '$json') {
48
+ return JSON.stringify(substitute(value.$json, ctx));
49
+ }
50
+ const out = {};
51
+ for (const key of keys) out[substituteString(key, ctx)] = substitute(value[key], ctx);
52
+ return out;
53
+ }
54
+ return value;
55
+ }
23
56
 
24
57
  /**
25
- * Agent registry. Each entry:
26
- * name — display name
27
- * aliases accepted subcommands (first is canonical)
28
- * install how to install the underlying CLI (shown when it's missing)
29
- * bin — executable we exec and probe on PATH
30
- * env — (key, model) => extra env vars merged over process.env
31
- * args — (model) => array of args passed to `bin`
58
+ * Resolve the install command for the current OS from a per-OS install object.
59
+ * Falls back to the linux command, then any string value present, if the exact
60
+ * `process.platform` key is missing. Tolerates a legacy plain-string `install`.
61
+ * Returns `{ command, fallback }` where `fallback` may be undefined.
32
62
  */
33
- const AGENTS = [
34
- {
35
- name: 'Claude Code',
36
- aliases: ['claude-code', 'claude', 'claudecode'],
37
- install: 'npm i -g @anthropic-ai/claude-code',
38
- bin: 'claude',
39
- env: (key, model) => ({
40
- ANTHROPIC_BASE_URL: API_BASE,
41
- ANTHROPIC_AUTH_TOKEN: key,
42
- ANTHROPIC_MODEL: model,
43
- ANTHROPIC_SMALL_FAST_MODEL: model,
44
- // Let Subconscious manage context instead of client-side compaction.
45
- DISABLE_AUTO_COMPACT: 'true',
46
- }),
47
- args: () => [],
48
- },
49
- {
50
- name: 'OpenCode',
51
- aliases: ['open-code', 'opencode'],
52
- install: 'npm i -g opencode-ai',
53
- bin: 'opencode',
54
- env: (key, model) => ({
55
- SUBCONSCIOUS_API_KEY: key,
56
- // OpenCode deep-merges this at startup; nothing touches ~/.config/opencode.
57
- OPENCODE_CONFIG_CONTENT: JSON.stringify({
58
- $schema: 'https://opencode.ai/config.json',
59
- provider: {
60
- subconscious: {
61
- npm: '@ai-sdk/openai-compatible',
62
- name: 'Subconscious',
63
- options: { baseURL: API_BASE_V1, apiKey: '{env:SUBCONSCIOUS_API_KEY}' },
64
- models: { [model]: { name: 'Subconscious', tools: true } },
65
- },
66
- },
67
- model: `subconscious/${model}`,
68
- }),
69
- }),
70
- args: () => [],
71
- },
72
- {
73
- name: 'Aider',
74
- aliases: ['aider'],
75
- install: 'python -m pip install aider-install && aider-install',
76
- bin: 'aider',
77
- env: (key) => ({
78
- OPENAI_API_BASE: API_BASE_V1,
79
- OPENAI_API_KEY: key,
80
- }),
81
- args: (model) => ['--model', `openai/${model}`],
82
- },
83
- {
84
- name: 'Codex CLI',
85
- aliases: ['codex'],
86
- install: 'npm i -g @openai/codex',
87
- bin: 'codex',
88
- env: (key) => ({ SUBCONSCIOUS_API_KEY: key }),
89
- // `-c model=…` (not `--model`, which is codex's Ollama shortcut).
90
- args: (model) => [
91
- '-c', 'model_providers.subconscious.name=Subconscious',
92
- '-c', `model_providers.subconscious.base_url=${API_BASE_V1}`,
93
- '-c', 'model_providers.subconscious.env_key=SUBCONSCIOUS_API_KEY',
94
- '-c', 'model_provider=subconscious',
95
- '-c', `model=${model}`,
96
- ],
97
- },
98
- ];
63
+ function resolveInstall(install) {
64
+ if (typeof install === 'string') return { command: install, fallback: undefined };
65
+ if (!install || typeof install !== 'object') return { command: undefined, fallback: undefined };
66
+ const command =
67
+ install[process.platform] ||
68
+ install.linux ||
69
+ Object.values(install).find((v) => typeof v === 'string');
70
+ return { command, fallback: install.fallback };
71
+ }
99
72
 
73
+ // --- Build the in-memory registry + alias index.
74
+ // Each agent gets a resolved per-OS `install` (string) plus optional
75
+ // `installFallback`, while keeping the original per-OS object available.
76
+ const AGENTS = registry.agents.map((agent) => {
77
+ const { command, fallback } = resolveInstall(agent.install);
78
+ return { ...agent, install: command, installFallback: fallback };
79
+ });
100
80
  const BY_ALIAS = new Map();
101
81
  for (const agent of AGENTS) {
102
- for (const alias of agent.aliases) BY_ALIAS.set(alias, agent);
82
+ BY_ALIAS.set(agent.id, agent);
83
+ for (const alias of agent.aliases || []) BY_ALIAS.set(alias, agent);
103
84
  }
104
85
 
105
86
  export function resolveAgent(name) {
@@ -107,16 +88,27 @@ export function resolveAgent(name) {
107
88
  }
108
89
 
109
90
  export function agentList() {
110
- return AGENTS.map((a) => ({ name: a.name, alias: a.aliases[0] }));
91
+ return AGENTS.map((a) => ({ name: a.name, alias: a.id }));
92
+ }
93
+
94
+ /**
95
+ * Resolve the substitution context for a launch:
96
+ * model — --model flag → SUBCONSCIOUS_MODEL → registry default
97
+ * baseUrl — SUBCONSCIOUS_BASE_URL → registry default
98
+ * baseUrlV1 — `${baseUrl}/v1` (so an override flows to both)
99
+ */
100
+ function buildContext(apiKey, model) {
101
+ const baseUrl = process.env.SUBCONSCIOUS_BASE_URL?.trim() || DEFAULTS.baseUrl;
102
+ return { apiKey, model, baseUrl, baseUrlV1: `${baseUrl}/v1` };
111
103
  }
112
104
 
113
105
  /**
114
106
  * Pull a `--model <value>` / `--model=<value>` flag out of the passthrough
115
107
  * args (so it sets the Subconscious model rather than reaching the agent).
116
- * Falls back to SUBCONSCIOUS_MODEL, then the default.
108
+ * Falls back to SUBCONSCIOUS_MODEL, then the registry default.
117
109
  */
118
110
  function extractModel(argv) {
119
- let model = process.env.SUBCONSCIOUS_MODEL?.trim() || DEFAULT_MODEL;
111
+ let model = process.env.SUBCONSCIOUS_MODEL?.trim() || DEFAULTS.model;
120
112
  const rest = [];
121
113
  for (let i = 0; i < argv.length; i++) {
122
114
  const a = argv[i];
@@ -137,25 +129,176 @@ function extractModel(argv) {
137
129
  return { model, rest };
138
130
  }
139
131
 
140
- /** Is `bin` an executable resolvable on PATH? */
141
- async function isOnPath(bin) {
142
- const dirs = (process.env.PATH || '').split(path.delimiter).filter(Boolean);
143
- const exts =
144
- process.platform === 'win32'
145
- ? (process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';')
146
- : [''];
132
+ /**
133
+ * Common locations a freshly-installed coding-agent binary lands in but which
134
+ * are often NOT on the current process's PATH (e.g. aider/claude install into
135
+ * `~/.local/bin`; npm globals into the npm prefix bin). Best-effort, deduped.
136
+ */
137
+ function candidateBinDirs() {
138
+ const home = os.homedir();
139
+ const dirs = [];
140
+
141
+ if (process.platform === 'win32') {
142
+ if (process.env.APPDATA) dirs.push(path.join(process.env.APPDATA, 'npm'));
143
+ if (process.env.USERPROFILE) {
144
+ dirs.push(path.join(process.env.USERPROFILE, '.local', 'bin'));
145
+ }
146
+ if (home) dirs.push(path.join(home, '.local', 'bin'));
147
+ } else {
148
+ dirs.push(path.join(home, '.local', 'bin'));
149
+ dirs.push('/opt/homebrew/bin');
150
+ dirs.push('/usr/local/bin');
151
+ }
152
+
153
+ // npm global bin (best-effort — npm may be absent).
154
+ try {
155
+ const prefix = execFileSync('npm', ['prefix', '-g'], {
156
+ encoding: 'utf-8',
157
+ stdio: ['ignore', 'pipe', 'ignore'],
158
+ }).trim();
159
+ if (prefix) {
160
+ dirs.push(process.platform === 'win32' ? prefix : path.join(prefix, 'bin'));
161
+ }
162
+ } catch {
163
+ // npm not available — skip.
164
+ }
165
+
166
+ // Dedupe, drop empties.
167
+ return [...new Set(dirs.filter(Boolean))];
168
+ }
169
+
170
+ /** Executable extensions to probe (Windows uses PATHEXT). */
171
+ function binExts() {
172
+ return process.platform === 'win32'
173
+ ? (process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';')
174
+ : [''];
175
+ }
176
+
177
+ /**
178
+ * Resolve `bin` against PATH plus the candidate bin dirs. Returns the directory
179
+ * containing the executable if found, otherwise null. Searching the candidate
180
+ * dirs lets us find binaries installed this session that aren't on PATH yet.
181
+ */
182
+ async function resolveBinPath(bin) {
183
+ const pathDirs = (process.env.PATH || '').split(path.delimiter).filter(Boolean);
184
+ const dirs = [...pathDirs, ...candidateBinDirs()];
185
+ const exts = binExts();
147
186
  for (const dir of dirs) {
148
187
  for (const ext of exts) {
149
188
  const candidate = path.join(dir, bin + ext);
150
189
  try {
151
190
  await fs.access(candidate, fsConstants.F_OK);
152
- return true;
191
+ return dir;
153
192
  } catch {
154
193
  // keep scanning
155
194
  }
156
195
  }
157
196
  }
158
- return false;
197
+ return null;
198
+ }
199
+
200
+ /**
201
+ * Build a PATH string with `extraDirs` prepended (deduped against PATH).
202
+ * Returns the augmented PATH value for use in a child env.
203
+ */
204
+ function augmentPath(extraDirs) {
205
+ const current = (process.env.PATH || '').split(path.delimiter).filter(Boolean);
206
+ const seen = new Set(current);
207
+ const prepend = extraDirs.filter((d) => d && !seen.has(d));
208
+ return [...prepend, ...current].join(path.delimiter);
209
+ }
210
+
211
+ /** Ask a yes/no question on the TTY. Empty answer counts as yes. */
212
+ function askYesNo(question) {
213
+ return new Promise((resolve) => {
214
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
215
+ rl.question(question, (answer) => {
216
+ rl.close();
217
+ const a = answer.trim().toLowerCase();
218
+ resolve(a === '' || a === 'y' || a === 'yes');
219
+ });
220
+ });
221
+ }
222
+
223
+ /** Run the agent's install command (may contain `&&`, so shell:true). */
224
+ function runInstaller(install) {
225
+ return new Promise((resolve) => {
226
+ const child = spawn(install, { shell: true, stdio: 'inherit' });
227
+ child.on('error', () => resolve(false));
228
+ child.on('exit', (code) => resolve(code === 0));
229
+ });
230
+ }
231
+
232
+ /** Print the resolved install command (plus any fallback) for an agent. */
233
+ function printInstallCommands(agent) {
234
+ console.error(` ${c.cyan}${agent.install}${c.reset}`);
235
+ if (agent.installFallback) {
236
+ console.error(` ${c.dim}or, as a fallback:${c.reset}`);
237
+ console.error(` ${c.cyan}${agent.installFallback}${c.reset}`);
238
+ }
239
+ console.error('');
240
+ }
241
+
242
+ /**
243
+ * Ensure the agent's binary is resolvable. If missing:
244
+ * - interactive TTY: offer to run the per-OS installer (with fallback), then
245
+ * re-resolve against PATH + candidate dirs.
246
+ * - non-interactive: print the resolved install command (+ fallback) and
247
+ * exit 127 without running anything.
248
+ *
249
+ * Returns the directory containing the bin (to prepend to the child's PATH) on
250
+ * success. May exit the process on failure or when manual action is needed.
251
+ */
252
+ async function ensureInstalled(agent) {
253
+ const existing = await resolveBinPath(agent.bin);
254
+ if (existing) return existing;
255
+
256
+ const interactive = process.stdin.isTTY && process.stdout.isTTY;
257
+
258
+ if (!interactive) {
259
+ console.error(
260
+ `\n ${c.red}${agent.name} isn't installed${c.reset} ${c.dim}(\`${agent.bin}\` not found on PATH).${c.reset}`,
261
+ );
262
+ console.error(` Install it with:\n`);
263
+ printInstallCommands(agent);
264
+ process.exit(127);
265
+ }
266
+
267
+ console.error(`\n ${c.bold}${agent.name}${c.reset} isn't installed.`);
268
+ const ok = await askYesNo(` Install it now? ${c.dim}[Y/n]${c.reset} `);
269
+ if (!ok) {
270
+ console.error(`\n No problem. Install it yourself with:\n`);
271
+ printInstallCommands(agent);
272
+ process.exit(127);
273
+ }
274
+
275
+ console.error(`\n ${c.dim}Running ${c.reset}${c.cyan}${agent.install}${c.reset}\n`);
276
+ let installed = await runInstaller(agent.install);
277
+
278
+ // Primary failed and a fallback exists — try it once.
279
+ if (!installed && agent.installFallback) {
280
+ console.error(
281
+ `\n ${c.dim}That didn't work. Trying the fallback: ${c.reset}${c.cyan}${agent.installFallback}${c.reset}\n`,
282
+ );
283
+ installed = await runInstaller(agent.installFallback);
284
+ }
285
+
286
+ if (!installed) {
287
+ console.error(`\n ${c.red}Install failed.${c.reset} Try it manually:\n`);
288
+ printInstallCommands(agent);
289
+ process.exit(127);
290
+ }
291
+
292
+ // PATH hardening: the freshly-installed binary is often not on the current
293
+ // process's PATH. Re-resolve against PATH + candidate dirs.
294
+ const found = await resolveBinPath(agent.bin);
295
+ if (found) return found;
296
+
297
+ console.error(
298
+ `\n ${c.dim}Installed ${agent.name}, but it isn't on this shell's PATH yet. ` +
299
+ `Open a new terminal (or add a bin dir to PATH) and re-run \`subconscious ${agent.id}\`.${c.reset}\n`,
300
+ );
301
+ process.exit(0);
159
302
  }
160
303
 
161
304
  /**
@@ -174,30 +317,32 @@ export async function runAgent(agent, argv) {
174
317
  process.exit(1);
175
318
  }
176
319
 
177
- if (!(await isOnPath(agent.bin))) {
178
- console.error(
179
- `\n ${c.red}${agent.name} isn't installed${c.reset} ${c.dim}(\`${agent.bin}\` not found on PATH).${c.reset}`,
180
- );
181
- console.error(` Install it with:\n`);
182
- console.error(` ${c.cyan}${agent.install}${c.reset}\n`);
183
- process.exit(127);
184
- }
320
+ const binDir = await ensureInstalled(agent);
321
+
322
+ const ctx = buildContext(auth.key, model);
323
+ const launch = substituteString(agent.launch, ctx);
324
+ const [bin, ...launchArgs] = launch.split(' ').filter(Boolean);
325
+ const envMap = substitute(agent.env, ctx);
185
326
 
186
- const env = { ...process.env, ...agent.env(auth.key, model) };
187
- const args = [...agent.args(model), ...rest];
327
+ // Prepend the resolved bin dir + candidate dirs to the child's PATH so the
328
+ // agent (and any subprocess it spawns) resolves correctly this session, even
329
+ // if it was installed into a dir not yet on the parent shell's PATH.
330
+ const extraDirs = [binDir, ...candidateBinDirs()].filter(Boolean);
331
+ const env = { ...process.env, ...envMap, PATH: augmentPath(extraDirs) };
332
+ const args = [...launchArgs, ...rest];
188
333
 
189
334
  console.log(
190
335
  ` ${c.dim}Launching ${c.reset}${c.bold}${agent.name}${c.reset} ${c.dim}on Subconscious ${c.reset}${c.dim}(${model})${c.reset}\n`,
191
336
  );
192
337
 
193
- const child = spawn(agent.bin, args, { stdio: 'inherit', env });
338
+ const child = spawn(bin, args, { stdio: 'inherit', env });
194
339
 
195
340
  child.on('error', (err) => {
196
341
  if (err.code === 'ENOENT') {
197
342
  console.error(
198
- `\n ${c.red}Could not launch \`${agent.bin}\`.${c.reset} Install it with:\n`,
343
+ `\n ${c.red}Could not launch \`${bin}\`.${c.reset} Install it with:\n`,
199
344
  );
200
- console.error(` ${c.cyan}${agent.install}${c.reset}\n`);
345
+ printInstallCommands(agent);
201
346
  process.exit(127);
202
347
  }
203
348
  console.error(`\n ${c.red}${err.message}${c.reset}\n`);
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "subconscious-cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "CLI for Subconscious — log in and launch coding agents (Claude Code, OpenCode, Aider, Codex) on your hosted models",
5
5
  "bin": {
6
6
  "subconscious": "./bin/cli.js",