subconscious-cli 6.0.0 → 6.0.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 +16 -2
- package/bin/agents.js +210 -70
- package/bin/auth.js +80 -32
- package/bin/branding.js +3 -1
- package/bin/cli.js +28 -20
- package/bin/clipboard-image.js +22 -5
- package/bin/colors.js +1 -1
- package/bin/feedback.js +57 -18
- package/bin/model-capabilities.js +4 -1
- package/bin/models.js +22 -6
- 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/opencode-provider.js +27 -5
- package/bin/profiles.js +126 -44
- package/bin/registry.generated.json +1 -1
- package/bin/runbook/opencode/opencode-plugin.d.ts +9 -0
- package/bin/runbook/opencode/subconscious-compaction.ts +26 -26
- package/bin/runbook/pi/subconscious-compaction.ts +69 -49
- package/bin/sessions.js +126 -41
- package/bin/tui.js +91 -32
- package/bin/update-check.js +49 -20
- package/bin/upgrade.js +49 -18
- package/bin/usage.js +26 -14
- package/bin/windows/agents.js +135 -32
- package/bin/windows/claude.js +43 -14
- package/bin/windows/common.js +56 -19
- package/bin/windows/install.js +120 -32
- package/bin/windows/launch.js +211 -49
- package/bin/windows/pi-compaction.ts +68 -13
- package/bin/windows/process.js +198 -56
- package/bin/windows/setup.js +231 -56
- package/package.json +9 -3
package/bin/tui.js
CHANGED
|
@@ -6,22 +6,29 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
|
|
7
7
|
import { agentList } from './agents.js';
|
|
8
8
|
import { DEFAULT_PLATFORM_URL, getApiKey, getPlatformUrl } from './auth.js';
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
import {
|
|
10
|
+
isAbortError,
|
|
11
|
+
normalizeModelIds,
|
|
12
|
+
resolveModelCatalog,
|
|
13
|
+
} from './models.js';
|
|
12
14
|
import {
|
|
13
15
|
DEFAULT_PROFILE,
|
|
14
16
|
listProfiles,
|
|
15
17
|
loadProfile,
|
|
16
|
-
resolvedModelSetting,
|
|
17
|
-
RUNBOOK_DEFAULTS,
|
|
18
18
|
SUPPORTED_MODELS as PACKAGED_MODELS,
|
|
19
|
+
RUNBOOK_DEFAULTS,
|
|
20
|
+
resolvedModelSetting,
|
|
19
21
|
} from './profiles.js';
|
|
22
|
+
import { discoverSessions, SESSION_HARNESSES } from './sessions.js';
|
|
23
|
+
import { compareVersions, fetchLatestVersion } from './update-check.js';
|
|
20
24
|
|
|
21
25
|
const BIN_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
22
26
|
const TUI_SOURCE_DIR = path.resolve(BIN_DIR, '../tui');
|
|
23
27
|
|
|
24
|
-
export function nativeTargetName(
|
|
28
|
+
export function nativeTargetName(
|
|
29
|
+
platform = process.platform,
|
|
30
|
+
arch = process.arch,
|
|
31
|
+
) {
|
|
25
32
|
const goArch = { x64: 'amd64', arm64: 'arm64' }[arch];
|
|
26
33
|
if (!goArch || !['darwin', 'linux', 'win32'].includes(platform)) return null;
|
|
27
34
|
const goOS = platform === 'win32' ? 'windows' : platform;
|
|
@@ -34,10 +41,13 @@ export function isTuiResult(result) {
|
|
|
34
41
|
result !== null &&
|
|
35
42
|
typeof result === 'object' &&
|
|
36
43
|
(result.updatePrompt === true ||
|
|
37
|
-
(Array.isArray(result.args) &&
|
|
44
|
+
(Array.isArray(result.args) &&
|
|
45
|
+
result.args.every((arg) => typeof arg === 'string'))) &&
|
|
38
46
|
(result.baseUrl === undefined || typeof result.baseUrl === 'string') &&
|
|
39
|
-
(result.installedVersion === undefined ||
|
|
40
|
-
|
|
47
|
+
(result.installedVersion === undefined ||
|
|
48
|
+
typeof result.installedVersion === 'string') &&
|
|
49
|
+
(result.latestVersion === undefined ||
|
|
50
|
+
typeof result.latestVersion === 'string')
|
|
41
51
|
);
|
|
42
52
|
}
|
|
43
53
|
|
|
@@ -50,9 +60,15 @@ async function pathExists(file) {
|
|
|
50
60
|
}
|
|
51
61
|
}
|
|
52
62
|
|
|
53
|
-
export async function tuiSourceIsNewerThan(
|
|
63
|
+
export async function tuiSourceIsNewerThan(
|
|
64
|
+
binary,
|
|
65
|
+
sourceFile = path.join(TUI_SOURCE_DIR, 'cmd/subc-tui/main.go'),
|
|
66
|
+
) {
|
|
54
67
|
try {
|
|
55
|
-
const [binaryStat, sourceStat] = await Promise.all([
|
|
68
|
+
const [binaryStat, sourceStat] = await Promise.all([
|
|
69
|
+
fs.stat(binary),
|
|
70
|
+
fs.stat(sourceFile),
|
|
71
|
+
]);
|
|
56
72
|
return sourceStat.mtimeMs > binaryStat.mtimeMs;
|
|
57
73
|
} catch {
|
|
58
74
|
return false;
|
|
@@ -70,7 +86,11 @@ function sourceTuiCommand() {
|
|
|
70
86
|
export async function resolveTuiExecutable(options = {}) {
|
|
71
87
|
const override = options.binary || process.env.SUBC_TUI_BIN?.trim();
|
|
72
88
|
if (override) {
|
|
73
|
-
return {
|
|
89
|
+
return {
|
|
90
|
+
command: override,
|
|
91
|
+
args: options.binaryArgs || [],
|
|
92
|
+
cwd: undefined,
|
|
93
|
+
};
|
|
74
94
|
}
|
|
75
95
|
|
|
76
96
|
const hasSource = await pathExists(path.join(TUI_SOURCE_DIR, 'go.mod'));
|
|
@@ -79,7 +99,10 @@ export async function resolveTuiExecutable(options = {}) {
|
|
|
79
99
|
const packaged = path.join(BIN_DIR, 'native', target);
|
|
80
100
|
// Source checkouts keep a cached host binary for speed. If TUI source is
|
|
81
101
|
// newer, that cache is stale (for example it will reject --updates).
|
|
82
|
-
if (
|
|
102
|
+
if (
|
|
103
|
+
(await pathExists(packaged)) &&
|
|
104
|
+
!(hasSource && (await tuiSourceIsNewerThan(packaged)))
|
|
105
|
+
) {
|
|
83
106
|
return { command: packaged, args: [], cwd: undefined };
|
|
84
107
|
}
|
|
85
108
|
}
|
|
@@ -91,7 +114,9 @@ export async function resolveTuiExecutable(options = {}) {
|
|
|
91
114
|
}
|
|
92
115
|
|
|
93
116
|
function selectedModelFor(profile) {
|
|
94
|
-
return resolvedModelSetting(
|
|
117
|
+
return resolvedModelSetting(
|
|
118
|
+
process.env.SUBCONSCIOUS_MODEL || profile.values.MODEL,
|
|
119
|
+
);
|
|
95
120
|
}
|
|
96
121
|
|
|
97
122
|
function subagentModelFor(profile) {
|
|
@@ -181,16 +206,22 @@ export async function writeAtomicJson(file, value) {
|
|
|
181
206
|
}
|
|
182
207
|
}
|
|
183
208
|
|
|
184
|
-
export async function createLocalTuiState(
|
|
209
|
+
export async function createLocalTuiState(
|
|
210
|
+
profileName = DEFAULT_PROFILE,
|
|
211
|
+
options = {},
|
|
212
|
+
) {
|
|
185
213
|
const activeProfile = options.profile || (await loadProfile(profileName));
|
|
186
|
-
const names = [...new Set([profileName, ...(await listProfiles())])].sort(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
214
|
+
const names = [...new Set([profileName, ...(await listProfiles())])].sort(
|
|
215
|
+
(a, b) => {
|
|
216
|
+
if (a === profileName) return -1;
|
|
217
|
+
if (b === profileName) return 1;
|
|
218
|
+
return a.localeCompare(b);
|
|
219
|
+
},
|
|
220
|
+
);
|
|
191
221
|
const profiles = await Promise.all(
|
|
192
222
|
names.map(async (name) => {
|
|
193
|
-
const profile =
|
|
223
|
+
const profile =
|
|
224
|
+
name === profileName ? activeProfile : await loadProfile(name);
|
|
194
225
|
const auth = await getApiKey(profile);
|
|
195
226
|
return {
|
|
196
227
|
name,
|
|
@@ -210,7 +241,10 @@ export async function createLocalTuiState(profileName = DEFAULT_PROFILE, options
|
|
|
210
241
|
activeProfile: profileName,
|
|
211
242
|
profilePath: activeProfile.path,
|
|
212
243
|
profiles,
|
|
213
|
-
models: normalizeModelIds(
|
|
244
|
+
models: normalizeModelIds(
|
|
245
|
+
[requestedModel, ...PACKAGED_MODELS],
|
|
246
|
+
requestedModel,
|
|
247
|
+
),
|
|
214
248
|
selectedModel: requestedModel,
|
|
215
249
|
subagentModel: subagentModelFor(activeProfile),
|
|
216
250
|
gatewayUrl,
|
|
@@ -220,7 +254,8 @@ export async function createLocalTuiState(profileName = DEFAULT_PROFILE, options
|
|
|
220
254
|
gatewayOverridden: Boolean(process.env.SUBCONSCIOUS_BASE_URL?.trim()),
|
|
221
255
|
platformUrl,
|
|
222
256
|
savedPlatformUrl:
|
|
223
|
-
activeProfile.values.PLATFORM_URL?.trim().replace(/\/+$/, '') ||
|
|
257
|
+
activeProfile.values.PLATFORM_URL?.trim().replace(/\/+$/, '') ||
|
|
258
|
+
DEFAULT_PLATFORM_URL,
|
|
224
259
|
platformOverridden: Boolean(process.env.SUBCONSCIOUS_URL?.trim()),
|
|
225
260
|
modelError: '',
|
|
226
261
|
modelSource: 'packaged',
|
|
@@ -274,11 +309,13 @@ export async function loadRemoteTuiUpdates(state, options = {}) {
|
|
|
274
309
|
const fetchLatest = options.fetchLatestVersion || fetchLatestVersion;
|
|
275
310
|
const readApiKey = options.getApiKey || getApiKey;
|
|
276
311
|
const updateDisabled =
|
|
277
|
-
options.disableUpdateCheck ??
|
|
312
|
+
options.disableUpdateCheck ??
|
|
313
|
+
process.env.SUBC_DISABLE_UPDATE_CHECK?.trim() === '1';
|
|
278
314
|
|
|
279
315
|
const tasks = [
|
|
280
316
|
(async () => {
|
|
281
|
-
const profile =
|
|
317
|
+
const profile =
|
|
318
|
+
options.profile || (await loadProfile(state.activeProfile));
|
|
282
319
|
if (signal?.aborted) return;
|
|
283
320
|
const auth = await readApiKey(profile);
|
|
284
321
|
if (signal?.aborted) return;
|
|
@@ -330,7 +367,10 @@ export async function loadRemoteTuiUpdates(state, options = {}) {
|
|
|
330
367
|
});
|
|
331
368
|
if (signal?.aborted || !latest) return;
|
|
332
369
|
if (compareVersions(latest, state.version) > 0) {
|
|
333
|
-
await enqueuePatch({
|
|
370
|
+
await enqueuePatch({
|
|
371
|
+
updateAvailable: true,
|
|
372
|
+
latestVersion: latest,
|
|
373
|
+
});
|
|
334
374
|
}
|
|
335
375
|
} catch (error) {
|
|
336
376
|
if (isAbortError(error) || signal?.aborted) return;
|
|
@@ -347,7 +387,10 @@ export async function loadRemoteTuiUpdates(state, options = {}) {
|
|
|
347
387
|
}
|
|
348
388
|
}
|
|
349
389
|
|
|
350
|
-
export async function createTuiState(
|
|
390
|
+
export async function createTuiState(
|
|
391
|
+
profileName = DEFAULT_PROFILE,
|
|
392
|
+
options = {},
|
|
393
|
+
) {
|
|
351
394
|
const state = await createLocalTuiState(profileName, options);
|
|
352
395
|
let next = { ...state };
|
|
353
396
|
await loadRemoteTuiUpdates(state, {
|
|
@@ -383,7 +426,8 @@ export async function runTui(options = {}) {
|
|
|
383
426
|
if (!executable) return null;
|
|
384
427
|
|
|
385
428
|
const providedState = options.state;
|
|
386
|
-
const state =
|
|
429
|
+
const state =
|
|
430
|
+
providedState || (await createLocalTuiState(options.profileName, options));
|
|
387
431
|
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'subc-tui-'));
|
|
388
432
|
const statePath = path.join(tempDir, 'state.json');
|
|
389
433
|
const resultPath = path.join(tempDir, 'result.json');
|
|
@@ -403,20 +447,35 @@ export async function runTui(options = {}) {
|
|
|
403
447
|
|
|
404
448
|
const remote = providedState
|
|
405
449
|
? Promise.resolve()
|
|
406
|
-
: loadRemoteTuiUpdates(state, {
|
|
450
|
+
: loadRemoteTuiUpdates(state, {
|
|
451
|
+
...options,
|
|
452
|
+
signal: controller.signal,
|
|
453
|
+
writePatch,
|
|
454
|
+
});
|
|
407
455
|
|
|
408
456
|
try {
|
|
409
457
|
// This state intentionally contains only display data. API keys are used
|
|
410
458
|
// by the Node command engine and are never passed into the TUI process.
|
|
411
|
-
await fs.writeFile(statePath, `${JSON.stringify(state)}\n`, {
|
|
459
|
+
await fs.writeFile(statePath, `${JSON.stringify(state)}\n`, {
|
|
460
|
+
mode: 0o600,
|
|
461
|
+
});
|
|
412
462
|
const code = await spawnAndWait(
|
|
413
463
|
executable.command,
|
|
414
|
-
[
|
|
464
|
+
[
|
|
465
|
+
...executable.args,
|
|
466
|
+
'--state',
|
|
467
|
+
statePath,
|
|
468
|
+
'--result',
|
|
469
|
+
resultPath,
|
|
470
|
+
'--updates',
|
|
471
|
+
updatesPath,
|
|
472
|
+
],
|
|
415
473
|
{ cwd: executable.cwd, spawn: options.spawn, stdio: options.stdio },
|
|
416
474
|
);
|
|
417
475
|
controller.abort();
|
|
418
476
|
await remote.catch(() => {});
|
|
419
|
-
if (code !== 0)
|
|
477
|
+
if (code !== 0)
|
|
478
|
+
throw new Error(`Subconscious TUI exited with status ${code}`);
|
|
420
479
|
|
|
421
480
|
try {
|
|
422
481
|
const result = JSON.parse(await fs.readFile(resultPath, 'utf-8'));
|
package/bin/update-check.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
1
|
import { spawn } from 'node:child_process';
|
|
3
|
-
import
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
4
3
|
import readline from 'node:readline';
|
|
5
4
|
import { fileURLToPath } from 'node:url';
|
|
6
5
|
import { LOGO_ART_SMALL_LINES } from './branding.js';
|
|
7
6
|
import { c, colorEnabled } from './colors.js';
|
|
7
|
+
import { spawnWindows } from './windows/process.js';
|
|
8
8
|
|
|
9
9
|
export const PACKAGE_NAME = 'subconscious-cli';
|
|
10
10
|
export const UPDATE_CHECK_TIMEOUT_MS = 1500;
|
|
@@ -46,7 +46,9 @@ export function detectInstallTarget(
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
const marker = `/lib/node_modules/${PACKAGE_NAME}/`;
|
|
49
|
-
let prefix = normalized.includes(marker)
|
|
49
|
+
let prefix = normalized.includes(marker)
|
|
50
|
+
? normalized.slice(0, normalized.indexOf(marker))
|
|
51
|
+
: '';
|
|
50
52
|
if (!prefix && platform === 'win32') {
|
|
51
53
|
const windowsMarker = `/node_modules/${PACKAGE_NAME}/`;
|
|
52
54
|
if (normalized.includes(windowsMarker)) {
|
|
@@ -59,7 +61,14 @@ export function detectInstallTarget(
|
|
|
59
61
|
return {
|
|
60
62
|
command: 'npm',
|
|
61
63
|
args,
|
|
62
|
-
display: ['npm', ...args]
|
|
64
|
+
display: ['npm', ...args]
|
|
65
|
+
.map(
|
|
66
|
+
platform === 'win32'
|
|
67
|
+
? (value) =>
|
|
68
|
+
/^[-/@A-Za-z0-9._:]+$/.test(value) ? value : `"${value}"`
|
|
69
|
+
: shellQuote,
|
|
70
|
+
)
|
|
71
|
+
.join(' '),
|
|
63
72
|
prefix,
|
|
64
73
|
};
|
|
65
74
|
}
|
|
@@ -67,7 +76,9 @@ export function detectInstallTarget(
|
|
|
67
76
|
function parseVersion(version) {
|
|
68
77
|
const match = String(version)
|
|
69
78
|
.trim()
|
|
70
|
-
.match(
|
|
79
|
+
.match(
|
|
80
|
+
/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/,
|
|
81
|
+
);
|
|
71
82
|
if (!match) return null;
|
|
72
83
|
return {
|
|
73
84
|
core: match.slice(1, 4).map(Number),
|
|
@@ -87,7 +98,8 @@ function comparePrerelease(a, b) {
|
|
|
87
98
|
|
|
88
99
|
const aNumber = /^\d+$/.test(a[index]) ? Number(a[index]) : null;
|
|
89
100
|
const bNumber = /^\d+$/.test(b[index]) ? Number(b[index]) : null;
|
|
90
|
-
if (aNumber !== null && bNumber !== null)
|
|
101
|
+
if (aNumber !== null && bNumber !== null)
|
|
102
|
+
return Math.sign(aNumber - bNumber);
|
|
91
103
|
if (aNumber !== null) return -1;
|
|
92
104
|
if (bNumber !== null) return 1;
|
|
93
105
|
return a[index].localeCompare(b[index]);
|
|
@@ -109,7 +121,9 @@ export function compareVersions(a, b) {
|
|
|
109
121
|
}
|
|
110
122
|
|
|
111
123
|
export async function currentVersion() {
|
|
112
|
-
const pkg = JSON.parse(
|
|
124
|
+
const pkg = JSON.parse(
|
|
125
|
+
await fs.readFile(new URL('../package.json', import.meta.url), 'utf-8'),
|
|
126
|
+
);
|
|
113
127
|
return pkg.version;
|
|
114
128
|
}
|
|
115
129
|
|
|
@@ -134,7 +148,11 @@ export async function fetchLatestVersion(options = {}) {
|
|
|
134
148
|
try {
|
|
135
149
|
const aborted = new Promise((_, reject) => {
|
|
136
150
|
const fail = () => {
|
|
137
|
-
const error = new Error(
|
|
151
|
+
const error = new Error(
|
|
152
|
+
signal?.aborted
|
|
153
|
+
? 'Update check was cancelled'
|
|
154
|
+
: 'Update check timed out',
|
|
155
|
+
);
|
|
138
156
|
error.name = 'AbortError';
|
|
139
157
|
reject(error);
|
|
140
158
|
};
|
|
@@ -154,7 +172,8 @@ export async function fetchLatestVersion(options = {}) {
|
|
|
154
172
|
]);
|
|
155
173
|
if (!response.ok) throw new Error(`npm returned HTTP ${response.status}`);
|
|
156
174
|
const payload = await response.json();
|
|
157
|
-
if (!parseVersion(payload?.version))
|
|
175
|
+
if (!parseVersion(payload?.version))
|
|
176
|
+
throw new Error('npm returned an invalid version');
|
|
158
177
|
return payload.version;
|
|
159
178
|
} finally {
|
|
160
179
|
signal?.removeEventListener('abort', onAbort);
|
|
@@ -175,7 +194,10 @@ export function renderUpdateNotice(installedVersion, latestVersion) {
|
|
|
175
194
|
{ raw: '', value: '' },
|
|
176
195
|
{ raw: '', value: '' },
|
|
177
196
|
{ raw: '', value: '' },
|
|
178
|
-
{
|
|
197
|
+
{
|
|
198
|
+
raw: 'Subconscious CLI',
|
|
199
|
+
value: `${c.orange}${c.bold}Subconscious CLI${c.reset}`,
|
|
200
|
+
},
|
|
179
201
|
{ raw: 'Update available', value: `${c.bold}Update available${c.reset}` },
|
|
180
202
|
{ raw: '', value: '' },
|
|
181
203
|
{
|
|
@@ -198,7 +220,8 @@ export function renderUpdateNotice(installedVersion, latestVersion) {
|
|
|
198
220
|
}
|
|
199
221
|
|
|
200
222
|
export function renderUpdateOptions(selectedIndex = 0, versions = {}) {
|
|
201
|
-
const installCommand =
|
|
223
|
+
const installCommand =
|
|
224
|
+
versions.installCommand || detectInstallTarget().display;
|
|
202
225
|
const descriptions = [
|
|
203
226
|
`Runs \`${installCommand}\``,
|
|
204
227
|
versions.installedVersion
|
|
@@ -209,9 +232,7 @@ export function renderUpdateOptions(selectedIndex = 0, versions = {}) {
|
|
|
209
232
|
return UPDATE_ACTIONS.map((label, index) => {
|
|
210
233
|
const active = index === selectedIndex;
|
|
211
234
|
const pointer = active ? `${c.orange}›${c.reset}` : ' ';
|
|
212
|
-
const activeStyle = colorEnabled
|
|
213
|
-
? `${c.bgOrange}${c.black}`
|
|
214
|
-
: c.inverse;
|
|
235
|
+
const activeStyle = colorEnabled ? `${c.bgOrange}${c.black}` : c.inverse;
|
|
215
236
|
const option = active
|
|
216
237
|
? `${activeStyle}${c.bold} ${label} ${c.reset}`
|
|
217
238
|
: ` ${label} `;
|
|
@@ -278,7 +299,8 @@ export async function selectUpdateAction(options = {}) {
|
|
|
278
299
|
}
|
|
279
300
|
|
|
280
301
|
export async function installLatest(options = {}) {
|
|
281
|
-
const spawnImpl =
|
|
302
|
+
const spawnImpl =
|
|
303
|
+
options.spawnImpl || (process.platform === 'win32' ? spawnWindows : spawn);
|
|
282
304
|
const target = options.target || detectInstallTarget();
|
|
283
305
|
return new Promise((resolve) => {
|
|
284
306
|
const child = spawnImpl(target.command, target.args, {
|
|
@@ -325,8 +347,10 @@ export async function showUpdateNotice(options = {}) {
|
|
|
325
347
|
write(`\n${notice}\n\n`);
|
|
326
348
|
|
|
327
349
|
const interactive =
|
|
328
|
-
options.interactive ??
|
|
329
|
-
|
|
350
|
+
options.interactive ??
|
|
351
|
+
(process.stdin.isTTY === true && process.stderr.isTTY === true);
|
|
352
|
+
if (!interactive)
|
|
353
|
+
return { installedVersion, latestVersion, action: 'skip' };
|
|
330
354
|
|
|
331
355
|
const select = options.select || selectUpdateAction;
|
|
332
356
|
const installTarget = options.installTarget || detectInstallTarget();
|
|
@@ -341,10 +365,13 @@ export async function showUpdateNotice(options = {}) {
|
|
|
341
365
|
return { installedVersion, latestVersion, action };
|
|
342
366
|
}
|
|
343
367
|
|
|
344
|
-
write(
|
|
368
|
+
write(
|
|
369
|
+
`\n ${c.cyan}Updating ${PACKAGE_NAME} to ${latestVersion}...${c.reset}\n\n`,
|
|
370
|
+
);
|
|
345
371
|
const install =
|
|
346
372
|
options.install ||
|
|
347
|
-
((expectedVersion) =>
|
|
373
|
+
((expectedVersion) =>
|
|
374
|
+
installLatest({ expectedVersion, target: installTarget }));
|
|
348
375
|
const installed = await install(latestVersion);
|
|
349
376
|
if (installed) {
|
|
350
377
|
write(
|
|
@@ -353,7 +380,9 @@ export async function showUpdateNotice(options = {}) {
|
|
|
353
380
|
return { installedVersion, latestVersion, action: 'updated' };
|
|
354
381
|
}
|
|
355
382
|
|
|
356
|
-
write(
|
|
383
|
+
write(
|
|
384
|
+
`\n ${c.red}Update failed or the running installation stayed unchanged.${c.reset}\n`,
|
|
385
|
+
);
|
|
357
386
|
write(` Try the exact detected install target manually:\n\n`);
|
|
358
387
|
write(` ${c.cyan}${installTarget.display}${c.reset}\n\n`);
|
|
359
388
|
return { installedVersion, latestVersion, action: 'failed' };
|
package/bin/upgrade.js
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { spawn } from 'node:child_process';
|
|
9
|
-
import { runWindows } from './windows/process.js';
|
|
10
9
|
import fs from 'node:fs/promises';
|
|
11
10
|
import readline from 'node:readline';
|
|
12
11
|
import { fileURLToPath } from 'node:url';
|
|
13
12
|
import { c } from './colors.js';
|
|
14
13
|
import { detectInstallTarget } from './update-check.js';
|
|
14
|
+
import { runWindows } from './windows/process.js';
|
|
15
15
|
|
|
16
16
|
export const PACKAGE_NAME = 'subconscious-cli';
|
|
17
17
|
export const MIN_LOGIN_VERSION = '4.0';
|
|
@@ -20,7 +20,9 @@ export function parseUpgradeArgs(argv = []) {
|
|
|
20
20
|
const latest = argv.includes('--latest');
|
|
21
21
|
const unknown = argv.filter((arg) => arg !== '--latest');
|
|
22
22
|
if (unknown.length) {
|
|
23
|
-
throw new Error(
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Unknown argument: ${unknown[0]}\nUsage: subc upgrade [--latest]`,
|
|
25
|
+
);
|
|
24
26
|
}
|
|
25
27
|
return { latest };
|
|
26
28
|
}
|
|
@@ -30,13 +32,19 @@ export function detectInstallCommand(here = fileURLToPath(import.meta.url)) {
|
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export async function currentCliVersion() {
|
|
33
|
-
const pkg = JSON.parse(
|
|
35
|
+
const pkg = JSON.parse(
|
|
36
|
+
await fs.readFile(new URL('../package.json', import.meta.url), 'utf-8'),
|
|
37
|
+
);
|
|
34
38
|
return pkg.version;
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
export function compareVersions(a, b) {
|
|
38
|
-
const pa = String(a)
|
|
39
|
-
|
|
42
|
+
const pa = String(a)
|
|
43
|
+
.split('.')
|
|
44
|
+
.map((part) => Number.parseInt(part, 10) || 0);
|
|
45
|
+
const pb = String(b)
|
|
46
|
+
.split('.')
|
|
47
|
+
.map((part) => Number.parseInt(part, 10) || 0);
|
|
40
48
|
const n = Math.max(pa.length, pb.length);
|
|
41
49
|
for (let i = 0; i < n; i++) {
|
|
42
50
|
const da = pa[i] || 0;
|
|
@@ -48,14 +56,20 @@ export function compareVersions(a, b) {
|
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
export async function fetchLatestVersion(fetchImpl = fetch) {
|
|
51
|
-
const res = await fetchImpl(
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
const res = await fetchImpl(
|
|
60
|
+
`https://registry.npmjs.org/${PACKAGE_NAME}/latest`,
|
|
61
|
+
{
|
|
62
|
+
signal: AbortSignal.timeout(10000),
|
|
63
|
+
},
|
|
64
|
+
);
|
|
54
65
|
if (!res.ok) {
|
|
55
|
-
throw new Error(
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Could not look up ${PACKAGE_NAME} on npm (${res.status}).`,
|
|
68
|
+
);
|
|
56
69
|
}
|
|
57
70
|
const data = await res.json();
|
|
58
|
-
if (!data?.version)
|
|
71
|
+
if (!data?.version)
|
|
72
|
+
throw new Error(`npm did not return a version for ${PACKAGE_NAME}.`);
|
|
59
73
|
return data.version;
|
|
60
74
|
}
|
|
61
75
|
|
|
@@ -72,7 +86,10 @@ export function printLoginUpgradeWarning() {
|
|
|
72
86
|
|
|
73
87
|
function askYesNo(question) {
|
|
74
88
|
return new Promise((resolve) => {
|
|
75
|
-
const rl = readline.createInterface({
|
|
89
|
+
const rl = readline.createInterface({
|
|
90
|
+
input: process.stdin,
|
|
91
|
+
output: process.stdout,
|
|
92
|
+
});
|
|
76
93
|
rl.question(question, (answer) => {
|
|
77
94
|
rl.close();
|
|
78
95
|
const a = answer.trim().toLowerCase();
|
|
@@ -84,7 +101,10 @@ function askYesNo(question) {
|
|
|
84
101
|
function runInstall(command) {
|
|
85
102
|
if (process.platform === 'win32') {
|
|
86
103
|
const target = detectInstallTarget();
|
|
87
|
-
return runWindows(target.command, target.args).then(
|
|
104
|
+
return runWindows(target.command, target.args).then(
|
|
105
|
+
(code) => code === 0,
|
|
106
|
+
() => false,
|
|
107
|
+
);
|
|
88
108
|
}
|
|
89
109
|
return new Promise((resolve) => {
|
|
90
110
|
const child = spawn(command, { shell: true, stdio: 'inherit' });
|
|
@@ -108,7 +128,9 @@ export async function installLatest(options = {}) {
|
|
|
108
128
|
}
|
|
109
129
|
|
|
110
130
|
if (latest && compareVersions(current, latest) >= 0) {
|
|
111
|
-
console.log(
|
|
131
|
+
console.log(
|
|
132
|
+
`\n ${c.green}Already up to date${c.reset} ${c.dim}(${current}).${c.reset}\n`,
|
|
133
|
+
);
|
|
112
134
|
return true;
|
|
113
135
|
}
|
|
114
136
|
|
|
@@ -117,7 +139,9 @@ export async function installLatest(options = {}) {
|
|
|
117
139
|
`\n Upgrading ${c.cyan}${PACKAGE_NAME}${c.reset} ${c.dim}${current}${c.reset} → ${c.cyan}${latest}${c.reset}`,
|
|
118
140
|
);
|
|
119
141
|
} else {
|
|
120
|
-
console.log(
|
|
142
|
+
console.log(
|
|
143
|
+
`\n Upgrading ${c.cyan}${PACKAGE_NAME}${c.reset} ${c.dim}${current}${c.reset} → latest`,
|
|
144
|
+
);
|
|
121
145
|
}
|
|
122
146
|
console.log(` ${c.dim}Running${c.reset} ${c.cyan}${command}${c.reset}\n`);
|
|
123
147
|
|
|
@@ -141,20 +165,25 @@ export async function installLatest(options = {}) {
|
|
|
141
165
|
return false;
|
|
142
166
|
}
|
|
143
167
|
} catch {
|
|
144
|
-
console.error(
|
|
168
|
+
console.error(
|
|
169
|
+
`\n ${c.red}Could not verify the updated CLI installation.${c.reset}`,
|
|
170
|
+
);
|
|
145
171
|
console.error(` Run the exact detected command manually:\n`);
|
|
146
172
|
console.error(` ${c.cyan}${command}${c.reset}\n`);
|
|
147
173
|
return false;
|
|
148
174
|
}
|
|
149
175
|
}
|
|
150
176
|
|
|
151
|
-
console.log(
|
|
177
|
+
console.log(
|
|
178
|
+
`\n ${c.green}${c.bold}✓ Upgraded.${c.reset} Re-run ${c.cyan}subc login${c.reset} if you were signing in.\n`,
|
|
179
|
+
);
|
|
152
180
|
return true;
|
|
153
181
|
}
|
|
154
182
|
|
|
155
183
|
export async function upgradeCommand(argv = [], options = {}) {
|
|
156
184
|
const { latest } = parseUpgradeArgs(argv);
|
|
157
|
-
const interactive =
|
|
185
|
+
const interactive =
|
|
186
|
+
options.interactive ?? (process.stdin.isTTY && process.stdout.isTTY);
|
|
158
187
|
const ask = options.ask || askYesNo;
|
|
159
188
|
const installLatestFn = options.installLatest || installLatest;
|
|
160
189
|
|
|
@@ -163,7 +192,9 @@ export async function upgradeCommand(argv = [], options = {}) {
|
|
|
163
192
|
console.error(
|
|
164
193
|
`\n ${c.yellow}Do you want to upgrade?${c.reset} requires a TTY.`,
|
|
165
194
|
);
|
|
166
|
-
console.error(
|
|
195
|
+
console.error(
|
|
196
|
+
` Run ${c.cyan}subc upgrade --latest${c.reset} instead.\n`,
|
|
197
|
+
);
|
|
167
198
|
process.exitCode = 1;
|
|
168
199
|
return;
|
|
169
200
|
}
|
package/bin/usage.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Usage display for `subc usage` — fetches billing/quota from the platform API.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { c } from './colors.js';
|
|
6
5
|
import { getApiKey, getPlatformUrl } from './auth.js';
|
|
6
|
+
import { c } from './colors.js';
|
|
7
7
|
import { DEFAULT_PROFILE } from './profiles.js';
|
|
8
8
|
import { printLoginUpgradeWarning } from './upgrade.js';
|
|
9
9
|
|
|
@@ -33,13 +33,18 @@ export function formatCurrency(value) {
|
|
|
33
33
|
|
|
34
34
|
export function shortModelSlug(slug) {
|
|
35
35
|
const text = String(slug ?? '').trim();
|
|
36
|
-
return text.startsWith('subconscious/')
|
|
36
|
+
return text.startsWith('subconscious/')
|
|
37
|
+
? text.slice('subconscious/'.length)
|
|
38
|
+
: text;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
export function isLegacyUsagePayload(data) {
|
|
40
42
|
if (!data || typeof data !== 'object') return true;
|
|
41
|
-
if ('credits' in data && data.plan && typeof data.plan === 'object')
|
|
42
|
-
|
|
43
|
+
if ('credits' in data && data.plan && typeof data.plan === 'object')
|
|
44
|
+
return false;
|
|
45
|
+
return (
|
|
46
|
+
'dailyTokenCeiling' in data || 'consumedToday' in data || !('plan' in data)
|
|
47
|
+
);
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
export function meterColor(percentage) {
|
|
@@ -120,7 +125,9 @@ export function formatUsageDisplay(data) {
|
|
|
120
125
|
);
|
|
121
126
|
} else {
|
|
122
127
|
const label =
|
|
123
|
-
described.kind === 'credit'
|
|
128
|
+
described.kind === 'credit'
|
|
129
|
+
? 'of daily credit used'
|
|
130
|
+
: 'of daily tokens used';
|
|
124
131
|
lines.push(
|
|
125
132
|
` ${renderProgressBar(described.percent)} ${formatAllowancePercent(described.percent)} ${label}`,
|
|
126
133
|
);
|
|
@@ -155,9 +162,7 @@ export function formatUsageDisplay(data) {
|
|
|
155
162
|
);
|
|
156
163
|
for (const model of models) {
|
|
157
164
|
const label = shortModelSlug(model.slug).padEnd(slugWidth);
|
|
158
|
-
lines.push(
|
|
159
|
-
` ${label} ${formatTokens(model.consumedToday ?? 0)}`,
|
|
160
|
-
);
|
|
165
|
+
lines.push(` ${label} ${formatTokens(model.consumedToday ?? 0)}`);
|
|
161
166
|
}
|
|
162
167
|
}
|
|
163
168
|
|
|
@@ -166,10 +171,13 @@ export function formatUsageDisplay(data) {
|
|
|
166
171
|
}
|
|
167
172
|
|
|
168
173
|
export async function fetchUsage(apiKey, platformUrl, fetchImpl = fetch) {
|
|
169
|
-
const res = await fetchImpl(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
174
|
+
const res = await fetchImpl(
|
|
175
|
+
`${platformUrl.replace(/\/$/, '')}${USAGE_API_PATH}`,
|
|
176
|
+
{
|
|
177
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
178
|
+
signal: AbortSignal.timeout(5000),
|
|
179
|
+
},
|
|
180
|
+
);
|
|
173
181
|
return res;
|
|
174
182
|
}
|
|
175
183
|
|
|
@@ -183,7 +191,9 @@ export async function usageCommand(argv = [], options = {}) {
|
|
|
183
191
|
const auth = await getApiKey(options.profile);
|
|
184
192
|
if (!auth) {
|
|
185
193
|
console.log(`\n ${c.dim}Not logged in.${c.reset}`);
|
|
186
|
-
console.log(
|
|
194
|
+
console.log(
|
|
195
|
+
` Run ${c.cyan}subc ${profileFlag}login${c.reset} to get started.\n`,
|
|
196
|
+
);
|
|
187
197
|
return;
|
|
188
198
|
}
|
|
189
199
|
|
|
@@ -236,7 +246,9 @@ export async function usageCommand(argv = [], options = {}) {
|
|
|
236
246
|
console.log(formatUsageDisplay(data));
|
|
237
247
|
} catch (error) {
|
|
238
248
|
if (error.name === 'TimeoutError' || error.name === 'AbortError') {
|
|
239
|
-
console.log(
|
|
249
|
+
console.log(
|
|
250
|
+
`\n ${c.yellow}Could not reach the platform (timed out).${c.reset}`,
|
|
251
|
+
);
|
|
240
252
|
console.log(` ${c.dim}Host: ${platformUrl}${c.reset}\n`);
|
|
241
253
|
return;
|
|
242
254
|
}
|