@tokensrc/codex 1.14.17 → 1.14.19

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.
Files changed (57) hide show
  1. package/README.md +7 -75
  2. package/dist/client.d.ts +1 -18
  3. package/dist/client.js +1 -136
  4. package/dist/client.js.map +1 -1
  5. package/dist/command-directory.js +1 -1
  6. package/dist/command-directory.js.map +1 -1
  7. package/dist/commands.d.ts +0 -1
  8. package/dist/commands.js +7 -314
  9. package/dist/commands.js.map +1 -1
  10. package/dist/credentials.d.ts +3 -75
  11. package/dist/credentials.js +2 -338
  12. package/dist/credentials.js.map +1 -1
  13. package/dist/index.d.ts +0 -8
  14. package/dist/index.js +0 -8
  15. package/dist/index.js.map +1 -1
  16. package/dist/managed-credential-v3-envelope.d.ts +0 -15
  17. package/dist/managed-credential-v3-envelope.js +0 -26
  18. package/dist/managed-credential-v3-envelope.js.map +1 -1
  19. package/dist/managed-credential-v3-flow.d.ts +0 -17
  20. package/dist/managed-credential-v3-flow.js +3 -90
  21. package/dist/managed-credential-v3-flow.js.map +1 -1
  22. package/dist/managed-credential-v3.d.ts +0 -8
  23. package/dist/managed-credential-v3.js +0 -23
  24. package/dist/managed-credential-v3.js.map +1 -1
  25. package/dist/profile.d.ts +0 -6
  26. package/dist/profile.js +0 -22
  27. package/dist/profile.js.map +1 -1
  28. package/dist/protocol.d.ts +0 -622
  29. package/dist/protocol.js +0 -277
  30. package/dist/protocol.js.map +1 -1
  31. package/dist/request-bound-device-v6-flow.js +1 -1
  32. package/dist/request-bound-device-v6-flow.js.map +1 -1
  33. package/package.json +2 -2
  34. package/dist/credential-supervisor.d.ts +0 -17
  35. package/dist/credential-supervisor.js +0 -84
  36. package/dist/credential-supervisor.js.map +0 -1
  37. package/dist/enrollment-envelope.d.ts +0 -18
  38. package/dist/enrollment-envelope.js +0 -112
  39. package/dist/enrollment-envelope.js.map +0 -1
  40. package/dist/enrollment-flow.d.ts +0 -27
  41. package/dist/enrollment-flow.js +0 -224
  42. package/dist/enrollment-flow.js.map +0 -1
  43. package/dist/migration-login-batch.d.ts +0 -66
  44. package/dist/migration-login-batch.js +0 -202
  45. package/dist/migration-login-batch.js.map +0 -1
  46. package/dist/retirement-flow.d.ts +0 -38
  47. package/dist/retirement-flow.js +0 -78
  48. package/dist/retirement-flow.js.map +0 -1
  49. package/dist/short-lived-delivery-flow.d.ts +0 -41
  50. package/dist/short-lived-delivery-flow.js +0 -141
  51. package/dist/short-lived-delivery-flow.js.map +0 -1
  52. package/dist/short-lived-delivery-journal.d.ts +0 -34
  53. package/dist/short-lived-delivery-journal.js +0 -137
  54. package/dist/short-lived-delivery-journal.js.map +0 -1
  55. package/dist/short-lived-delivery.d.ts +0 -26
  56. package/dist/short-lived-delivery.js +0 -139
  57. package/dist/short-lived-delivery.js.map +0 -1
package/dist/commands.js CHANGED
@@ -4,34 +4,23 @@ import { join } from "node:path";
4
4
  import { getStoragePath, loadAccounts, setStoragePath, setStoragePathDirect } from "codex-multi-auth/storage";
5
5
  import { normalizeCodexServiceUrl } from "./client.js";
6
6
  import { CodexServiceError } from "./errors.js";
7
- import { backupCodexFileCredentials, codexFileCredentialsMatchAccount, discardCodexFileCredentialBackup, inspectLocalCodexCredentialStatus, readManagedCodexLineageState, resetManagedCodexFileCredentials, restoreCodexFileCredentials, writeCodexChatgptFileCredentials } from "./credentials.js";
7
+ import { backupCodexFileCredentials, codexFileCredentialsMatchAccount, inspectLocalCodexCredentialStatus, resetManagedCodexFileCredentials, restoreCodexFileCredentials, writeCodexChatgptFileCredentials } from "./credentials.js";
8
8
  import { CODEX_DISTRIBUTION_SCHEMA_VERSION } from "./protocol.js";
9
9
  import { formatCodexAccountDisplayIdentity, resolveCodexAdminAccount, resolveCodexAccountDisplayName } from "./account-display.js";
10
10
  import { loadCodexQuotaUploads } from "./quota-cache.js";
11
11
  import { cleanupLegacyCodexProvider } from "./legacy-cleanup.js";
12
- import { executeCodexEnrollmentLogin } from "./enrollment-flow.js";
13
- import { executeCodexMigrationLoginBatch } from "./migration-login-batch.js";
14
- import { executeCodexShortLivedCredentialDelivery } from "./short-lived-delivery-flow.js";
15
12
  import { executeCodexTokenPoolV4Delivery, reconcilePendingManagedCodexV3Operation, reportManagedCodexV3Credentials } from "./managed-credential-v3-flow.js";
16
13
  import { resolveManagedCodexV3AssignmentTarget } from "./managed-credential-v3-assignment.js";
17
14
  import { executeCodexTokenPoolV4Login } from "./token-pool-v4-upload-flow.js";
18
15
  import { readManagedCodexV3State } from "./managed-credential-v3.js";
19
16
  import { profileAccountTokenPoolV4Enabled, profileMultiDeviceCredentialSyncEnabled, profileRequestBoundDeviceCredentialsEnabled } from "./profile.js";
20
17
  import { superviseCodexV5Credentials, synchronizeCodexV5SelectionOnce } from "./multi-device-v5-flow.js";
21
- import { executeCodexAssignmentRetirementLocalDelete } from "./retirement-flow.js";
22
- import { clearCodexSelection, loadCodexSelection, rememberCodexSelectionProviderAccount, saveCodexSelection } from "./selection.js";
18
+ import { clearCodexSelection, loadCodexSelection, saveCodexSelection } from "./selection.js";
23
19
  import { installCodexShortcuts, removeCodexShortcuts } from "./shortcuts.js";
24
20
  import { isCodexAccountAvailable, isCodexSubscriptionExpired, renderCodexAccountDashboard, selectCodexAccount } from "./tui.js";
25
- import { readCodexShortLivedDeliveryJournal } from "./short-lived-delivery-journal.js";
26
- import { superviseCodexShortLivedCredentials } from "./credential-supervisor.js";
27
21
  import { ensureCodexV6DeviceCredentials, reconcileCodexV6DeviceAccess, reportCodexV6DeviceCredentials, requireAvailableCodexV6DeviceAccess } from "./request-bound-device-v6-flow.js";
28
- import { getOrCreateCodexInstallationId } from "./multi-device-v5-state.js";
29
22
  import { resetCodexV6LocalState } from "./request-bound-device-v6-state.js";
30
23
  export const NO_AVAILABLE_CHATGPT_ACCOUNT_MESSAGE = "当前用户没有绑定可用的 ChatGPT 账号。请前往 OA 系统提交 IT11 工单,申请独享或共享的 ChatGPT 个人账号。";
31
- // The Server refreshes a user-bound lineage only inside its final 60-second
32
- // credential safety window. Asking for a new delivery earlier would merely
33
- // re-encrypt the same access/ID tokens and must not be treated as a renewal.
34
- const SERVER_MANAGED_SHORT_LIVED_REFRESH_WINDOW_MS = 60_000;
35
24
  const SCHEMA_V2_LEGACY_MANAGEMENT_DISABLED = "schema_v2_legacy_management_disabled";
36
25
  function findV3Assignment(input) {
37
26
  return input.assignments.items.find((item) => item.assignmentId === input.assignmentId
@@ -39,11 +28,6 @@ function findV3Assignment(input) {
39
28
  && item.providerAccountId === input.providerAccountId
40
29
  && item.lineageId === input.lineageId);
41
30
  }
42
- export function effectiveCodexCredentialRefreshBefore(requestedMs, serverManagedShortLived) {
43
- return serverManagedShortLived
44
- ? Math.min(requestedMs, SERVER_MANAGED_SHORT_LIVED_REFRESH_WINDOW_MS)
45
- : requestedMs;
46
- }
47
31
  async function json(command, deps) {
48
32
  if (command.optsWithGlobals().json)
49
33
  return true;
@@ -62,10 +46,6 @@ function success(value) {
62
46
  function importFailures(credentials) {
63
47
  return credentials.filter(({ status }) => status === "REJECTED" || status === "UNKNOWN");
64
48
  }
65
- function schemaV2LifecycleEnabled(config) {
66
- const capabilities = config.profile?.userBoundRefreshTokens;
67
- return capabilities?.delivery === true || capabilities?.migrationPlans === true;
68
- }
69
49
  function accountTokenPoolV4Enabled(config) {
70
50
  return profileAccountTokenPoolV4Enabled(config.profile);
71
51
  }
@@ -85,19 +65,9 @@ function codexConfigPathForEnv(env) {
85
65
  return codexHome ? join(codexHome, "config.toml") : undefined;
86
66
  }
87
67
  function assertLegacyManagementEnabled(config) {
88
- const requestBound = requestBoundDeviceCredentialsEnabled(config);
89
- if (!schemaV2LifecycleEnabled(config) && !requestBound)
68
+ if (!requestBoundDeviceCredentialsEnabled(config))
90
69
  return;
91
- throw new CodexServiceError(requestBound
92
- ? "Request-bound per-device credentials are enabled; legacy direct assignment management is disabled."
93
- : "Schema-v2 user-bound credentials are enabled; legacy refresh-token pool management is disabled.", SCHEMA_V2_LEGACY_MANAGEMENT_DISABLED, 409);
94
- }
95
- function assignmentCredentialStatus(assignment) {
96
- if (isCodexSubscriptionExpired(assignment))
97
- return "EXPIRED";
98
- if (assignment.deliveryEligible)
99
- return "AVAILABLE";
100
- return ["NEEDS_REAUTH", "LOST", "DIVERGED", "COMPROMISED"].includes(assignment.lineageStatus ?? "") ? "NEEDS_REAUTH" : "TEMPORARILY_UNAVAILABLE";
70
+ throw new CodexServiceError("Request-bound per-device credentials are enabled; legacy direct assignment management is disabled.", SCHEMA_V2_LEGACY_MANAGEMENT_DISABLED, 409);
101
71
  }
102
72
  function assertCodexSubscriptionActive(subscription, label) {
103
73
  if (!isCodexSubscriptionExpired(subscription))
@@ -290,31 +260,6 @@ async function loadAccountData(deps, options = {}, allowV4Replacement = false) {
290
260
  preferredV3Assignments
291
261
  };
292
262
  }
293
- if (deps.config.profile?.userBoundRefreshTokens?.delivery === true) {
294
- const assignments = await deps.clientFor(serviceConnection(options)).listCredentialAssignments();
295
- const accounts = assignments.items.map((assignment) => ({
296
- id: assignment.managedAccountId,
297
- accountId: assignment.assignmentId,
298
- label: assignment.label,
299
- isDefault: previous?.account.assignmentId === assignment.assignmentId,
300
- ...(assignment.credentialExpiresAt
301
- ? { accessExpiresAt: Date.parse(assignment.credentialExpiresAt) }
302
- : {}),
303
- ...subscriptionSummary(assignment),
304
- credentialStatus: assignmentCredentialStatus(assignment),
305
- lineageStatus: assignment.lineageStatus ?? undefined,
306
- quota: null
307
- }));
308
- return {
309
- response: {
310
- schemaVersion: CODEX_DISTRIBUTION_SCHEMA_VERSION,
311
- accounts,
312
- defaultAccountId: previous?.account.managedAccountId ?? null
313
- },
314
- selectedAccountId: previous?.account.managedAccountId,
315
- assignments
316
- };
317
- }
318
263
  const response = await deps.clientFor(serviceConnection(options)).listAccounts();
319
264
  return {
320
265
  response,
@@ -443,39 +388,6 @@ async function installCredentials(deps, selection, service, allowV4Replacement =
443
388
  });
444
389
  return { expiresAt: Date.parse(delivered.credentialExpiresAt), selection };
445
390
  }
446
- if (deps.config.profile?.userBoundRefreshTokens?.delivery === true) {
447
- await discardCodexFileCredentialBackup(deps.env);
448
- if (selection.account.distributionMode !== "SERVER_MANAGED_SHORT_LIVED"
449
- || !selection.account.assignmentId || !selection.account.managedAccountId) {
450
- throw new CodexServiceError("The saved Codex selection is not a schema-v2 credential assignment.", "codex_assignment_not_found", 404);
451
- }
452
- const assignments = await client.listCredentialAssignments();
453
- const assignment = assignments.items.find((item) => item.assignmentId === selection.account.assignmentId
454
- && item.managedAccountId === selection.account.managedAccountId);
455
- if (!assignment) {
456
- throw new CodexServiceError("The selected schema-v2 credential assignment is no longer active.", "codex_assignment_not_found", 404);
457
- }
458
- const state = await readManagedCodexLineageState(deps.env);
459
- const journal = await readCodexShortLivedDeliveryJournal(deps.env);
460
- const recoverable = journal?.tenantId === deps.config.connection.tenantId
461
- && journal.assignmentId === assignment.assignmentId
462
- || state?.assignmentId === assignment.assignmentId
463
- && (state.tenantId === deps.config.connection.tenantId || state.tenantId === null)
464
- && (state.phase === "INSTALLING"
465
- || state.phase === "INSTALLED_PENDING_ACK" && Date.parse(state.ackExpiresAt) > Date.now());
466
- if (!assignment.deliveryEligible && !recoverable) {
467
- throw new CodexServiceError("The selected schema-v2 credential assignment is not ready for delivery.", "short_lived_credential_lineage_not_ready", 409);
468
- }
469
- const delivered = await executeCodexShortLivedCredentialDelivery({
470
- client,
471
- tenantId: deps.config.connection.tenantId,
472
- assignmentId: assignment.assignmentId,
473
- allowNewDelivery: assignment.deliveryEligible,
474
- env: deps.env
475
- });
476
- const updated = await rememberCodexSelectionProviderAccount(deps.config.persistence.configDirectory, assignment.assignmentId, delivered.providerAccountId);
477
- return { expiresAt: Date.parse(delivered.credentialExpiresAt), selection: updated };
478
- }
479
391
  await backupCodexFileCredentials(deps.env);
480
392
  const response = await client.getLoginCredentials(selection.account.id);
481
393
  await writeCodexChatgptFileCredentials({
@@ -633,43 +545,6 @@ async function selectedCredentials(deps, requested, options = {}) {
633
545
  if (!selection || serviceChanged || requested && !requestedMatchesSelection) {
634
546
  selection = (await chooseAccount(deps, requested, options)).selection;
635
547
  }
636
- if (deps.config.profile?.userBoundRefreshTokens?.delivery === true) {
637
- if (selection.account.distributionMode !== "SERVER_MANAGED_SHORT_LIVED") {
638
- selection = (await chooseAccount(deps, requested, options)).selection;
639
- }
640
- const assignments = await deps.clientFor({
641
- serverUrl: selection.serviceUrl,
642
- allowInsecureHttp: selection.allowInsecurePoc
643
- }).listCredentialAssignments();
644
- const assignment = assignments.items.find((item) => item.assignmentId === selection.account.assignmentId
645
- && item.managedAccountId === selection.account.managedAccountId);
646
- if (!assignment) {
647
- throw new CodexServiceError("The selected schema-v2 credential assignment is no longer active.", "codex_assignment_not_found", 404);
648
- }
649
- assertCodexSubscriptionActive(assignment, assignment.label);
650
- const state = await readManagedCodexLineageState(deps.env);
651
- const journal = await readCodexShortLivedDeliveryJournal(deps.env);
652
- const stateMatches = state !== undefined
653
- && (state.tenantId === deps.config.connection.tenantId || state.tenantId === null)
654
- && state.assignmentId === assignment.assignmentId
655
- && state.managedAccountId === assignment.managedAccountId;
656
- const local = await inspectLocalCodexCredentialStatus(stateMatches ? state.providerAccountId : selection.account.providerAccountId, deps.env);
657
- const pending = journal?.tenantId === deps.config.connection.tenantId
658
- && journal.assignmentId === assignment.assignmentId
659
- || stateMatches && state.phase !== "INSTALLED";
660
- if (!assignment.deliveryEligible || !stateMatches || !local.usable
661
- || local.matchesExpectedAccount !== true || pending) {
662
- return installCredentials(deps, selection, {
663
- serverUrl: selection.serviceUrl,
664
- allowInsecureHttp: selection.allowInsecurePoc
665
- });
666
- }
667
- const matchingState = state;
668
- if (selection.account.providerAccountId !== matchingState.providerAccountId) {
669
- selection = await rememberCodexSelectionProviderAccount(deps.config.persistence.configDirectory, assignment.assignmentId, matchingState.providerAccountId);
670
- }
671
- return { selection, expiresAt: local.expiresAt ?? Date.parse(matchingState.credentialExpiresAt) };
672
- }
673
548
  if (!selection.account.accountId) {
674
549
  throw new CodexServiceError("The saved Codex selection has no provider account.", "invalid_configuration");
675
550
  }
@@ -762,21 +637,7 @@ async function launchWithSelectedCredentials(deps, selected, launch) {
762
637
  }
763
638
  }
764
639
  }
765
- if (deps.config.profile?.userBoundRefreshTokens?.delivery !== true)
766
- return launch();
767
- let current = selected;
768
- return superviseCodexShortLivedCredentials({
769
- initialExpiresAt: current.expiresAt,
770
- launch: (signal) => launch(signal),
771
- renew: async () => {
772
- const installed = await installCredentials(deps, current.selection, {
773
- serverUrl: current.selection.serviceUrl,
774
- allowInsecureHttp: current.selection.allowInsecurePoc
775
- });
776
- current = installed;
777
- return { expiresAt: installed.expiresAt };
778
- }
779
- });
640
+ return launch();
780
641
  }
781
642
  export function registerCodexCommands(program, deps, options = {}) {
782
643
  const codex = options.nested === false
@@ -850,41 +711,7 @@ export function registerCodexCommands(program, deps, options = {}) {
850
711
  });
851
712
  }
852
713
  const requests = codex.command("request")
853
- .description("Apply for Codex access and manage request-bound device token sets");
854
- serviceOptions(requests.command("create")
855
- .description("Submit an account request and identify this device as the initial binding candidate")
856
- .requiredOption("--reason <text>", "申请原因,至少 5 个字符")
857
- .requiredOption("--until <timestamp>", "申请有效期结束时间,必须包含时区")
858
- .option("--devices <count>", "申请的设备数量", "1")
859
- .option("--device-label <label>", "当前设备名称")
860
- .option("--idempotency-key <key>", "重试时复用同一个幂等键"))
861
- .action(async (opts, command) => {
862
- assertRequestBoundDeviceCredentialsEnabled(deps.config);
863
- const requestedValidUntil = parseRequestInstant(opts.until, "--until");
864
- if (Date.parse(requestedValidUntil) <= Date.now())
865
- throw new CodexServiceError("--until 必须晚于当前时间。", "invalid_codex_account_request");
866
- const installationId = await getOrCreateCodexInstallationId(deps.env);
867
- const clientRequestId = randomUUID();
868
- const result = await deps.clientFor(serviceConnection(opts)).createV6AccountRequest({
869
- schemaVersion: 6,
870
- clientRequestId,
871
- reason: opts.reason,
872
- requestedDeviceCount: parsePositiveInteger(opts.devices, "--devices"),
873
- requestedValidUntil,
874
- initialDevice: {
875
- installationId,
876
- label: (opts.deviceLabel?.trim() || hostname() || "Codex device").slice(0, 128),
877
- platform: v6Platform()
878
- }
879
- }, opts.idempotencyKey?.trim() || clientRequestId);
880
- if (await json(command, deps))
881
- print(deps.output, success({
882
- ...result, installationId
883
- }));
884
- else
885
- deps.output.write(`申请已提交:${formatV6Request(result.request)}\n`
886
- + `首台设备候选:${installationId}。审批后每个设备将获得独立完整 Token Set。\n`);
887
- });
714
+ .description("Inspect administrator-created requests and reconcile this device");
888
715
  serviceOptions(requests.command("list").description("List the current user's Codex account requests"))
889
716
  .action(async (opts, command) => {
890
717
  assertRequestBoundDeviceCredentialsEnabled(deps.config);
@@ -937,18 +764,6 @@ export function registerCodexCommands(program, deps, options = {}) {
937
764
  else
938
765
  requireAvailableCodexV6DeviceAccess(access);
939
766
  });
940
- serviceOptions(requests.command("cancel <requestId>")
941
- .description("Cancel a still-submitted account request")
942
- .requiredOption("--version <number>", "当前请求版本")
943
- .option("--idempotency-key <key>", "重试时复用同一个幂等键"))
944
- .action(async (requestId, opts, command) => {
945
- assertRequestBoundDeviceCredentialsEnabled(deps.config);
946
- const result = await deps.clientFor(serviceConnection(opts)).cancelV6AccountRequest(requestId, { schemaVersion: 6, expectedRequestVersion: parseNonNegativeInteger(opts.version, "--version") }, opts.idempotencyKey?.trim() || randomUUID());
947
- if (await json(command, deps))
948
- print(deps.output, success(result));
949
- else
950
- deps.output.write(`申请已取消:${formatV6Request(result.request)}\n`);
951
- });
952
767
  serviceOptions(codex.command("list").description("List available Codex accounts without downloading credentials"))
953
768
  .action(async (opts, command) => {
954
769
  if (requestBoundDeviceCredentialsEnabled(deps.config)) {
@@ -1093,8 +908,6 @@ export function registerCodexCommands(program, deps, options = {}) {
1093
908
  .action(async (_options, command) => {
1094
909
  const selection = await loadCodexSelection(deps.config.persistence.configDirectory);
1095
910
  const cleanup = await cleanupLegacyCodexProvider(codexConfigPathForEnv(deps.env));
1096
- if (schemaV2LifecycleEnabled(deps.config))
1097
- await discardCodexFileCredentialBackup(deps.env);
1098
911
  const auth = await resetManagedCodexFileCredentials(selection?.account.providerAccountId ?? selection?.account.accountId, deps.env);
1099
912
  await resetCodexV6LocalState(deps.env);
1100
913
  await clearCodexSelection(deps.config.persistence.configDirectory);
@@ -1193,7 +1006,7 @@ export function registerCodexCommands(program, deps, options = {}) {
1193
1006
  if (!Number.isFinite(requestedRefreshBefore) || requestedRefreshBefore <= 0) {
1194
1007
  throw new Error("--refresh-before must be positive.");
1195
1008
  }
1196
- const refreshBefore = effectiveCodexCredentialRefreshBefore(requestedRefreshBefore, deps.config.profile?.userBoundRefreshTokens?.delivery === true);
1009
+ const refreshBefore = requestedRefreshBefore;
1197
1010
  let expiresAt = selected.expiresAt;
1198
1011
  do {
1199
1012
  const timestamp = Date.now();
@@ -1296,126 +1109,6 @@ export function registerCodexCommands(program, deps, options = {}) {
1296
1109
  if (failures.length > 0)
1297
1110
  process.exitCode = 1;
1298
1111
  });
1299
- if (schemaV2LifecycleEnabled(deps.config)) {
1300
- const enrollment = codex.command("enrollment")
1301
- .description("Execute administrator-created one-shot Codex login intents");
1302
- serviceOptions(enrollment.command("status <intent>").description("Show redacted enrollment intent status"))
1303
- .action(async (intent, opts, command) => {
1304
- const result = await deps.clientFor(serviceConnection(opts)).getEnrollmentIntent(intent);
1305
- if (await json(command, deps)) {
1306
- print(deps.output, { ok: true, ...result });
1307
- return;
1308
- }
1309
- deps.output.write(`${result.intent.id} ${result.intent.type} ${result.intent.status}`
1310
- + ` revision=${result.intent.revision}\n`);
1311
- deps.output.write(`expires=${result.intent.expiresAt}`
1312
- + `${result.intent.assignmentId ? ` assignment=${result.intent.assignmentId}` : ""}`
1313
- + `${result.intent.managedAccountId ? ` account=${result.intent.managedAccountId}` : ""}\n`);
1314
- if (!result.capabilities.candidateUpload) {
1315
- deps.output.write("安全候选上传尚未启用;当前任务只能查询或取消,不会启动 OpenAI 登录。\n");
1316
- }
1317
- });
1318
- serviceOptions(enrollment.command("login <intent>")
1319
- .description("Run one isolated official Codex login for an enrollment intent"))
1320
- .option("--device-auth", "use the official Codex device authorization flow")
1321
- .action(async (intent, _opts, command) => {
1322
- const result = await executeCodexEnrollmentLogin({
1323
- client: deps.clientFor(serviceConnection(_opts)),
1324
- tenantId: deps.config.connection.tenantId,
1325
- intentId: intent,
1326
- deviceAuth: _opts.deviceAuth
1327
- });
1328
- if (await json(command, deps))
1329
- print(deps.output, { ok: true, ...result });
1330
- else {
1331
- deps.output.write(`${result.intent.id} 登录候选已处理;状态=${result.intent.status}`
1332
- + `${result.result.managedAccountId ? `,账号=${result.result.managedAccountId}` : ""}`
1333
- + `${result.result.lineageId ? `,lineage=${result.result.lineageId}` : ""}`
1334
- + "。\n");
1335
- if (result.result.outcome === "TEMPORARILY_UNAVAILABLE") {
1336
- deps.output.write("同一进程内的安全重试已耗尽;请取消并新建任务,不能用另一次登录覆盖已保留的候选。\n");
1337
- }
1338
- }
1339
- });
1340
- serviceOptions(enrollment.command("login-batch <planId>")
1341
- .description("Sequentially execute isolated logins from a migration plan queue")
1342
- .option("--device-auth", "use the official Codex device authorization flow")
1343
- .option("--dry-run", "show the redacted sequential plan without starting login")
1344
- .option("--page-size <count>", "migration queue page size", "100"))
1345
- .action(async (planId, opts, command) => {
1346
- if (deps.config.profile?.userBoundRefreshTokens?.migrationPlans !== true) {
1347
- throw new CodexServiceError("The selected tenant has not enabled schema-v2 migration login plans.", "capability_not_enabled", 409);
1348
- }
1349
- const pageSize = Number(opts.pageSize);
1350
- if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) {
1351
- throw new CodexServiceError("--page-size must be an integer from 1 to 100.", "invalid_configuration");
1352
- }
1353
- const batch = await executeCodexMigrationLoginBatch({
1354
- client: deps.clientFor(serviceConnection(opts)),
1355
- tenantId: deps.config.connection.tenantId,
1356
- planId,
1357
- deviceAuth: opts.deviceAuth,
1358
- dryRun: opts.dryRun,
1359
- pageSize
1360
- });
1361
- const failed = batch.counts.failed > 0
1362
- || ["TARGET_UNCERTAIN", "AUTHENTICATION_ERROR", "SERVICE_ERROR", "LOGIN_ERROR"]
1363
- .includes(batch.stopReason);
1364
- if (await json(command, deps)) {
1365
- print(deps.output, { ok: !failed, ...batch });
1366
- }
1367
- else {
1368
- deps.output.write(`migration=${batch.plan.id} status=${batch.plan.status} revision=${batch.plan.revision}`
1369
- + ` stop=${batch.stopReason}${batch.dryRun ? " dry-run" : ""}\n`);
1370
- deps.output.write(`capacity: verifiedMinimum=${batch.capacity.verifiedMinimum}, verifiedLimit=unknown,`
1371
- + ` sequentialRequired=${batch.capacity.sequentialRequired}; verifiedMinimum 不是上限。\n`);
1372
- for (const item of batch.items) {
1373
- deps.output.write(`${item.assignmentId} ${item.action}/${item.status}`
1374
- + `${item.intentId ? ` intent=${item.intentId}` : ""}`
1375
- + `${item.outcome ? ` outcome=${item.outcome}` : ""}`
1376
- + `${item.skipReason ? ` skip=${item.skipReason}` : ""}`
1377
- + `${item.safeErrorCode ? ` error=${item.safeErrorCode}` : ""}\n`);
1378
- }
1379
- deps.output.write(`summary: attempted=${batch.counts.attempted}, completed=${batch.counts.completed},`
1380
- + ` failed=${batch.counts.failed}, skipped=${batch.counts.skipped}.\n`);
1381
- }
1382
- if (failed)
1383
- process.exitCode = 1;
1384
- });
1385
- serviceOptions(enrollment.command("cancel <intent>").description("Cancel an enrollment intent"))
1386
- .option("--idempotency-key <key>", "reuse a prior cancellation idempotency key")
1387
- .action(async (intent, opts, command) => {
1388
- const client = deps.clientFor(serviceConnection(opts));
1389
- const current = await client.getEnrollmentIntent(intent);
1390
- const result = await client.cancelEnrollmentIntent(intent, {
1391
- idempotencyKey: opts.idempotencyKey?.trim() || randomUUID(),
1392
- revision: current.intent.revision
1393
- });
1394
- if (await json(command, deps))
1395
- print(deps.output, { ok: true, ...result });
1396
- else
1397
- deps.output.write(`${result.intent.id} 已取消;状态=${result.intent.status}。\n`);
1398
- });
1399
- const retirement = codex.command("retirement")
1400
- .description("Remove an exact locally installed lineage for a Server retirement request");
1401
- serviceOptions(retirement.command("acknowledge <retirement>")
1402
- .description("Delete the matching managed short-lived credentials and append local evidence")
1403
- .option("--idempotency-key <key>", "reuse a prior local deletion acknowledgement key"))
1404
- .action(async (retirementId, opts, command) => {
1405
- const result = await executeCodexAssignmentRetirementLocalDelete({
1406
- client: deps.clientFor(serviceConnection(opts)),
1407
- retirementId,
1408
- env: deps.env,
1409
- ...(opts.idempotencyKey?.trim() ? { idempotencyKey: opts.idempotencyKey.trim() } : {})
1410
- });
1411
- if (await json(command, deps))
1412
- print(deps.output, { ok: true, ...result });
1413
- else
1414
- deps.output.write(`本机匹配的短效凭据已删除并记录证据;retirement=${result.retirement.id}`
1415
- + `,revision=${result.retirement.revision},状态=${result.retirement.status}。\n`
1416
- + "该证据不代表其他终端已清理;最终退役仍等待全部已披露短效凭据自然过期。\n");
1417
- });
1418
- }
1419
1112
  const admin = codex.command("admin")
1420
1113
  .description("Administer server-managed Codex accounts and assignments");
1421
1114
  const adminRequests = admin.command("requests")