subconscious-cli 5.0.3 → 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/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 { isAbortError, normalizeModelIds, resolveModelCatalog } from './models.js';
10
- import { discoverSessions, SESSION_HARNESSES } from './sessions.js';
11
- import { compareVersions, fetchLatestVersion } from './update-check.js';
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(platform = process.platform, arch = process.arch) {
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) && result.args.every((arg) => typeof arg === 'string'))) &&
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 || typeof result.installedVersion === 'string') &&
40
- (result.latestVersion === undefined || typeof result.latestVersion === 'string')
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(binary, sourceFile = path.join(TUI_SOURCE_DIR, 'cmd/subc-tui/main.go')) {
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([fs.stat(binary), fs.stat(sourceFile)]);
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 { command: override, args: options.binaryArgs || [], cwd: undefined };
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 (await pathExists(packaged) && !(hasSource && (await tuiSourceIsNewerThan(packaged)))) {
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(process.env.SUBCONSCIOUS_MODEL || profile.values.MODEL);
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(profileName = DEFAULT_PROFILE, options = {}) {
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((a, b) => {
187
- if (a === profileName) return -1;
188
- if (b === profileName) return 1;
189
- return a.localeCompare(b);
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 = name === profileName ? activeProfile : await loadProfile(name);
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([requestedModel, ...PACKAGED_MODELS], requestedModel),
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(/\/+$/, '') || DEFAULT_PLATFORM_URL,
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',
@@ -257,16 +292,30 @@ async function raceAbort(signal, promise) {
257
292
  export async function loadRemoteTuiUpdates(state, options = {}) {
258
293
  const signal = options.signal;
259
294
  const writePatch = options.writePatch || (async () => {});
295
+ // Catalog and session writes finish together. A later patch must not start
296
+ // until the earlier file replace has finished, or the slower write lands
297
+ // last and drops the other fields. Windows CI hits that race.
298
+ let writeQueue = Promise.resolve();
299
+ const enqueuePatch = (partial) => {
300
+ const run = writeQueue.then(() => writePatch(partial));
301
+ writeQueue = run.then(
302
+ () => {},
303
+ () => {},
304
+ );
305
+ return run;
306
+ };
260
307
  const resolveCatalog = options.resolveCatalog || resolveModelCatalog;
261
308
  const discover = options.discoverSessions || discoverSessions;
262
309
  const fetchLatest = options.fetchLatestVersion || fetchLatestVersion;
263
310
  const readApiKey = options.getApiKey || getApiKey;
264
311
  const updateDisabled =
265
- options.disableUpdateCheck ?? process.env.SUBC_DISABLE_UPDATE_CHECK?.trim() === '1';
312
+ options.disableUpdateCheck ??
313
+ process.env.SUBC_DISABLE_UPDATE_CHECK?.trim() === '1';
266
314
 
267
315
  const tasks = [
268
316
  (async () => {
269
- const profile = options.profile || (await loadProfile(state.activeProfile));
317
+ const profile =
318
+ options.profile || (await loadProfile(state.activeProfile));
270
319
  if (signal?.aborted) return;
271
320
  const auth = await readApiKey(profile);
272
321
  if (signal?.aborted) return;
@@ -280,7 +329,7 @@ export async function loadRemoteTuiUpdates(state, options = {}) {
280
329
  signal,
281
330
  });
282
331
  if (signal?.aborted) return;
283
- await writePatch({
332
+ await enqueuePatch({
284
333
  models: catalog.models,
285
334
  modelError: catalog.error?.message || '',
286
335
  modelSource: catalog.source,
@@ -300,7 +349,7 @@ export async function loadRemoteTuiUpdates(state, options = {}) {
300
349
  }),
301
350
  );
302
351
  if (signal?.aborted) return;
303
- await writePatch({
352
+ await enqueuePatch({
304
353
  sessions: serializeSessions(sessions),
305
354
  sessionsLoading: false,
306
355
  });
@@ -318,7 +367,10 @@ export async function loadRemoteTuiUpdates(state, options = {}) {
318
367
  });
319
368
  if (signal?.aborted || !latest) return;
320
369
  if (compareVersions(latest, state.version) > 0) {
321
- await writePatch({ updateAvailable: true, latestVersion: latest });
370
+ await enqueuePatch({
371
+ updateAvailable: true,
372
+ latestVersion: latest,
373
+ });
322
374
  }
323
375
  } catch (error) {
324
376
  if (isAbortError(error) || signal?.aborted) return;
@@ -335,7 +387,10 @@ export async function loadRemoteTuiUpdates(state, options = {}) {
335
387
  }
336
388
  }
337
389
 
338
- export async function createTuiState(profileName = DEFAULT_PROFILE, options = {}) {
390
+ export async function createTuiState(
391
+ profileName = DEFAULT_PROFILE,
392
+ options = {},
393
+ ) {
339
394
  const state = await createLocalTuiState(profileName, options);
340
395
  let next = { ...state };
341
396
  await loadRemoteTuiUpdates(state, {
@@ -371,7 +426,8 @@ export async function runTui(options = {}) {
371
426
  if (!executable) return null;
372
427
 
373
428
  const providedState = options.state;
374
- const state = providedState || (await createLocalTuiState(options.profileName, options));
429
+ const state =
430
+ providedState || (await createLocalTuiState(options.profileName, options));
375
431
  const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'subc-tui-'));
376
432
  const statePath = path.join(tempDir, 'state.json');
377
433
  const resultPath = path.join(tempDir, 'result.json');
@@ -391,20 +447,35 @@ export async function runTui(options = {}) {
391
447
 
392
448
  const remote = providedState
393
449
  ? Promise.resolve()
394
- : loadRemoteTuiUpdates(state, { ...options, signal: controller.signal, writePatch });
450
+ : loadRemoteTuiUpdates(state, {
451
+ ...options,
452
+ signal: controller.signal,
453
+ writePatch,
454
+ });
395
455
 
396
456
  try {
397
457
  // This state intentionally contains only display data. API keys are used
398
458
  // by the Node command engine and are never passed into the TUI process.
399
- await fs.writeFile(statePath, `${JSON.stringify(state)}\n`, { mode: 0o600 });
459
+ await fs.writeFile(statePath, `${JSON.stringify(state)}\n`, {
460
+ mode: 0o600,
461
+ });
400
462
  const code = await spawnAndWait(
401
463
  executable.command,
402
- [...executable.args, '--state', statePath, '--result', resultPath, '--updates', updatesPath],
464
+ [
465
+ ...executable.args,
466
+ '--state',
467
+ statePath,
468
+ '--result',
469
+ resultPath,
470
+ '--updates',
471
+ updatesPath,
472
+ ],
403
473
  { cwd: executable.cwd, spawn: options.spawn, stdio: options.stdio },
404
474
  );
405
475
  controller.abort();
406
476
  await remote.catch(() => {});
407
- if (code !== 0) throw new Error(`Subconscious TUI exited with status ${code}`);
477
+ if (code !== 0)
478
+ throw new Error(`Subconscious TUI exited with status ${code}`);
408
479
 
409
480
  try {
410
481
  const result = JSON.parse(await fs.readFile(resultPath, 'utf-8'));
@@ -1,10 +1,10 @@
1
- import fs from 'node:fs/promises';
2
1
  import { spawn } from 'node:child_process';
3
- import { spawnWindows } from './windows/process.js';
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) ? normalized.slice(0, normalized.indexOf(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].map(platform === 'win32' ? value => /^[-/@A-Za-z0-9._:]+$/.test(value) ? value : `"${value}"` : shellQuote).join(' '),
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(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
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) return Math.sign(aNumber - bNumber);
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(await fs.readFile(new URL('../package.json', import.meta.url), 'utf-8'));
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(signal?.aborted ? 'Update check was cancelled' : 'Update check timed out');
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)) throw new Error('npm returned an invalid 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
- { raw: 'Subconscious CLI', value: `${c.orange}${c.bold}Subconscious CLI${c.reset}` },
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 = versions.installCommand || detectInstallTarget().display;
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 = options.spawnImpl || (process.platform === 'win32' ? spawnWindows : spawn);
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 ?? (process.stdin.isTTY === true && process.stderr.isTTY === true);
329
- if (!interactive) return { installedVersion, latestVersion, action: 'skip' };
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(`\n ${c.cyan}Updating ${PACKAGE_NAME} to ${latestVersion}...${c.reset}\n\n`);
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) => installLatest({ expectedVersion, target: installTarget }));
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(`\n ${c.red}Update failed or the running installation stayed unchanged.${c.reset}\n`);
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(`Unknown argument: ${unknown[0]}\nUsage: subc upgrade [--latest]`);
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(await fs.readFile(new URL('../package.json', import.meta.url), 'utf-8'));
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).split('.').map((part) => Number.parseInt(part, 10) || 0);
39
- const pb = String(b).split('.').map((part) => Number.parseInt(part, 10) || 0);
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(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
52
- signal: AbortSignal.timeout(10000),
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(`Could not look up ${PACKAGE_NAME} on npm (${res.status}).`);
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) throw new Error(`npm did not return a version for ${PACKAGE_NAME}.`);
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({ input: process.stdin, output: process.stdout });
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(code => code === 0, () => false);
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(`\n ${c.green}Already up to date${c.reset} ${c.dim}(${current}).${c.reset}\n`);
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(`\n Upgrading ${c.cyan}${PACKAGE_NAME}${c.reset} ${c.dim}${current}${c.reset} → latest`);
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(`\n ${c.red}Could not verify the updated CLI installation.${c.reset}`);
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(`\n ${c.green}${c.bold}✓ Upgraded.${c.reset} Re-run ${c.cyan}subc login${c.reset} if you were signing in.\n`);
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 = options.interactive ?? (process.stdin.isTTY && process.stdout.isTTY);
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(` Run ${c.cyan}subc upgrade --latest${c.reset} instead.\n`);
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
  }