opencode-copilot-account-switcher 0.12.4 → 0.13.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.
@@ -40,8 +40,13 @@ export const CODEX_PROVIDER_DESCRIPTOR = {
40
40
  commands: [
41
41
  "codex-status",
42
42
  ],
43
- menuEntries: [],
43
+ menuEntries: [
44
+ "switch-account",
45
+ "add-account",
46
+ "refresh-snapshot",
47
+ ],
44
48
  capabilities: [
49
+ "auth",
45
50
  "slash-commands",
46
51
  ],
47
52
  };
@@ -61,6 +66,6 @@ export function createCodexProviderDescriptor(input = {}) {
61
66
  auth: {
62
67
  provider: "openai",
63
68
  },
64
- enabledByDefault: input.enabled === true,
69
+ enabledByDefault: input.enabled !== false,
65
70
  };
66
71
  }
@@ -1,7 +1,8 @@
1
- import { COPILOT_PROVIDER_DESCRIPTOR } from "./descriptor.js";
1
+ import { CODEX_PROVIDER_DESCRIPTOR, COPILOT_PROVIDER_DESCRIPTOR } from "./descriptor.js";
2
2
  import { createCodexProviderDescriptor, createCopilotProviderDescriptor } from "./descriptor.js";
3
3
  const PROVIDER_DESCRIPTORS = [
4
4
  COPILOT_PROVIDER_DESCRIPTOR,
5
+ CODEX_PROVIDER_DESCRIPTOR,
5
6
  ];
6
7
  export function listProviderDescriptors() {
7
8
  return [...PROVIDER_DESCRIPTORS];
@@ -21,7 +22,7 @@ export function createProviderRegistry(input) {
21
22
  descriptor: createCopilotProviderDescriptor({ buildPluginHooks: input.buildPluginHooks }),
22
23
  },
23
24
  codex: {
24
- descriptor: createCodexProviderDescriptor({ enabled: false }),
25
+ descriptor: createCodexProviderDescriptor({ enabled: true }),
25
26
  },
26
27
  };
27
28
  }
package/dist/ui/menu.d.ts CHANGED
@@ -81,7 +81,21 @@ export type MenuAction = {
81
81
  type: "cancel";
82
82
  };
83
83
  export type MenuLanguage = "zh" | "en";
84
- export declare function getMenuCopy(language?: MenuLanguage): {
84
+ export type MenuProvider = "copilot" | "codex";
85
+ type MenuCapabilities = {
86
+ importAuth: boolean;
87
+ quota: boolean;
88
+ refreshIdentity: boolean;
89
+ checkModels: boolean;
90
+ defaultAccountGroup: boolean;
91
+ assignModels: boolean;
92
+ loopSafety: boolean;
93
+ policyScope: boolean;
94
+ experimentalSlashCommands: boolean;
95
+ networkRetry: boolean;
96
+ syntheticAgentInitiator: boolean;
97
+ };
98
+ export declare function getMenuCopy(language?: MenuLanguage, provider?: MenuProvider): {
85
99
  menuTitle: string;
86
100
  menuSubtitle: string;
87
101
  switchLanguageLabel: string;
@@ -117,6 +131,7 @@ export declare function getMenuCopy(language?: MenuLanguage): {
117
131
  removeAll: string;
118
132
  };
119
133
  export declare function buildMenuItems(input: {
134
+ provider?: MenuProvider;
120
135
  accounts: AccountInfo[];
121
136
  refresh?: {
122
137
  enabled: boolean;
@@ -130,9 +145,11 @@ export declare function buildMenuItems(input: {
130
145
  networkRetryEnabled: boolean;
131
146
  syntheticAgentInitiatorEnabled?: boolean;
132
147
  experimentalSlashCommandsEnabled?: boolean;
148
+ capabilities?: Partial<MenuCapabilities>;
133
149
  language?: MenuLanguage;
134
150
  }): MenuItem<MenuAction>[];
135
151
  export declare function showMenu(accounts: AccountInfo[], input?: {
152
+ provider?: MenuProvider;
136
153
  refresh?: {
137
154
  enabled: boolean;
138
155
  minutes: number;
@@ -145,6 +162,13 @@ export declare function showMenu(accounts: AccountInfo[], input?: {
145
162
  networkRetryEnabled?: boolean;
146
163
  syntheticAgentInitiatorEnabled?: boolean;
147
164
  experimentalSlashCommandsEnabled?: boolean;
165
+ capabilities?: Partial<MenuCapabilities>;
148
166
  language?: MenuLanguage;
149
167
  }): Promise<MenuAction>;
150
- export declare function showAccountActions(account: AccountInfo): Promise<"switch" | "remove" | "back">;
168
+ export declare function buildAccountActionItems(account: AccountInfo, input?: {
169
+ provider?: MenuProvider;
170
+ }): MenuItem<"switch" | "remove" | "back" | "models">[];
171
+ export declare function showAccountActions(account: AccountInfo, input?: {
172
+ provider?: MenuProvider;
173
+ }): Promise<"switch" | "remove" | "back">;
174
+ export {};
package/dist/ui/menu.js CHANGED
@@ -1,7 +1,111 @@
1
1
  import { ANSI } from "./ansi.js";
2
2
  import { select } from "./select.js";
3
3
  import { confirm } from "./confirm.js";
4
- export function getMenuCopy(language = "zh") {
4
+ function defaultMenuCapabilities(provider) {
5
+ if (provider === "codex") {
6
+ return {
7
+ importAuth: false,
8
+ quota: true,
9
+ refreshIdentity: false,
10
+ checkModels: false,
11
+ defaultAccountGroup: false,
12
+ assignModels: false,
13
+ loopSafety: false,
14
+ policyScope: false,
15
+ experimentalSlashCommands: false,
16
+ networkRetry: false,
17
+ syntheticAgentInitiator: false,
18
+ };
19
+ }
20
+ return {
21
+ importAuth: true,
22
+ quota: true,
23
+ refreshIdentity: true,
24
+ checkModels: true,
25
+ defaultAccountGroup: true,
26
+ assignModels: true,
27
+ loopSafety: true,
28
+ policyScope: true,
29
+ experimentalSlashCommands: true,
30
+ networkRetry: true,
31
+ syntheticAgentInitiator: true,
32
+ };
33
+ }
34
+ export function getMenuCopy(language = "zh", provider = "copilot") {
35
+ if (provider === "codex") {
36
+ if (language === "en") {
37
+ return {
38
+ menuTitle: "OpenAI Codex accounts",
39
+ menuSubtitle: "Select an action or account",
40
+ switchLanguageLabel: "切换到中文",
41
+ actionsHeading: "Actions",
42
+ addAccount: "Add account",
43
+ addAccountHint: "OpenAI OAuth login",
44
+ importAuth: "Import from auth.json",
45
+ checkQuotas: "Refresh snapshots",
46
+ refreshIdentity: "Sync account identity",
47
+ checkModels: "Sync available models",
48
+ defaultAccountGroup: "Default account group",
49
+ assignModels: "Assign account groups per model",
50
+ autoRefreshOn: "Auto refresh: On",
51
+ autoRefreshOff: "Auto refresh: Off",
52
+ setRefresh: "Set refresh interval",
53
+ loopSafetyOn: "Guided Loop Safety: On",
54
+ loopSafetyOff: "Guided Loop Safety: Off",
55
+ loopSafetyHint: "Reduce unnecessary handoff replies while work can continue",
56
+ policyScopeCopilotOnly: "Policy default scope: Current provider only",
57
+ policyScopeAllModels: "Policy default scope: All models",
58
+ policyScopeHint: "Choose whether Guided Loop Safety applies only to the current provider by default or to all models",
59
+ experimentalSlashCommandsOn: "Experimental slash commands: On",
60
+ experimentalSlashCommandsOff: "Experimental slash commands: Off",
61
+ experimentalSlashCommandsHint: "Experimental provider-specific slash commands",
62
+ retryOn: "Network Retry: On",
63
+ retryOff: "Network Retry: Off",
64
+ retryHint: "Helps recover some requests after retries or malformed responses",
65
+ syntheticInitiatorOn: "Send synthetic messages as agent: On",
66
+ syntheticInitiatorOff: "Send synthetic messages as agent: Off",
67
+ syntheticInitiatorHint: "Changes upstream behavior; misuse may increase billing risk or trigger abuse signals",
68
+ accountsHeading: "Accounts",
69
+ dangerHeading: "Danger zone",
70
+ removeAll: "Remove all accounts",
71
+ };
72
+ }
73
+ return {
74
+ menuTitle: "OpenAI Codex 账号",
75
+ menuSubtitle: "请选择操作或账号",
76
+ switchLanguageLabel: "Switch to English",
77
+ actionsHeading: "操作",
78
+ addAccount: "添加账号",
79
+ addAccountHint: "OpenAI OAuth 登录",
80
+ importAuth: "从 auth.json 导入",
81
+ checkQuotas: "刷新快照",
82
+ refreshIdentity: "同步账号身份信息",
83
+ checkModels: "同步可用模型列表",
84
+ defaultAccountGroup: "配置默认账号组",
85
+ assignModels: "为模型配置账号组",
86
+ autoRefreshOn: "自动刷新:已开启",
87
+ autoRefreshOff: "自动刷新:已关闭",
88
+ setRefresh: "设置刷新间隔",
89
+ loopSafetyOn: "Guided Loop Safety:已开启",
90
+ loopSafetyOff: "Guided Loop Safety:已关闭",
91
+ loopSafetyHint: "让模型更少无谓汇报,没做完前优先继续干活",
92
+ policyScopeCopilotOnly: "Policy 默认注入范围:仅当前 provider",
93
+ policyScopeAllModels: "Policy 默认注入范围:所有模型",
94
+ policyScopeHint: "决定 Guided Loop Safety 默认只作用于当前 provider,还是扩展到所有模型",
95
+ experimentalSlashCommandsOn: "实验性 Slash Commands:已开启",
96
+ experimentalSlashCommandsOff: "实验性 Slash Commands:已关闭",
97
+ experimentalSlashCommandsHint: "当前 provider 的实验性 Slash Commands 开关",
98
+ retryOn: "Network Retry:已开启",
99
+ retryOff: "Network Retry:已关闭",
100
+ retryHint: "请求异常时可自动重试并修复部分请求",
101
+ syntheticInitiatorOn: "synthetic 消息按 agent 身份发送:已开启",
102
+ syntheticInitiatorOff: "synthetic 消息按 agent 身份发送:已关闭",
103
+ syntheticInitiatorHint: "会改变与 upstream 的默认行为;误用可能带来异常计费或 abuse 风险",
104
+ accountsHeading: "账号",
105
+ dangerHeading: "危险操作",
106
+ removeAll: "删除全部账号",
107
+ };
108
+ }
5
109
  if (language === "en") {
6
110
  return {
7
111
  menuTitle: "GitHub Copilot accounts",
@@ -100,67 +204,108 @@ function getStatusBadge(status) {
100
204
  return "";
101
205
  }
102
206
  export function buildMenuItems(input) {
103
- const copy = getMenuCopy(input.language);
207
+ const provider = input.provider ?? "copilot";
208
+ const copy = getMenuCopy(input.language, provider);
209
+ const capabilities = {
210
+ ...defaultMenuCapabilities(provider),
211
+ ...input.capabilities,
212
+ };
213
+ if (provider === "codex") {
214
+ capabilities.refreshIdentity = false;
215
+ capabilities.checkModels = false;
216
+ capabilities.defaultAccountGroup = false;
217
+ capabilities.assignModels = false;
218
+ capabilities.loopSafety = false;
219
+ capabilities.policyScope = false;
220
+ capabilities.experimentalSlashCommands = false;
221
+ capabilities.networkRetry = false;
222
+ capabilities.syntheticAgentInitiator = false;
223
+ }
104
224
  const quotaHint = input.lastQuotaRefresh ? `last ${formatRelativeTime(input.lastQuotaRefresh)}` : undefined;
105
225
  const loopSafetyProviderScope = input.loopSafetyProviderScope ?? "copilot-only";
106
226
  const experimentalSlashCommandsEnabled = input.experimentalSlashCommandsEnabled !== false;
107
- return [
227
+ const actions = [
108
228
  { label: copy.actionsHeading, value: { type: "cancel" }, kind: "heading" },
109
229
  { label: copy.switchLanguageLabel, value: { type: "toggle-language" }, color: "cyan" },
110
230
  { label: copy.addAccount, value: { type: "add" }, color: "cyan", hint: copy.addAccountHint },
111
- { label: copy.importAuth, value: { type: "import" }, color: "cyan" },
112
- { label: copy.checkQuotas, value: { type: "quota" }, color: "cyan", hint: quotaHint },
113
- { label: copy.refreshIdentity, value: { type: "refresh-identity" }, color: "cyan" },
114
- { label: copy.checkModels, value: { type: "check-models" }, color: "cyan" },
115
- {
231
+ ];
232
+ if (capabilities.importAuth) {
233
+ actions.push({ label: copy.importAuth, value: { type: "import" }, color: "cyan" });
234
+ }
235
+ if (capabilities.quota) {
236
+ actions.push({ label: copy.checkQuotas, value: { type: "quota" }, color: "cyan", hint: quotaHint });
237
+ }
238
+ if (capabilities.refreshIdentity) {
239
+ actions.push({ label: copy.refreshIdentity, value: { type: "refresh-identity" }, color: "cyan" });
240
+ }
241
+ if (capabilities.checkModels) {
242
+ actions.push({ label: copy.checkModels, value: { type: "check-models" }, color: "cyan" });
243
+ }
244
+ if (capabilities.defaultAccountGroup) {
245
+ actions.push({
116
246
  label: copy.defaultAccountGroup,
117
247
  value: { type: "configure-default-group" },
118
248
  color: "cyan",
119
249
  hint: input.defaultAccountGroupCount !== undefined ? `${input.defaultAccountGroupCount} selected` : undefined,
120
- },
121
- {
250
+ });
251
+ }
252
+ if (capabilities.assignModels) {
253
+ actions.push({
122
254
  label: copy.assignModels,
123
255
  value: { type: "assign-models" },
124
256
  color: "cyan",
125
257
  hint: input.modelAccountAssignmentCount ? `${input.modelAccountAssignmentCount} groups` : undefined,
126
- },
127
- {
128
- label: input.refresh?.enabled ? copy.autoRefreshOn : copy.autoRefreshOff,
129
- value: { type: "toggle-refresh" },
130
- color: "cyan",
131
- hint: input.refresh ? `${input.refresh.minutes}m` : undefined,
132
- },
133
- { label: copy.setRefresh, value: { type: "set-interval" }, color: "cyan" },
134
- {
258
+ });
259
+ }
260
+ actions.push({
261
+ label: input.refresh?.enabled ? copy.autoRefreshOn : copy.autoRefreshOff,
262
+ value: { type: "toggle-refresh" },
263
+ color: "cyan",
264
+ hint: input.refresh ? `${input.refresh.minutes}m` : undefined,
265
+ });
266
+ actions.push({ label: copy.setRefresh, value: { type: "set-interval" }, color: "cyan" });
267
+ if (capabilities.loopSafety) {
268
+ actions.push({
135
269
  label: input.loopSafetyEnabled ? copy.loopSafetyOn : copy.loopSafetyOff,
136
270
  value: { type: "toggle-loop-safety" },
137
271
  color: "cyan",
138
272
  hint: copy.loopSafetyHint,
139
- },
140
- {
273
+ });
274
+ }
275
+ if (capabilities.policyScope) {
276
+ actions.push({
141
277
  label: loopSafetyProviderScope === "all-models" ? copy.policyScopeAllModels : copy.policyScopeCopilotOnly,
142
278
  value: { type: "toggle-loop-safety-provider-scope" },
143
279
  color: "cyan",
144
280
  hint: copy.policyScopeHint,
145
- },
146
- {
281
+ });
282
+ }
283
+ if (capabilities.experimentalSlashCommands) {
284
+ actions.push({
147
285
  label: experimentalSlashCommandsEnabled ? copy.experimentalSlashCommandsOn : copy.experimentalSlashCommandsOff,
148
286
  value: { type: "toggle-experimental-slash-commands" },
149
287
  color: "cyan",
150
288
  hint: copy.experimentalSlashCommandsHint,
151
- },
152
- {
289
+ });
290
+ }
291
+ if (capabilities.networkRetry) {
292
+ actions.push({
153
293
  label: input.networkRetryEnabled ? copy.retryOn : copy.retryOff,
154
294
  value: { type: "toggle-network-retry" },
155
295
  color: "cyan",
156
296
  hint: copy.retryHint,
157
- },
158
- {
297
+ });
298
+ }
299
+ if (capabilities.syntheticAgentInitiator) {
300
+ actions.push({
159
301
  label: input.syntheticAgentInitiatorEnabled ? copy.syntheticInitiatorOn : copy.syntheticInitiatorOff,
160
302
  value: { type: "toggle-synthetic-agent-initiator" },
161
303
  color: "cyan",
162
304
  hint: copy.syntheticInitiatorHint,
163
- },
305
+ });
306
+ }
307
+ return [
308
+ ...actions,
164
309
  { label: "", value: { type: "cancel" }, separator: true },
165
310
  { label: copy.accountsHeading, value: { type: "cancel" }, kind: "heading" },
166
311
  ...input.accounts.map((account) => {
@@ -193,8 +338,10 @@ export function buildMenuItems(input) {
193
338
  export async function showMenu(accounts, input = {}) {
194
339
  let currentLanguage = input.language ?? "zh";
195
340
  while (true) {
196
- const copy = getMenuCopy(currentLanguage);
341
+ const provider = input.provider ?? "copilot";
342
+ const copy = getMenuCopy(currentLanguage, provider);
197
343
  const items = buildMenuItems({
344
+ provider,
198
345
  accounts,
199
346
  refresh: input.refresh,
200
347
  lastQuotaRefresh: input.lastQuotaRefresh,
@@ -205,6 +352,7 @@ export async function showMenu(accounts, input = {}) {
205
352
  networkRetryEnabled: input.networkRetryEnabled === true,
206
353
  syntheticAgentInitiatorEnabled: input.syntheticAgentInitiatorEnabled === true,
207
354
  experimentalSlashCommandsEnabled: input.experimentalSlashCommandsEnabled,
355
+ capabilities: input.capabilities,
208
356
  language: currentLanguage,
209
357
  });
210
358
  const result = await select(items, {
@@ -226,7 +374,20 @@ export async function showMenu(accounts, input = {}) {
226
374
  return result;
227
375
  }
228
376
  }
229
- export async function showAccountActions(account) {
377
+ export function buildAccountActionItems(account, input = {}) {
378
+ const provider = input.provider ?? "copilot";
379
+ const modelAction = provider === "copilot" && (account.modelList || account.modelsError)
380
+ ? [{ label: "View models", value: "models", color: "cyan" }]
381
+ : [];
382
+ return [
383
+ { label: "Back", value: "back" },
384
+ ...modelAction,
385
+ { label: "Switch to this account", value: "switch", color: "cyan" },
386
+ { label: "Remove this account", value: "remove", color: "red" },
387
+ ];
388
+ }
389
+ export async function showAccountActions(account, input = {}) {
390
+ const provider = input.provider ?? "copilot";
230
391
  const badge = getStatusBadge(account.status);
231
392
  const header = `${account.name}${badge ? " " + badge : ""}`;
232
393
  const info = [
@@ -234,23 +395,15 @@ export async function showAccountActions(account) {
234
395
  account.plan ? `Plan: ${account.plan}` : undefined,
235
396
  account.sku ? `SKU: ${account.sku}` : undefined,
236
397
  account.reset ? `Reset: ${account.reset}` : undefined,
237
- account.models ? `Models: ${account.models.enabled}/${account.models.enabled + account.models.disabled}` : undefined,
398
+ provider === "copilot" && account.models ? `Models: ${account.models.enabled}/${account.models.enabled + account.models.disabled}` : undefined,
238
399
  account.orgs?.length ? `Orgs: ${account.orgs.slice(0, 2).join(",")}` : undefined,
239
- account.modelsError ? `Models error: ${account.modelsError}` : undefined,
400
+ provider === "copilot" && account.modelsError ? `Models error: ${account.modelsError}` : undefined,
240
401
  ]
241
402
  .filter(Boolean)
242
403
  .join("\n");
243
404
  const subtitle = info;
244
405
  while (true) {
245
- const modelAction = account.modelList || account.modelsError
246
- ? [{ label: "View models", value: "models", color: "cyan" }]
247
- : [];
248
- const result = await select([
249
- { label: "Back", value: "back" },
250
- ...modelAction,
251
- { label: "Switch to this account", value: "switch", color: "cyan" },
252
- { label: "Remove this account", value: "remove", color: "red" },
253
- ], { message: header, subtitle, clearScreen: true, autoSelectSingle: false });
406
+ const result = await select(buildAccountActionItems(account, { provider }), { message: header, subtitle, clearScreen: true, autoSelectSingle: false });
254
407
  if (result === "models") {
255
408
  await showModels(account);
256
409
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-copilot-account-switcher",
3
- "version": "0.12.4",
3
+ "version": "0.13.0",
4
4
  "description": "GitHub Copilot account switcher plugin for OpenCode",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",