borgmcp 4.3.0 → 4.4.0

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.
@@ -3,6 +3,7 @@ import { resolve } from 'node:path';
3
3
  import type { ActiveCube } from './cubes.js';
4
4
 
5
5
  export type OpenCodeSeatIdentityErrorCode =
6
+ | 'IDENTITY_HANDSHAKE_TIMEOUT'
6
7
  | 'ROOTS_UNAVAILABLE'
7
8
  | 'ROOTS_INVALID'
8
9
  | 'SEAT_NOT_FOUND'
@@ -217,8 +217,7 @@ export function renderRoster(inputs: RenderRosterInputs): string {
217
217
  const regenCountMarker =
218
218
  typeof d.regen_count === 'number' ? ` · \`regen-count:${d.regen_count}\`` : '';
219
219
  if (resolvedSince) {
220
- // T2.1 awake/stale column. When available, wake_state is the server's
221
- // richer state; older servers fall back to seen_since. `seen_since === true` → drone called a
220
+ // T2.1 awake/stale column. `seen_since === true` means the drone called a
222
221
  // tool after the resolved timestamp; treat as awake. False or
223
222
  // missing → stale. Missing should not happen when the server
224
223
  // echoed a since, but defending against a shape mismatch is
@@ -232,7 +231,7 @@ export function renderRoster(inputs: RenderRosterInputs): string {
232
231
  // probe call — pure redundancy. The per-row `last seen X ago`
233
232
  // field carries the diagnostic detail for "how stale is this
234
233
  // particular drone."
235
- const marker = d.wake_state ?? (d.seen_since === true ? 'awake' : 'stale');
234
+ const marker = d.seen_since === true ? 'awake' : 'stale';
236
235
  lines.push(
237
236
  `- **${d.label}**${addr} (Role: ${roleName}) — last seen ${lastSeen} · \`${marker}\`${regenCountMarker}${wakePathMarker}${wakePathClassMarker}`
238
237
  );
@@ -110,6 +110,56 @@ export class BorgServerUnreachableError extends Error {
110
110
  }
111
111
  }
112
112
 
113
+ export type OpenCodeFailureCode =
114
+ | 'unauthorized'
115
+ | 'not-found'
116
+ | 'incompatible-api'
117
+ | 'timeout'
118
+ | 'transient';
119
+
120
+ export class OpenCodeAuthenticationError extends Error {
121
+ readonly code = 'unauthorized' as const;
122
+
123
+ constructor(message = 'OpenCode API authentication is unavailable') {
124
+ super(message);
125
+ this.name = 'OpenCodeAuthenticationError';
126
+ }
127
+ }
128
+
129
+ export class OpenCodeHttpError extends Error {
130
+ constructor(
131
+ public readonly status: number,
132
+ public readonly code: OpenCodeFailureCode,
133
+ message: string,
134
+ ) {
135
+ super(message);
136
+ this.name = 'OpenCodeHttpError';
137
+ }
138
+ }
139
+
140
+ export class OpenCodeResponseError extends Error {
141
+ readonly code = 'incompatible-api' as const;
142
+
143
+ constructor(
144
+ message = 'OpenCode returned an incompatible API response',
145
+ options?: { cause?: unknown },
146
+ ) {
147
+ super(message, options);
148
+ this.name = 'OpenCodeResponseError';
149
+ }
150
+ }
151
+
152
+ export class OpenCodeUnreachableError extends Error {
153
+ constructor(
154
+ public readonly code: 'timeout' | 'transient',
155
+ message: string,
156
+ options?: { cause?: unknown },
157
+ ) {
158
+ super(message, options);
159
+ this.name = 'OpenCodeUnreachableError';
160
+ }
161
+ }
162
+
113
163
  export class CubeCreationOutcomeUnknownError extends Error {
114
164
  constructor() {
115
165
  super('Cube creation outcome is unknown.');
@@ -264,8 +264,13 @@ export function renderStreamStatus(inputs: RenderInputs): string {
264
264
  }
265
265
 
266
266
  if (wakePath.agentKind === 'opencode' && wakePath.openCode) {
267
- const delivery = wakePath.openCode.deliveryStates;
268
- lines.push(`- **OpenCode delivery connected**: ${wakePath.openCode.connected}`);
267
+ const openCode = wakePath.openCode;
268
+ const delivery = openCode.deliveryStates;
269
+ lines.push(`- **OpenCode delivery connected**: ${openCode.connected}`);
270
+ lines.push(`- **OpenCode target session**: ${openCode.sessionId ?? '_(none resolved yet)_'}`);
271
+ lines.push(`- **OpenCode last injection**: ${openCode.lastInjectionResult ?? '_(none yet)_'}${typeof openCode.lastInjectionAt === 'number' ? ` at ${new Date(openCode.lastInjectionAt).toISOString()}` : ''}`);
272
+ lines.push(`- **OpenCode last accepted entry**: ${openCode.lastAcceptedEntryId ?? '_(none)_'}`);
273
+ lines.push(`- **OpenCode last failure code**: ${openCode.lastFailureCode ?? '_(none)_'}`);
269
274
  lines.push(`- **OpenCode queued**: ${delivery.queued}`);
270
275
  lines.push(
271
276
  `- **OpenCode delivered-unconfirmed**: ${delivery['delivered-unconfirmed']}`
package/src/update-cmd.ts CHANGED
@@ -42,6 +42,7 @@ export interface UpdateTarget {
42
42
  export interface UpdateOptions {
43
43
  yes: boolean;
44
44
  help?: boolean;
45
+ registry?: string;
45
46
  target?: UpdateTarget;
46
47
  }
47
48
 
@@ -129,10 +130,21 @@ type ServerUpdateFailureStage =
129
130
  interface NpmContext {
130
131
  commandPath: string;
131
132
  commandIdentity: string;
133
+ registry: string;
132
134
  prefix: string;
133
135
  root: string;
134
136
  }
135
137
 
138
+ class RegistryChangedDuringUpdateError extends Error {
139
+ constructor(
140
+ readonly expectedRegistry: string,
141
+ readonly observedRegistry: string,
142
+ ) {
143
+ super(`npm registry changed during update from ${expectedRegistry} to ${observedRegistry}`);
144
+ this.name = 'RegistryChangedDuringUpdateError';
145
+ }
146
+ }
147
+
136
148
  function signalExitCode(error: unknown): number | null {
137
149
  return error instanceof CommandSignalError ? error.exitCode : null;
138
150
  }
@@ -141,11 +153,26 @@ function errorMessage(error: unknown, fallback: string): string {
141
153
  return error instanceof Error ? error.message : fallback;
142
154
  }
143
155
 
156
+ function updateRetryCommand(registry?: string): string {
157
+ return `borg update --yes${registry ? ` --registry ${shellEscape(registry)}` : ''}`;
158
+ }
159
+
160
+ function renderUpdateRetry(error: unknown, registry?: string): string {
161
+ if (error instanceof RegistryChangedDuringUpdateError) {
162
+ return (
163
+ `The configured npm registry changed during the update.\n` +
164
+ `Restore ${error.expectedRegistry} and retry with: ${updateRetryCommand(error.expectedRegistry)}\n` +
165
+ `Or deliberately start a new update against the current registry with: ${updateRetryCommand(error.observedRegistry)}\n`
166
+ );
167
+ }
168
+ return `Retry with: ${updateRetryCommand(registry)}\n`;
169
+ }
170
+
144
171
  function hasErrorCode(error: unknown, code: string): boolean {
145
172
  return error instanceof Error && (error as NodeJS.ErrnoException).code === code;
146
173
  }
147
174
 
148
- function renderReentryPreflightFailure(error: unknown, target: UpdateTarget): string {
175
+ function renderReentryPreflightFailure(error: unknown, target: UpdateTarget, registry?: string): string {
149
176
  return (
150
177
  `Update preflight failed: ${errorMessage(error, 'unknown failure')}\n` +
151
178
  `Observed update state:\n` +
@@ -154,7 +181,7 @@ function renderReentryPreflightFailure(error: unknown, target: UpdateTarget): st
154
181
  ` prepared runtime: not inspected\n` +
155
182
  ` running runtime: not inspected\n` +
156
183
  `Server mutation was not attempted.\n` +
157
- `Retry with: borg update --yes\n`
184
+ renderUpdateRetry(error, registry)
158
185
  );
159
186
  }
160
187
 
@@ -185,6 +212,27 @@ export function isExactSemver(value: unknown): value is string {
185
212
  return typeof value === 'string' && EXACT_SEMVER.test(value);
186
213
  }
187
214
 
215
+ function normalizeRegistryUrl(value: string): string {
216
+ if (value !== value.trim()) throw new Error('npm registry URL must not contain surrounding whitespace');
217
+ let url: URL;
218
+ try {
219
+ url = new URL(value);
220
+ } catch {
221
+ throw new Error('npm registry URL is invalid');
222
+ }
223
+ if (
224
+ url.protocol !== 'https:' ||
225
+ url.username !== '' ||
226
+ url.password !== '' ||
227
+ url.search !== '' ||
228
+ url.hash !== ''
229
+ ) {
230
+ throw new Error('npm registry URL must be an HTTPS URL without credentials, query, or fragment');
231
+ }
232
+ if (!url.pathname.endsWith('/')) url.pathname += '/';
233
+ return url.href;
234
+ }
235
+
188
236
  function isCanonicalSha512Integrity(value: unknown): boolean {
189
237
  if (typeof value !== 'string' || !value.startsWith('sha512-') || value.includes(' ')) return false;
190
238
  const encoded = value.slice('sha512-'.length);
@@ -219,6 +267,7 @@ export function parseUpdateArgs(
219
267
  ): ParsedUpdateArgs {
220
268
  let yes = false;
221
269
  let help = false;
270
+ let registry: string | undefined;
222
271
  let clientVersion: string | undefined;
223
272
  let serverVersion: string | undefined;
224
273
  let serverPresent: boolean | undefined;
@@ -230,6 +279,16 @@ export function parseUpdateArgs(
230
279
  yes = true;
231
280
  } else if (arg === '--help' || arg === '-h') {
232
281
  help = true;
282
+ } else if (arg === '--registry') {
283
+ if (registry !== undefined) return { ok: false, error: '--registry may be specified only once' };
284
+ const value = args[index + 1];
285
+ if (!value) return { ok: false, error: '--registry requires a value' };
286
+ index += 1;
287
+ try {
288
+ registry = normalizeRegistryUrl(value);
289
+ } catch (error) {
290
+ return { ok: false, error: errorMessage(error, 'npm registry URL is invalid') };
291
+ }
233
292
  } else if (arg === '--target-client' || arg === '--target-server' || arg === '--server-present') {
234
293
  hasInternalOption = true;
235
294
  const value = args[index + 1];
@@ -262,10 +321,11 @@ export function parseUpdateArgs(
262
321
  ok: true,
263
322
  yes,
264
323
  ...(help ? { help: true } : {}),
324
+ ...(registry ? { registry } : {}),
265
325
  target: { clientVersion, serverVersion, serverPresent },
266
326
  };
267
327
  }
268
- return { ok: true, yes, ...(help ? { help: true } : {}) };
328
+ return { ok: true, yes, ...(help ? { help: true } : {}), ...(registry ? { registry } : {}) };
269
329
  }
270
330
 
271
331
  function validatePublishedPackage(
@@ -582,7 +642,9 @@ function verifyServerStatus(status: ServerStatus, target: PublishedPackage): 'ru
582
642
  function renderServerFailureRecovery(
583
643
  status: ServerStatus | null,
584
644
  updateAttempted: boolean,
585
- retryCommand: 'borg update --yes' | 'borg server status' | 'borg server update' | 'borg server start',
645
+ retryCommand: string,
646
+ error: unknown,
647
+ registry?: string,
586
648
  ): string {
587
649
  let text = '';
588
650
  if (status?.state === 'stopped') {
@@ -594,7 +656,9 @@ function renderServerFailureRecovery(
594
656
  `If it is stopped, run the recovery command reported by borg server status.\n`
595
657
  );
596
658
  }
597
- if (retryCommand !== 'borg server start') {
659
+ if (error instanceof RegistryChangedDuringUpdateError) {
660
+ text += renderUpdateRetry(error, registry);
661
+ } else if (retryCommand !== 'borg server start') {
598
662
  text += status?.state === 'stopped'
599
663
  ? `Then retry the failed stage with: ${retryCommand}\n`
600
664
  : `Next: ${retryCommand}\n`;
@@ -622,6 +686,7 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
622
686
  let pair: { client: PublishedPackage; server: PublishedPackage };
623
687
  let client: InstalledPackage;
624
688
  let discoveredServer: InstalledPackage | null;
689
+ const updateRetry = updateRetryCommand(options.registry);
625
690
  try {
626
691
  [pair, client, discoveredServer] = await Promise.all([
627
692
  publishedPair(options.target, deps),
@@ -631,7 +696,7 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
631
696
  } catch (error) {
632
697
  const interrupted = signalExitCode(error);
633
698
  deps.stderr(options.target
634
- ? renderReentryPreflightFailure(error, options.target)
699
+ ? renderReentryPreflightFailure(error, options.target, options.registry)
635
700
  : (
636
701
  `Update preflight failed: ${errorMessage(error, 'unknown failure')}\n` +
637
702
  `Observed update state:\n` +
@@ -640,7 +705,9 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
640
705
  ` prepared runtime: not inspected\n` +
641
706
  ` running runtime: not inspected\n` +
642
707
  `No mutation was attempted.\n` +
643
- `Manual fallback: npm install -g ${CLIENT_PACKAGE} && npm install -g ${SERVER_PACKAGE}\n`
708
+ (error instanceof RegistryChangedDuringUpdateError
709
+ ? renderUpdateRetry(error, options.registry)
710
+ : `Manual fallback: npm install -g ${CLIENT_PACKAGE} && npm install -g ${SERVER_PACKAGE}\n`)
644
711
  ));
645
712
  return interrupted ?? 1;
646
713
  }
@@ -655,13 +722,13 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
655
722
  ` prepared runtime: not inspected\n` +
656
723
  ` running runtime: not inspected\n` +
657
724
  `Server mutation was not attempted.\n` +
658
- `Retry with: borg update --yes\n`,
725
+ `Retry with: ${updateRetry}\n`,
659
726
  );
660
727
  return 1;
661
728
  }
662
729
 
663
730
  deps.stdout(
664
- `Published update plan (${CANONICAL_NPM_REGISTRY}):\n` +
731
+ `Published update plan (${options.registry ?? CANONICAL_NPM_REGISTRY}):\n` +
665
732
  ` client: ${CLIENT_PACKAGE}@${client.version} -> ${CLIENT_PACKAGE}@${pair.client.version}\n` +
666
733
  ` target integrity: ${pair.client.integrity}\n` +
667
734
  ` server: ${discoveredServer ? `${SERVER_PACKAGE}@${discoveredServer.version}` : 'not installed'} -> ${SERVER_PACKAGE}@${pair.server.version}\n` +
@@ -707,6 +774,7 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
707
774
  const args = [
708
775
  'update',
709
776
  '--yes',
777
+ ...(options.registry ? ['--registry', options.registry] : []),
710
778
  '--target-client', pair.client.version,
711
779
  '--target-server', pair.server.version,
712
780
  '--server-present', serverWasPresent ? 'yes' : 'no',
@@ -726,7 +794,7 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
726
794
  ` prepared runtime: not inspected\n` +
727
795
  ` running runtime: not inspected\n` +
728
796
  `Server mutation was not attempted.\n` +
729
- `Retry with: borg update --yes\n`,
797
+ renderUpdateRetry(error, options.registry),
730
798
  );
731
799
  return interrupted ?? 1;
732
800
  }
@@ -746,7 +814,9 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
746
814
  ` prepared runtime: not inspected\n` +
747
815
  ` running runtime: not inspected\n` +
748
816
  `Server mutation was not attempted.\n` +
749
- `Next: reinstall ${CLIENT_PACKAGE}@${pair.client.version} from ${CANONICAL_NPM_REGISTRY}, then rerun borg update --yes.\n`,
817
+ (error instanceof RegistryChangedDuringUpdateError
818
+ ? renderUpdateRetry(error, options.registry)
819
+ : `Next: reinstall ${CLIENT_PACKAGE}@${pair.client.version} from ${options.registry ?? CANONICAL_NPM_REGISTRY}, then rerun ${updateRetry}.\n`),
750
820
  );
751
821
  return interrupted ?? 1;
752
822
  }
@@ -794,7 +864,7 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
794
864
  ` prepared runtime: not inspected\n` +
795
865
  ` running runtime: not inspected\n` +
796
866
  `Server runtime mutation was not attempted.\n` +
797
- `Retry with: borg update --yes\n`,
867
+ renderUpdateRetry(error, options.registry),
798
868
  );
799
869
  return interrupted ?? 1;
800
870
  }
@@ -805,7 +875,7 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
805
875
  let updateAttempted = false;
806
876
  let recoveryStatusAttempted = false;
807
877
  let failureStage: ServerUpdateFailureStage = 'initial server status check';
808
- let retryCommand: Parameters<typeof renderServerFailureRecovery>[2] = 'borg server status';
878
+ let retryCommand = 'borg server status';
809
879
  const observeStatusAfterFailure = async (): Promise<void> => {
810
880
  recoveryStatusAttempted = true;
811
881
  try {
@@ -820,7 +890,7 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
820
890
  initialServerState = status.state;
821
891
  if (status.installedController !== exactServerIdentity(pair.server.version)) {
822
892
  failureStage = 'server controller identity check';
823
- retryCommand = 'borg update --yes';
893
+ retryCommand = updateRetry;
824
894
  throw new Error('server status contradicted the verified controller identity');
825
895
  }
826
896
  try {
@@ -856,7 +926,7 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
856
926
  retryCommand = 'borg server update';
857
927
  const state = verifyServerStatus(status, pair.server);
858
928
  failureStage = 'final package verification';
859
- retryCommand = 'borg update --yes';
929
+ retryCommand = updateRetry;
860
930
  const [finalClient, finalServer] = await Promise.all([
861
931
  deps.currentClient(),
862
932
  deps.currentServer(),
@@ -904,7 +974,7 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
904
974
  deps.stderr(
905
975
  `Server update failed during ${failureStage}: ${errorMessage(error, 'unknown failure')}.\n` +
906
976
  renderServerState(client, server, observedStatus, observedUpdate) +
907
- renderServerFailureRecovery(observedStatus, updateAttempted, retryCommand),
977
+ renderServerFailureRecovery(observedStatus, updateAttempted, retryCommand, error, options.registry),
908
978
  );
909
979
  return interrupted ?? 1;
910
980
  }
@@ -998,26 +1068,34 @@ async function npmText(commandPath: string, args: readonly string[], label: stri
998
1068
  return singleLine(result.stdout, label);
999
1069
  }
1000
1070
 
1001
- function requireCanonicalRegistry(value: string): void {
1002
- let normalized: string;
1003
- try {
1004
- normalized = new URL(value).href;
1005
- } catch {
1006
- throw new Error('npm registry configuration is invalid');
1071
+ function requireAcknowledgedRegistry(value: string, acknowledgedRegistry?: string): string {
1072
+ const normalized = normalizeRegistryUrl(value);
1073
+ if (acknowledgedRegistry !== undefined) {
1074
+ const acknowledged = normalizeRegistryUrl(acknowledgedRegistry);
1075
+ if (acknowledged !== normalized) {
1076
+ throw new Error(
1077
+ `the configured npm registry ${normalized} does not match the explicitly acknowledged registry ${acknowledged}`,
1078
+ );
1079
+ }
1080
+ return normalized;
1007
1081
  }
1008
1082
  if (normalized !== CANONICAL_NPM_REGISTRY) {
1009
1083
  throw new Error(
1010
- `borg update requires the canonical npm registry ${CANONICAL_NPM_REGISTRY}; ` +
1011
- `the configured registry is unsupported. Use your package manager manually for this installation.`,
1084
+ `borg update uses the canonical npm registry ${CANONICAL_NPM_REGISTRY} by default; ` +
1085
+ `the configured registry ${normalized} has not been explicitly acknowledged. ` +
1086
+ `Rerun with: borg update --registry ${shellEscape(normalized)} to acknowledge this exact registry for one update.`,
1012
1087
  );
1013
1088
  }
1089
+ return normalized;
1014
1090
  }
1015
1091
 
1016
- async function resolveNpmContext(): Promise<NpmContext> {
1092
+ async function resolveNpmContext(acknowledgedRegistry?: string): Promise<NpmContext> {
1017
1093
  const commandPath = which.sync('npm');
1018
1094
  const commandIdentity = await realpath(commandPath);
1019
- const registry = await npmText(commandPath, ['config', 'get', 'registry'], 'registry');
1020
- requireCanonicalRegistry(registry);
1095
+ const registry = requireAcknowledgedRegistry(
1096
+ await npmText(commandPath, ['config', 'get', 'registry'], 'registry'),
1097
+ acknowledgedRegistry,
1098
+ );
1021
1099
  const prefixText = await npmText(commandPath, ['prefix', '--global'], 'global prefix');
1022
1100
  const rootText = await npmText(commandPath, ['root', '--global'], 'global root');
1023
1101
  if (!isAbsolute(prefixText) || !isAbsolute(rootText)) {
@@ -1029,7 +1107,7 @@ async function resolveNpmContext(): Promise<NpmContext> {
1029
1107
  if (relativeRoot === '' || relativeRoot.startsWith('..') || isAbsolute(relativeRoot)) {
1030
1108
  throw new Error('npm global root is outside its global prefix');
1031
1109
  }
1032
- return { commandPath, commandIdentity, prefix, root };
1110
+ return { commandPath, commandIdentity, registry, prefix, root };
1033
1111
  }
1034
1112
 
1035
1113
  async function assertNpmContext(context: NpmContext): Promise<NpmContext> {
@@ -1037,8 +1115,8 @@ async function assertNpmContext(context: NpmContext): Promise<NpmContext> {
1037
1115
  if (await realpath(activeCommand) !== context.commandIdentity) {
1038
1116
  throw new Error('active npm executable changed during update');
1039
1117
  }
1040
- const registry = await npmText(context.commandPath, ['config', 'get', 'registry'], 'registry');
1041
- requireCanonicalRegistry(registry);
1118
+ const registry = normalizeRegistryUrl(await npmText(context.commandPath, ['config', 'get', 'registry'], 'registry'));
1119
+ if (registry !== context.registry) throw new RegistryChangedDuringUpdateError(context.registry, registry);
1042
1120
  const prefix = await realpath(await npmText(context.commandPath, ['prefix', '--global'], 'global prefix'));
1043
1121
  if (prefix !== context.prefix) throw new Error('npm global prefix changed during update');
1044
1122
  const root = await realpath(await npmText(context.commandPath, ['root', '--global'], 'global root'));
@@ -1146,8 +1224,7 @@ async function defaultPublishedPackage(
1146
1224
  if (version !== 'latest' && !isExactSemver(version)) throw new Error('invalid registry target version');
1147
1225
  // Keep npm context validation above, but read the registry's typed manifest
1148
1226
  // contract directly rather than parsing npm CLI presentation output.
1149
- void context;
1150
- const endpoint = new URL(`${encodeURIComponent(name)}/${encodeURIComponent(version)}`, CANONICAL_NPM_REGISTRY);
1227
+ const endpoint = new URL(`${encodeURIComponent(name)}/${encodeURIComponent(version)}`, context.registry);
1151
1228
  let published: PublishedPackage;
1152
1229
  try {
1153
1230
  const response = await fetch(endpoint, {
@@ -1179,8 +1256,7 @@ async function defaultPublishedVersions(
1179
1256
  name: typeof CLIENT_PACKAGE | typeof SERVER_PACKAGE,
1180
1257
  context: NpmContext,
1181
1258
  ): Promise<string[]> {
1182
- void context;
1183
- const endpoint = new URL(encodeURIComponent(name), CANONICAL_NPM_REGISTRY);
1259
+ const endpoint = new URL(encodeURIComponent(name), context.registry);
1184
1260
  try {
1185
1261
  const response = await fetch(endpoint, {
1186
1262
  headers: { Accept: 'application/json' },
@@ -1229,10 +1305,10 @@ async function defaultConfirm(message: string, defaultYes = false): Promise<'yes
1229
1305
  }
1230
1306
  }
1231
1307
 
1232
- export function buildDefaultUpdateDeps(): UpdateDeps {
1308
+ export function buildDefaultUpdateDeps(acknowledgedRegistry?: string): UpdateDeps {
1233
1309
  let contextPromise: Promise<NpmContext> | undefined;
1234
1310
  const context = async (): Promise<NpmContext> => {
1235
- contextPromise ??= resolveNpmContext();
1311
+ contextPromise ??= resolveNpmContext(acknowledgedRegistry);
1236
1312
  return assertNpmContext(await contextPromise);
1237
1313
  };
1238
1314
  return {
@@ -1256,7 +1332,7 @@ export function buildDefaultUpdateDeps(): UpdateDeps {
1256
1332
  '--global',
1257
1333
  ...(options?.ignoreScripts ? ['--ignore-scripts'] : []),
1258
1334
  `--prefix=${npm.prefix}`,
1259
- `--registry=${CANONICAL_NPM_REGISTRY}`,
1335
+ `--registry=${npm.registry}`,
1260
1336
  `${name}@${version}`,
1261
1337
  ], { inherit: true });
1262
1338
  if (result.code !== 0) throw new Error(`${name} installation exited ${result.code}`);
@@ -1292,17 +1368,18 @@ export function buildDefaultUpdateDeps(): UpdateDeps {
1292
1368
 
1293
1369
  export async function runEarlyUpdate(
1294
1370
  argv: readonly string[],
1295
- deps: UpdateDeps = buildDefaultUpdateDeps(),
1371
+ deps?: UpdateDeps,
1296
1372
  ): Promise<number | null> {
1297
1373
  if (argv[2] !== 'update') return null;
1298
1374
  const parsed = parseUpdateArgs(argv.slice(3), process.env[REENTRY_ENV] === '1');
1375
+ const resolvedDeps = deps ?? buildDefaultUpdateDeps(parsed.ok ? parsed.registry : undefined);
1299
1376
  if (!parsed.ok) {
1300
- deps.stderr(`${parsed.error}\nRun \`borg update --help\` for usage.\n`);
1377
+ resolvedDeps.stderr(`${parsed.error}\nRun \`borg update --help\` for usage.\n`);
1301
1378
  return 1;
1302
1379
  }
1303
1380
  if (parsed.help) {
1304
- deps.stdout(updateHelpText(''));
1381
+ resolvedDeps.stdout(updateHelpText(''));
1305
1382
  return 0;
1306
1383
  }
1307
- return runUpdate(parsed, deps);
1384
+ return runUpdate(parsed, resolvedDeps);
1308
1385
  }