subconscious-cli 4.0.10 → 4.0.12
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 +13 -4
- package/bin/agents.js +5 -3
- package/bin/cli.js +18 -11
- package/bin/models.js +87 -25
- package/bin/native/subc-tui-darwin-amd64 +0 -0
- package/bin/native/subc-tui-darwin-arm64 +0 -0
- package/bin/native/subc-tui-linux-amd64 +0 -0
- package/bin/native/subc-tui-linux-arm64 +0 -0
- package/bin/native/subc-tui-windows-amd64.exe +0 -0
- package/bin/native/subc-tui-windows-arm64.exe +0 -0
- package/bin/profiles.js +4 -1
- package/bin/tui.js +1 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -40,6 +40,13 @@ leaving the TUI.
|
|
|
40
40
|
`subc help` and `subc --help` continue to print script-friendly command help.
|
|
41
41
|
Non-interactive `subc` also prints regular help.
|
|
42
42
|
|
|
43
|
+
Print the installed CLI version without performing an update or gateway check:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
subc --version
|
|
47
|
+
subc -v
|
|
48
|
+
```
|
|
49
|
+
|
|
43
50
|
Every command accepts `help` as a subcommand. These only read the selected
|
|
44
51
|
profile; they do not authenticate, install, configure, or launch anything:
|
|
45
52
|
|
|
@@ -121,7 +128,8 @@ also require `jq`; Cursor and Copilot also require `curl`.
|
|
|
121
128
|
|
|
122
129
|
`subc dsh` defaults to the DeepSeek Harness Web UI. It injects an ephemeral
|
|
123
130
|
Cordis overlay containing the active gateway URL, selected model, token limits,
|
|
124
|
-
client header, and every model
|
|
131
|
+
client header, and every model from the live catalog (`/v1/models/available`
|
|
132
|
+
when a profile key is present, otherwise public `/v1/models`); the API key remains in
|
|
125
133
|
the process environment and is never written into the overlay. The temporary
|
|
126
134
|
file is removed when Harness exits, and existing `$DSH_HOME` settings are not
|
|
127
135
|
rewritten. Run a one-shot task with `subc dsh headless "PROMPT"`.
|
|
@@ -187,9 +195,10 @@ values. A command-line `--model` override has the highest model precedence.
|
|
|
187
195
|
|
|
188
196
|
## Models and endpoint overrides
|
|
189
197
|
|
|
190
|
-
List the available models with `subc models`.
|
|
191
|
-
|
|
192
|
-
|
|
198
|
+
List the available models with `subc models`. When the selected profile has an
|
|
199
|
+
API key, the command fetches the key-scoped `/v1/models/available` catalog. If
|
|
200
|
+
that request fails, it falls back to the public `/v1/models` fleet list, then
|
|
201
|
+
to the models packaged with the CLI:
|
|
193
202
|
|
|
194
203
|
```text
|
|
195
204
|
subconscious/glm-5.2 (default)
|
package/bin/agents.js
CHANGED
|
@@ -21,7 +21,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
21
21
|
import { c } from './colors.js';
|
|
22
22
|
import { getApiKey } from './auth.js';
|
|
23
23
|
import { profileSettingsForAgent, resolvedProfileValues } from './profiles.js';
|
|
24
|
-
import { resolveModelCatalog } from './models.js';
|
|
24
|
+
import { isLiveModelSource, PUBLIC_CATALOG_FALLBACK_MESSAGE, resolveModelCatalog } from './models.js';
|
|
25
25
|
|
|
26
26
|
// --- Registry (single source of truth, generated copy shipped in the package).
|
|
27
27
|
const registry = JSON.parse(
|
|
@@ -709,7 +709,9 @@ async function resolvedModelsForLaunch(profile, apiKey, selectedModel) {
|
|
|
709
709
|
selectedModel,
|
|
710
710
|
fallbackModels: PACKAGED_MODELS,
|
|
711
711
|
});
|
|
712
|
-
if (catalog.
|
|
712
|
+
if (catalog.source === 'public' && apiKey) {
|
|
713
|
+
console.error(` ${c.dim}${PUBLIC_CATALOG_FALLBACK_MESSAGE}${c.reset}\n`);
|
|
714
|
+
} else if (catalog.error) {
|
|
713
715
|
console.error(
|
|
714
716
|
` ${c.yellow}Could not fetch the live model catalog; using packaged defaults.${c.reset}`,
|
|
715
717
|
);
|
|
@@ -720,7 +722,7 @@ async function resolvedModelsForLaunch(profile, apiKey, selectedModel) {
|
|
|
720
722
|
|
|
721
723
|
export function selectLaunchModel(requestedModel, modelSource, catalog) {
|
|
722
724
|
const useLiveDefault =
|
|
723
|
-
catalog.source
|
|
725
|
+
isLiveModelSource(catalog.source) &&
|
|
724
726
|
catalog.models.length > 0 &&
|
|
725
727
|
!catalog.models.includes(requestedModel) &&
|
|
726
728
|
(modelSource === 'profile' || modelSource === 'default');
|
package/bin/cli.js
CHANGED
|
@@ -226,13 +226,22 @@ function requireNamedProfile(profile) {
|
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
async function main() {
|
|
229
|
-
const update = await showUpdateNotice();
|
|
230
|
-
if (update?.action === 'updated' || update?.action === 'cancel') return;
|
|
231
|
-
|
|
232
229
|
let parsed = extractProfile(process.argv.slice(2));
|
|
233
230
|
let { args, profileName, profileExplicit } = parsed;
|
|
234
231
|
let command = args[0];
|
|
235
232
|
|
|
233
|
+
// Version output must be fast and reliable even when npm or the gateway is
|
|
234
|
+
// unavailable, so handle it before the automatic update check.
|
|
235
|
+
if (command === '--version' || command === '-v') {
|
|
236
|
+
const pkgPath = new URL('../package.json', import.meta.url);
|
|
237
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
|
|
238
|
+
console.log(pkg.version);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const update = await showUpdateNotice();
|
|
243
|
+
if (update?.action === 'updated' || update?.action === 'cancel') return;
|
|
244
|
+
|
|
236
245
|
if (!command && process.stdin.isTTY === true && process.stdout.isTTY === true && process.env.TERM !== 'dumb') {
|
|
237
246
|
const selection = await runTui({ profileName });
|
|
238
247
|
if (!selection?.args?.length) return;
|
|
@@ -261,13 +270,6 @@ async function main() {
|
|
|
261
270
|
return;
|
|
262
271
|
}
|
|
263
272
|
|
|
264
|
-
if (command === '--version' || command === '-v') {
|
|
265
|
-
const pkgPath = new URL('../package.json', import.meta.url);
|
|
266
|
-
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
|
|
267
|
-
console.log(pkg.version);
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
273
|
if (command === 'setup') {
|
|
272
274
|
printRetiredSetup(args.slice(1));
|
|
273
275
|
process.exitCode = 1;
|
|
@@ -321,7 +323,12 @@ async function main() {
|
|
|
321
323
|
selectedModel,
|
|
322
324
|
fallbackModels: PACKAGED_MODELS,
|
|
323
325
|
});
|
|
324
|
-
modelsCommand(catalog.models, {
|
|
326
|
+
modelsCommand(catalog.models, {
|
|
327
|
+
selectedModel,
|
|
328
|
+
error: catalog.error,
|
|
329
|
+
source: catalog.source,
|
|
330
|
+
hasApiKey: Boolean(auth?.key),
|
|
331
|
+
});
|
|
325
332
|
return;
|
|
326
333
|
}
|
|
327
334
|
|
package/bin/models.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Resolve the model catalog exposed by an OpenAI-compatible gateway.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* When a profile (or agent) API key is present, prefer the key-scoped
|
|
5
|
+
* /v1/models/available list. If that request fails, fall back to the public
|
|
6
|
+
* /v1/models fleet catalog. Launches keep working with the packaged registry
|
|
7
|
+
* when the gateway is offline or returns nothing usable.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
const MODEL_ID_PATTERN = /^[-A-Za-z0-9._:/+]+$/;
|
|
9
11
|
export const DEFAULT_MODEL_FETCH_TIMEOUT_MS = 3000;
|
|
12
|
+
export const AVAILABLE_MODELS_PATH = '/v1/models/available';
|
|
13
|
+
export const PUBLIC_MODELS_PATH = '/v1/models';
|
|
14
|
+
export const PUBLIC_CATALOG_FALLBACK_MESSAGE =
|
|
15
|
+
'Provisioned catalog unavailable; showing the public model list.';
|
|
16
|
+
|
|
17
|
+
export function isLiveModelSource(source) {
|
|
18
|
+
return source === 'available' || source === 'public';
|
|
19
|
+
}
|
|
10
20
|
|
|
11
21
|
export function normalizeModelIds(modelIds = [], selectedModel) {
|
|
12
22
|
const models = [];
|
|
@@ -27,19 +37,37 @@ export function normalizeModelIds(modelIds = [], selectedModel) {
|
|
|
27
37
|
return models;
|
|
28
38
|
}
|
|
29
39
|
|
|
40
|
+
function gatewayOrigin(baseUrl) {
|
|
41
|
+
if (!baseUrl?.trim()) throw new Error('Gateway URL is not configured');
|
|
42
|
+
return baseUrl.trim().replace(/\/+$/, '');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function modelsEndpoint(baseUrl, path) {
|
|
46
|
+
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
47
|
+
return `${gatewayOrigin(baseUrl)}${normalizedPath}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
30
50
|
export async function fetchGatewayModels({
|
|
31
51
|
baseUrl,
|
|
32
52
|
apiKey,
|
|
53
|
+
path = PUBLIC_MODELS_PATH,
|
|
33
54
|
fetchImpl = globalThis.fetch,
|
|
34
55
|
timeoutMs = DEFAULT_MODEL_FETCH_TIMEOUT_MS,
|
|
35
56
|
}) {
|
|
36
|
-
if (!baseUrl?.trim()) throw new Error('Gateway URL is not configured');
|
|
37
|
-
if (!apiKey?.trim()) throw new Error('No API key is available for model discovery');
|
|
38
57
|
if (typeof fetchImpl !== 'function') {
|
|
39
58
|
throw new Error('This Node.js version does not support fetch');
|
|
40
59
|
}
|
|
41
60
|
|
|
42
|
-
const endpoint =
|
|
61
|
+
const endpoint = modelsEndpoint(baseUrl, path);
|
|
62
|
+
const headers = {
|
|
63
|
+
Accept: 'application/json',
|
|
64
|
+
'Cache-Control': 'no-cache, no-store',
|
|
65
|
+
Pragma: 'no-cache',
|
|
66
|
+
};
|
|
67
|
+
if (apiKey?.trim()) {
|
|
68
|
+
headers.Authorization = `Bearer ${apiKey.trim()}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
43
71
|
const controller = new AbortController();
|
|
44
72
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
45
73
|
timeout.unref?.();
|
|
@@ -47,12 +75,7 @@ export async function fetchGatewayModels({
|
|
|
47
75
|
try {
|
|
48
76
|
const response = await fetchImpl(endpoint, {
|
|
49
77
|
method: 'GET',
|
|
50
|
-
headers
|
|
51
|
-
Accept: 'application/json',
|
|
52
|
-
Authorization: `Bearer ${apiKey.trim()}`,
|
|
53
|
-
'Cache-Control': 'no-cache, no-store',
|
|
54
|
-
Pragma: 'no-cache',
|
|
55
|
-
},
|
|
78
|
+
headers,
|
|
56
79
|
cache: 'no-store',
|
|
57
80
|
signal: controller.signal,
|
|
58
81
|
});
|
|
@@ -66,9 +89,7 @@ export async function fetchGatewayModels({
|
|
|
66
89
|
throw new Error('Model discovery returned an invalid response');
|
|
67
90
|
}
|
|
68
91
|
|
|
69
|
-
|
|
70
|
-
if (!models.length) throw new Error('Model discovery returned no usable model IDs');
|
|
71
|
-
return models;
|
|
92
|
+
return normalizeModelIds(payload.data.map((model) => model?.id));
|
|
72
93
|
} catch (error) {
|
|
73
94
|
if (controller.signal.aborted) {
|
|
74
95
|
throw new Error(`Model discovery timed out after ${timeoutMs}ms`);
|
|
@@ -79,6 +100,33 @@ export async function fetchGatewayModels({
|
|
|
79
100
|
}
|
|
80
101
|
}
|
|
81
102
|
|
|
103
|
+
async function fetchPublicModels({ baseUrl, fetchImpl, timeoutMs }) {
|
|
104
|
+
const models = await fetchGatewayModels({
|
|
105
|
+
baseUrl,
|
|
106
|
+
path: PUBLIC_MODELS_PATH,
|
|
107
|
+
fetchImpl,
|
|
108
|
+
timeoutMs,
|
|
109
|
+
});
|
|
110
|
+
if (!models.length) throw new Error('Model discovery returned no usable model IDs');
|
|
111
|
+
return models;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function packagedCatalog(selectedModel, fallbackModels, error) {
|
|
115
|
+
return {
|
|
116
|
+
models: normalizeModelIds([selectedModel, ...fallbackModels], selectedModel),
|
|
117
|
+
source: 'packaged',
|
|
118
|
+
error,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function liveCatalog(models, selectedModel, source) {
|
|
123
|
+
return {
|
|
124
|
+
models: normalizeModelIds(models, selectedModel),
|
|
125
|
+
source,
|
|
126
|
+
error: null,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
82
130
|
export async function resolveModelCatalog({
|
|
83
131
|
baseUrl,
|
|
84
132
|
apiKey,
|
|
@@ -87,18 +135,32 @@ export async function resolveModelCatalog({
|
|
|
87
135
|
fetchImpl = globalThis.fetch,
|
|
88
136
|
timeoutMs = DEFAULT_MODEL_FETCH_TIMEOUT_MS,
|
|
89
137
|
}) {
|
|
138
|
+
const key = apiKey?.trim() || '';
|
|
139
|
+
|
|
140
|
+
if (key) {
|
|
141
|
+
try {
|
|
142
|
+
const discovered = await fetchGatewayModels({
|
|
143
|
+
baseUrl,
|
|
144
|
+
apiKey: key,
|
|
145
|
+
path: AVAILABLE_MODELS_PATH,
|
|
146
|
+
fetchImpl,
|
|
147
|
+
timeoutMs,
|
|
148
|
+
});
|
|
149
|
+
return liveCatalog(discovered, selectedModel, 'available');
|
|
150
|
+
} catch (availableError) {
|
|
151
|
+
try {
|
|
152
|
+
const discovered = await fetchPublicModels({ baseUrl, fetchImpl, timeoutMs });
|
|
153
|
+
return liveCatalog(discovered, selectedModel, 'public');
|
|
154
|
+
} catch {
|
|
155
|
+
return packagedCatalog(selectedModel, fallbackModels, availableError);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
90
160
|
try {
|
|
91
|
-
const discovered = await
|
|
92
|
-
return
|
|
93
|
-
models: normalizeModelIds(discovered, selectedModel),
|
|
94
|
-
source: 'gateway',
|
|
95
|
-
error: null,
|
|
96
|
-
};
|
|
161
|
+
const discovered = await fetchPublicModels({ baseUrl, fetchImpl, timeoutMs });
|
|
162
|
+
return liveCatalog(discovered, selectedModel, 'public');
|
|
97
163
|
} catch (error) {
|
|
98
|
-
return
|
|
99
|
-
models: normalizeModelIds([selectedModel, ...fallbackModels], selectedModel),
|
|
100
|
-
source: 'packaged',
|
|
101
|
-
error,
|
|
102
|
-
};
|
|
164
|
+
return packagedCatalog(selectedModel, fallbackModels, error);
|
|
103
165
|
}
|
|
104
166
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/bin/profiles.js
CHANGED
|
@@ -12,6 +12,7 @@ import os from 'node:os';
|
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import { readFileSync } from 'node:fs';
|
|
14
14
|
import { c } from './colors.js';
|
|
15
|
+
import { PUBLIC_CATALOG_FALLBACK_MESSAGE } from './models.js';
|
|
15
16
|
|
|
16
17
|
const registry = JSON.parse(
|
|
17
18
|
readFileSync(new URL('./registry.generated.json', import.meta.url), 'utf-8'),
|
|
@@ -818,7 +819,9 @@ export function modelsCommand(models = SUPPORTED_MODELS, options = {}) {
|
|
|
818
819
|
const suffix = model === selectedModel ? ` ${c.dim}(default)${c.reset}` : '';
|
|
819
820
|
console.log(` ${c.cyan}${model}${c.reset}${suffix}`);
|
|
820
821
|
}
|
|
821
|
-
if (options.
|
|
822
|
+
if (options.source === 'public' && options.hasApiKey) {
|
|
823
|
+
console.error(`\n ${c.dim}${PUBLIC_CATALOG_FALLBACK_MESSAGE}${c.reset}`);
|
|
824
|
+
} else if (options.error) {
|
|
822
825
|
console.error(
|
|
823
826
|
`\n ${c.yellow}Could not fetch the live model catalog; showing packaged defaults.${c.reset}`,
|
|
824
827
|
);
|
package/bin/tui.js
CHANGED
|
@@ -140,6 +140,7 @@ export async function createTuiState(profileName = DEFAULT_PROFILE, options = {}
|
|
|
140
140
|
RUNBOOK_DEFAULTS.GATEWAY_URL,
|
|
141
141
|
gatewayOverridden: Boolean(process.env.SUBCONSCIOUS_BASE_URL?.trim()),
|
|
142
142
|
modelError: catalog.error?.message || '',
|
|
143
|
+
modelSource: catalog.source,
|
|
143
144
|
agents: agentList().map((agent) => ({
|
|
144
145
|
command: agent.alias,
|
|
145
146
|
name: agent.name,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "subconscious-cli",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.12",
|
|
4
4
|
"description": "CLI for Subconscious — run Claude Code, Codex, OpenCode, DeepSeek Harness, Cursor, Copilot, and Pi",
|
|
5
5
|
"bin": {
|
|
6
6
|
"subc": "bin/cli.js"
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"build:tui:host": "node scripts/build-tui.js --host",
|
|
11
11
|
"test": "node --test",
|
|
12
12
|
"test:tui": "go -C tui test ./...",
|
|
13
|
-
"prepack": "npm run build:tui"
|
|
13
|
+
"prepack": "npm run build:tui",
|
|
14
|
+
"prepublishOnly": "node scripts/require-publish-script.js"
|
|
14
15
|
},
|
|
15
16
|
"files": [
|
|
16
17
|
"bin"
|