subconscious-cli 4.0.11 → 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 CHANGED
@@ -128,7 +128,8 @@ also require `jq`; Cursor and Copilot also require `curl`.
128
128
 
129
129
  `subc dsh` defaults to the DeepSeek Harness Web UI. It injects an ephemeral
130
130
  Cordis overlay containing the active gateway URL, selected model, token limits,
131
- client header, and every model returned by `/v1/models`; the API key remains in
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
132
133
  the process environment and is never written into the overlay. The temporary
133
134
  file is removed when Harness exits, and existing `$DSH_HOME` settings are not
134
135
  rewritten. Run a one-shot task with `subc dsh headless "PROMPT"`.
@@ -194,9 +195,10 @@ values. A command-line `--model` override has the highest model precedence.
194
195
 
195
196
  ## Models and endpoint overrides
196
197
 
197
- List the available models with `subc models`. The command fetches the active
198
- gateway's authenticated `/v1/models` catalog and falls back to the models
199
- packaged with the CLI when discovery is unavailable:
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:
200
202
 
201
203
  ```text
202
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.error) {
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 === 'gateway' &&
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
@@ -323,7 +323,12 @@ async function main() {
323
323
  selectedModel,
324
324
  fallbackModels: PACKAGED_MODELS,
325
325
  });
326
- modelsCommand(catalog.models, { selectedModel, error: catalog.error });
326
+ modelsCommand(catalog.models, {
327
+ selectedModel,
328
+ error: catalog.error,
329
+ source: catalog.source,
330
+ hasApiKey: Boolean(auth?.key),
331
+ });
327
332
  return;
328
333
  }
329
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
- * Live discovery is best-effort: launches should keep working with the
5
- * packaged registry when the gateway is offline or does not expose /v1/models.
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 = `${baseUrl.trim().replace(/\/+$/, '')}/v1/models`;
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
- const models = normalizeModelIds(payload.data.map((model) => model?.id));
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 fetchGatewayModels({ baseUrl, apiKey, fetchImpl, timeoutMs });
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
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.error) {
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.11",
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"