opencode-copilot-account-switcher 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,13 +12,14 @@
12
12
 
13
13
  ## English
14
14
 
15
- Manage and switch between multiple **GitHub Copilot** accounts in **OpenCode**. This plugin only adds account switching and quota checks it **uses the official `github-copilot` provider** and does **not** require model reconfiguration.
15
+ Manage and switch between multiple **GitHub Copilot** accounts in **OpenCode**. This plugin adds account switching, quota checks, and an optional **Guided Loop Safety** mode that can help Copilot keep a single premium request working longer with fewer report interruptions before it truly needs user input. It **uses the official `github-copilot` provider** and does **not** require model reconfiguration.
16
16
 
17
17
  ## What You Get
18
18
 
19
19
  - **Multi-account support** — add multiple Copilot accounts and switch anytime
20
20
  - **Quota check** — view remaining quota per account
21
21
  - **Auth import** — import Copilot tokens from OpenCode auth storage
22
+ - **Guided Loop Safety** — a stricter Copilot-only question-first policy designed to keep non-blocked work moving, require `question` for user-facing reports when available, and help cut avoidable quota burn caused by repeated status interruptions
22
23
  - **Zero model config** — no model changes required (official provider only)
23
24
 
24
25
  ---
@@ -52,7 +53,7 @@ Install the opencode-copilot-account-switcher plugin by following: https://raw.g
52
53
  3. **Login** to GitHub Copilot:
53
54
 
54
55
  ```bash
55
- opencode auth login github-copilot
56
+ opencode auth login --provider github-copilot
56
57
  ```
57
58
 
58
59
  </details>
@@ -80,7 +81,7 @@ Install the opencode-copilot-account-switcher plugin by following: https://raw.g
80
81
  ### Verification
81
82
 
82
83
  ```bash
83
- opencode auth login github-copilot
84
+ opencode auth login --provider github-copilot
84
85
  ```
85
86
 
86
87
  </details>
@@ -92,7 +93,7 @@ opencode auth login github-copilot
92
93
  Run inside the GitHub Copilot auth flow:
93
94
 
94
95
  ```bash
95
- opencode auth login github-copilot
96
+ opencode auth login --provider github-copilot
96
97
  ```
97
98
 
98
99
  You will see an interactive menu (arrow keys + enter) with actions:
@@ -100,10 +101,13 @@ You will see an interactive menu (arrow keys + enter) with actions:
100
101
  - **Add account**
101
102
  - **Import from auth.json**
102
103
  - **Check quotas**
104
+ - **Guided Loop Safety** — prompt-guided question-first reporting that requires `question` for user-facing reports when available, keeps non-blocked work moving, and avoids unnecessary subagent calls
103
105
  - **Switch account**
104
106
  - **Remove account**
105
107
  - **Remove all**
106
108
 
109
+ If you want GitHub Copilot sessions to stay in a single premium request longer, enable Guided Loop Safety from the account menu. It is a prompt-guided, Copilot-only question-first mode: when `question` is available and permitted, user-facing reports must go through it; if safe non-blocked work remains, Copilot should keep going instead of pausing early; only when no safe action remains should it use `question` to ask for the next task or clarification, while also reducing unnecessary subagent calls.
110
+
107
111
  ---
108
112
 
109
113
  ## Storage
@@ -130,13 +134,14 @@ No. It uses the official provider and only adds account switching + quota checks
130
134
 
131
135
  ## 中文
132
136
 
133
- 在 **OpenCode** 中管理并切换多个 **GitHub Copilot** 账号。本插件只提供**账号切换与配额查询**,**完全依赖官方 `github-copilot` provider**,无需修改模型配置。
137
+ 在 **OpenCode** 中管理并切换多个 **GitHub Copilot** 账号。本插件提供**账号切换、配额查询**以及可选的 **Guided Loop Safety** 模式,帮助 Copilot 在一次 premium request 里更持续地工作,并尽量减少真正需要你输入之前的汇报打断。**完全依赖官方 `github-copilot` provider**,无需修改模型配置。
134
138
 
135
139
  ## 功能一览
136
140
 
137
141
  - **多账号管理** — 添加多个 Copilot 账号,随时切换
138
142
  - **配额查询** — 查看每个账号的剩余额度
139
143
  - **导入认证** — 可从 OpenCode 认证存储导入
144
+ - **Guided Loop Safety** — 仅对 Copilot 生效的更严格 question-first 提示词策略,推动非阻塞工作持续执行、在可用时要求用户可见汇报走 `question`,并帮助降低因反复中断带来的无谓配额消耗
140
145
  - **无需模型配置** — 使用官方 provider,无需改模型
141
146
 
142
147
  ---
@@ -170,7 +175,7 @@ No. It uses the official provider and only adds account switching + quota checks
170
175
  3. **登录 GitHub Copilot**:
171
176
 
172
177
  ```bash
173
- opencode auth login github-copilot
178
+ opencode auth login --provider github-copilot
174
179
  ```
175
180
 
176
181
  </details>
@@ -198,7 +203,7 @@ No. It uses the official provider and only adds account switching + quota checks
198
203
  ### 验证
199
204
 
200
205
  ```bash
201
- opencode auth login github-copilot
206
+ opencode auth login --provider github-copilot
202
207
  ```
203
208
 
204
209
  </details>
@@ -210,7 +215,7 @@ opencode auth login github-copilot
210
215
  在 Copilot 认证流程中运行:
211
216
 
212
217
  ```bash
213
- opencode auth login github-copilot
218
+ opencode auth login --provider github-copilot
214
219
  ```
215
220
 
216
221
  会出现交互式菜单(方向键 + 回车):
@@ -218,10 +223,13 @@ opencode auth login github-copilot
218
223
  - **添加账号**
219
224
  - **从 auth.json 导入**
220
225
  - **检查配额**
226
+ - **Guided Loop Safety 开关** — 通过提示词引导模型在可用时必须使用 `question` 做用户可见汇报、继续完成非阻塞工作,并避免不必要的子代理调用
221
227
  - **切换账号**
222
228
  - **删除账号**
223
229
  - **全部删除**
224
230
 
231
+ 如果你希望 GitHub Copilot 会话在一次 premium request 中尽量持续工作、更少被汇报打断,可以在账号菜单中开启 Guided Loop Safety。它是仅对 Copilot 生效的 prompt 引导式 question-first 模式:当 `question` 工具在当前会话中可用且被允许时,用户可见汇报必须通过它完成;只要还有安全的非阻塞工作可做,Copilot 就应继续执行而不是提前暂停;只有在当前确实没有可安全执行的动作时,才应通过 `question` 询问下一项任务或所需澄清,同时也会减少不必要的子代理调用。
232
+
225
233
  ---
226
234
 
227
235
  ## 存储位置
package/dist/index.d.ts CHANGED
@@ -1 +1,3 @@
1
- export { CopilotAccountSwitcher } from "./plugin";
1
+ export { CopilotAccountSwitcher } from "./plugin.js";
2
+ export { applyMenuAction } from "./plugin-actions.js";
3
+ export { buildPluginHooks } from "./plugin-hooks.js";
package/dist/index.js CHANGED
@@ -1 +1,3 @@
1
- export { CopilotAccountSwitcher } from "./plugin";
1
+ export { CopilotAccountSwitcher } from "./plugin.js";
2
+ export { applyMenuAction } from "./plugin-actions.js";
3
+ export { buildPluginHooks } from "./plugin-hooks.js";
@@ -0,0 +1,21 @@
1
+ import type { Hooks } from "@opencode-ai/plugin";
2
+ import { type StoreFile } from "./store.js";
3
+ export declare const LOOP_SAFETY_POLICY = "Guided Loop Safety Policy\n- Continue working on any remaining non-blocked task before stopping to report or wait for more instructions.\n- If you are not fully blocked, do not stop just because you feel ready to pause; finish the work that can still be done safely.\n- When the question tool is available and permitted in the current session, all user-facing reports must be delivered through the question tool.\n- The question tool is considered available and permitted when it appears in the active tool list and the current session has not denied its use.\n- Direct assistant text is allowed only when the question tool is unavailable, denied, or absent from the current session.\n- When reporting multiple related items, prefer a single question tool call with multiple well-grouped questions instead of multiple separate interruptions.\n- Group related items into clear question batches such as current progress, key findings, and next-step choices.\n- For long or complex reports, split the report into paginated or sequential question batches instead of overloading one large message.\n- Present the highest-priority information first and defer secondary details to later question batches when needed.\n- Even when no explicit decision is required, prefer brief question-tool status updates over direct assistant text whenever the tool is available.\n- Avoid unnecessary question frequency; combine small related updates when a single question call can cover them clearly.\n- When no further action can be taken safely and no non-blocked work remains, use the question tool to ask for the next task or clarification instead of ending with direct assistant text.\n- Dispatching task or subagent work is expensive and should be avoided unless it materially improves the result.\n- Materially improves the result means clearly beneficial cases such as parallel analysis of independent areas; it does not include routine local searches, small file reads, or straightforward edits.\n- If task or subagent delegation is used, keep the number minimal and explain the reason briefly through the question tool when available.";
4
+ export type ExperimentalChatSystemTransformHook = (input: {
5
+ sessionID: string;
6
+ model: {
7
+ providerID: string;
8
+ };
9
+ }, output: {
10
+ system: string[];
11
+ }) => Promise<void>;
12
+ export type CopilotPluginHooks = Hooks & {
13
+ "experimental.chat.system.transform"?: ExperimentalChatSystemTransformHook;
14
+ };
15
+ export declare function isCopilotProvider(providerID: string): boolean;
16
+ export declare function applyLoopSafetyPolicy(input: {
17
+ providerID: string;
18
+ enabled: boolean;
19
+ system: string[];
20
+ }): string[];
21
+ export declare function createLoopSafetySystemTransform(loadStore?: () => Promise<StoreFile | undefined>): ExperimentalChatSystemTransformHook;
@@ -0,0 +1,42 @@
1
+ import { readStoreSafe } from "./store.js";
2
+ export const LOOP_SAFETY_POLICY = `Guided Loop Safety Policy
3
+ - Continue working on any remaining non-blocked task before stopping to report or wait for more instructions.
4
+ - If you are not fully blocked, do not stop just because you feel ready to pause; finish the work that can still be done safely.
5
+ - When the question tool is available and permitted in the current session, all user-facing reports must be delivered through the question tool.
6
+ - The question tool is considered available and permitted when it appears in the active tool list and the current session has not denied its use.
7
+ - Direct assistant text is allowed only when the question tool is unavailable, denied, or absent from the current session.
8
+ - When reporting multiple related items, prefer a single question tool call with multiple well-grouped questions instead of multiple separate interruptions.
9
+ - Group related items into clear question batches such as current progress, key findings, and next-step choices.
10
+ - For long or complex reports, split the report into paginated or sequential question batches instead of overloading one large message.
11
+ - Present the highest-priority information first and defer secondary details to later question batches when needed.
12
+ - Even when no explicit decision is required, prefer brief question-tool status updates over direct assistant text whenever the tool is available.
13
+ - Avoid unnecessary question frequency; combine small related updates when a single question call can cover them clearly.
14
+ - When no further action can be taken safely and no non-blocked work remains, use the question tool to ask for the next task or clarification instead of ending with direct assistant text.
15
+ - Dispatching task or subagent work is expensive and should be avoided unless it materially improves the result.
16
+ - Materially improves the result means clearly beneficial cases such as parallel analysis of independent areas; it does not include routine local searches, small file reads, or straightforward edits.
17
+ - If task or subagent delegation is used, keep the number minimal and explain the reason briefly through the question tool when available.`;
18
+ export function isCopilotProvider(providerID) {
19
+ return providerID === "github-copilot" || providerID === "github-copilot-enterprise";
20
+ }
21
+ export function applyLoopSafetyPolicy(input) {
22
+ if (!input.enabled)
23
+ return input.system;
24
+ if (!isCopilotProvider(input.providerID))
25
+ return input.system;
26
+ if (input.system.includes(LOOP_SAFETY_POLICY))
27
+ return input.system;
28
+ return [...input.system, LOOP_SAFETY_POLICY];
29
+ }
30
+ export function createLoopSafetySystemTransform(loadStore = readStoreSafe) {
31
+ return async (input, output) => {
32
+ const store = await loadStore().catch(() => undefined);
33
+ const next = applyLoopSafetyPolicy({
34
+ providerID: input.model.providerID,
35
+ enabled: store?.loopSafetyEnabled === true,
36
+ system: output.system,
37
+ });
38
+ if (next.length === output.system.length)
39
+ return;
40
+ output.system.push(LOOP_SAFETY_POLICY);
41
+ };
42
+ }
@@ -0,0 +1,7 @@
1
+ import type { StoreFile } from "./store.js";
2
+ import type { MenuAction } from "./ui/menu.js";
3
+ export declare function applyMenuAction(input: {
4
+ action: MenuAction;
5
+ store: StoreFile;
6
+ writeStore: (store: StoreFile) => Promise<void>;
7
+ }): Promise<boolean>;
@@ -0,0 +1,7 @@
1
+ export async function applyMenuAction(input) {
2
+ if (input.action.type !== "toggle-loop-safety")
3
+ return false;
4
+ input.store.loopSafetyEnabled = input.store.loopSafetyEnabled !== true;
5
+ await input.writeStore(input.store);
6
+ return true;
7
+ }
@@ -0,0 +1,6 @@
1
+ import { type CopilotPluginHooks } from "./loop-safety-plugin.js";
2
+ import { type StoreFile } from "./store.js";
3
+ export declare function buildPluginHooks(input: {
4
+ auth: CopilotPluginHooks["auth"];
5
+ loadStore?: () => Promise<StoreFile | undefined>;
6
+ }): CopilotPluginHooks;
@@ -0,0 +1,8 @@
1
+ import { createLoopSafetySystemTransform } from "./loop-safety-plugin.js";
2
+ import { readStoreSafe } from "./store.js";
3
+ export function buildPluginHooks(input) {
4
+ return {
5
+ auth: input.auth,
6
+ "experimental.chat.system.transform": createLoopSafetySystemTransform(input.loadStore ?? readStoreSafe),
7
+ };
8
+ }
package/dist/plugin.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { createInterface } from "node:readline/promises";
2
2
  import { stdin as input, stdout as output } from "node:process";
3
- import { isTTY } from "./ui/ansi";
4
- import { showAccountActions, showMenu } from "./ui/menu";
5
- import { authPath, readAuth, readStore, writeStore } from "./store";
3
+ import { applyMenuAction } from "./plugin-actions.js";
4
+ import { buildPluginHooks } from "./plugin-hooks.js";
5
+ import { isTTY } from "./ui/ansi.js";
6
+ import { showAccountActions, showMenu } from "./ui/menu.js";
7
+ import { authPath, readAuth, readStore, writeStore } from "./store.js";
6
8
  function now() {
7
9
  return Date.now();
8
10
  }
@@ -442,6 +444,32 @@ async function switchAccount(client, entry) {
442
444
  });
443
445
  }
444
446
  export const CopilotAccountSwitcher = async ({ client }) => {
447
+ const methods = [
448
+ {
449
+ type: "oauth",
450
+ label: "Manage GitHub Copilot accounts",
451
+ async authorize() {
452
+ const entry = await runMenu();
453
+ return {
454
+ url: "",
455
+ instructions: "",
456
+ method: "auto",
457
+ async callback() {
458
+ if (!entry)
459
+ return { type: "failed" };
460
+ return {
461
+ type: "success",
462
+ provider: entry.enterpriseUrl ? "github-copilot-enterprise" : "github-copilot",
463
+ refresh: entry.refresh,
464
+ access: entry.access,
465
+ expires: entry.expires,
466
+ ...(entry.enterpriseUrl ? { enterpriseUrl: entry.enterpriseUrl } : {}),
467
+ };
468
+ },
469
+ };
470
+ },
471
+ },
472
+ ];
445
473
  async function runMenu() {
446
474
  const store = await readStore();
447
475
  const auth = await readAuth().catch(() => ({}));
@@ -533,11 +561,14 @@ export const CopilotAccountSwitcher = async ({ client }) => {
533
561
  : undefined,
534
562
  }));
535
563
  const refresh = { enabled: store.autoRefresh === true, minutes: store.refreshMinutes ?? 15 };
536
- const action = await showMenu(accounts, refresh, store.lastQuotaRefresh);
564
+ const action = await showMenu(accounts, refresh, store.lastQuotaRefresh, store.loopSafetyEnabled === true);
537
565
  if (action.type === "cancel") {
538
566
  const active = store.active ? store.accounts[store.active] : undefined;
539
567
  return active;
540
568
  }
569
+ if (await applyMenuAction({ action, store, writeStore })) {
570
+ continue;
571
+ }
541
572
  if (action.type === "add") {
542
573
  const mode = await promptText("Add via device login? (y/n): ");
543
574
  const useDevice = mode.toLowerCase() === "y" || mode.toLowerCase() === "yes";
@@ -673,35 +704,10 @@ export const CopilotAccountSwitcher = async ({ client }) => {
673
704
  }
674
705
  }
675
706
  }
676
- return {
707
+ return buildPluginHooks({
677
708
  auth: {
678
709
  provider: "github-copilot",
679
- methods: [
680
- {
681
- type: "oauth",
682
- label: "Manage GitHub Copilot accounts",
683
- async authorize() {
684
- const entry = await runMenu();
685
- return {
686
- url: "",
687
- instructions: "",
688
- method: "auto",
689
- async callback() {
690
- if (!entry)
691
- return { type: "failed" };
692
- return {
693
- type: "success",
694
- provider: entry.enterpriseUrl ? "github-copilot-enterprise" : "github-copilot",
695
- refresh: entry.refresh,
696
- access: entry.access,
697
- expires: entry.expires,
698
- ...(entry.enterpriseUrl ? { enterpriseUrl: entry.enterpriseUrl } : {}),
699
- };
700
- },
701
- };
702
- },
703
- },
704
- ],
710
+ methods,
705
711
  },
706
- };
712
+ });
707
713
  };
package/dist/store.d.ts CHANGED
@@ -54,9 +54,12 @@ export type StoreFile = {
54
54
  autoRefresh?: boolean;
55
55
  refreshMinutes?: number;
56
56
  lastQuotaRefresh?: number;
57
+ loopSafetyEnabled?: boolean;
57
58
  };
58
59
  export declare function storePath(): string;
59
60
  export declare function authPath(): string;
60
- export declare function readStore(): Promise<StoreFile>;
61
+ export declare function parseStore(raw: string): StoreFile;
62
+ export declare function readStore(filePath?: string): Promise<StoreFile>;
63
+ export declare function readStoreSafe(filePath?: string): Promise<StoreFile | undefined>;
61
64
  export declare function readAuth(filePath?: string): Promise<Record<string, AccountEntry>>;
62
65
  export declare function writeStore(store: StoreFile): Promise<void>;
package/dist/store.js CHANGED
@@ -12,12 +12,12 @@ export function authPath() {
12
12
  const dataDir = xdgData ?? path.join(os.homedir(), ".local", "share");
13
13
  return path.join(dataDir, "opencode", authFile);
14
14
  }
15
- export async function readStore() {
16
- const file = storePath();
17
- const raw = await fs.readFile(file, "utf-8").catch(() => "");
15
+ export function parseStore(raw) {
18
16
  const data = raw ? JSON.parse(raw) : { accounts: {} };
19
17
  if (!data.accounts)
20
18
  data.accounts = {};
19
+ if (data.loopSafetyEnabled !== true)
20
+ data.loopSafetyEnabled = false;
21
21
  for (const [name, entry] of Object.entries(data.accounts)) {
22
22
  const info = entry;
23
23
  if (!info.name)
@@ -25,6 +25,26 @@ export async function readStore() {
25
25
  }
26
26
  return data;
27
27
  }
28
+ export async function readStore(filePath = storePath()) {
29
+ const raw = await fs.readFile(filePath, "utf-8").catch((error) => {
30
+ if (error.code === "ENOENT")
31
+ return "";
32
+ throw error;
33
+ });
34
+ return parseStore(raw);
35
+ }
36
+ export async function readStoreSafe(filePath = storePath()) {
37
+ try {
38
+ const raw = await fs.readFile(filePath, "utf-8");
39
+ return parseStore(raw);
40
+ }
41
+ catch (error) {
42
+ const issue = error;
43
+ if (issue.code === "ENOENT")
44
+ return parseStore("");
45
+ return undefined;
46
+ }
47
+ }
28
48
  export async function readAuth(filePath) {
29
49
  const dataFile = path.join(xdgData ?? path.join(os.homedir(), ".local", "share"), "opencode", authFile);
30
50
  const configFile = path.join(xdgConfig ?? path.join(os.homedir(), ".config"), "opencode", authFile);
@@ -1,4 +1,4 @@
1
- import { select } from "./select";
1
+ import { select } from "./select.js";
2
2
  export async function confirm(message, defaultYes = false) {
3
3
  const items = defaultYes
4
4
  ? [
package/dist/ui/menu.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type MenuItem } from "./select.js";
1
2
  export type AccountStatus = "active" | "expired" | "unknown";
2
3
  export interface AccountInfo {
3
4
  name: string;
@@ -52,6 +53,8 @@ export type MenuAction = {
52
53
  type: "toggle-refresh";
53
54
  } | {
54
55
  type: "set-interval";
56
+ } | {
57
+ type: "toggle-loop-safety";
55
58
  } | {
56
59
  type: "switch";
57
60
  account: AccountInfo;
@@ -63,8 +66,17 @@ export type MenuAction = {
63
66
  } | {
64
67
  type: "cancel";
65
68
  };
69
+ export declare function buildMenuItems(input: {
70
+ accounts: AccountInfo[];
71
+ refresh?: {
72
+ enabled: boolean;
73
+ minutes: number;
74
+ };
75
+ lastQuotaRefresh?: number;
76
+ loopSafetyEnabled: boolean;
77
+ }): MenuItem<MenuAction>[];
66
78
  export declare function showMenu(accounts: AccountInfo[], refresh?: {
67
79
  enabled: boolean;
68
80
  minutes: number;
69
- }, lastQuotaRefresh?: number): Promise<MenuAction>;
81
+ }, lastQuotaRefresh?: number, loopSafetyEnabled?: boolean): Promise<MenuAction>;
70
82
  export declare function showAccountActions(account: AccountInfo): Promise<"switch" | "remove" | "back">;
package/dist/ui/menu.js CHANGED
@@ -1,6 +1,6 @@
1
- import { ANSI } from "./ansi";
2
- import { select } from "./select";
3
- import { confirm } from "./confirm";
1
+ import { ANSI } from "./ansi.js";
2
+ import { select } from "./select.js";
3
+ import { confirm } from "./confirm.js";
4
4
  function formatRelativeTime(timestamp) {
5
5
  if (!timestamp)
6
6
  return "never";
@@ -25,9 +25,9 @@ function getStatusBadge(status) {
25
25
  return `${ANSI.red}[expired]${ANSI.reset}`;
26
26
  return "";
27
27
  }
28
- export async function showMenu(accounts, refresh, lastQuotaRefresh) {
29
- const quotaHint = lastQuotaRefresh ? `last ${formatRelativeTime(lastQuotaRefresh)}` : undefined;
30
- const items = [
28
+ export function buildMenuItems(input) {
29
+ const quotaHint = input.lastQuotaRefresh ? `last ${formatRelativeTime(input.lastQuotaRefresh)}` : undefined;
30
+ return [
31
31
  { label: "Actions", value: { type: "cancel" }, kind: "heading" },
32
32
  { label: "Add account", value: { type: "add" }, color: "cyan", hint: "device login or manual" },
33
33
  { label: "Import from auth.json", value: { type: "import" }, color: "cyan" },
@@ -35,15 +35,21 @@ export async function showMenu(accounts, refresh, lastQuotaRefresh) {
35
35
  { label: "Refresh identity", value: { type: "refresh-identity" }, color: "cyan" },
36
36
  { label: "Check models", value: { type: "check-models" }, color: "cyan" },
37
37
  {
38
- label: refresh?.enabled ? "Disable auto refresh" : "Enable auto refresh",
38
+ label: input.refresh?.enabled ? "Disable auto refresh" : "Enable auto refresh",
39
39
  value: { type: "toggle-refresh" },
40
40
  color: "cyan",
41
- hint: refresh ? `${refresh.minutes}m` : undefined,
41
+ hint: input.refresh ? `${input.refresh.minutes}m` : undefined,
42
42
  },
43
43
  { label: "Set refresh interval", value: { type: "set-interval" }, color: "cyan" },
44
+ {
45
+ label: input.loopSafetyEnabled ? "Disable guided loop safety" : "Enable guided loop safety",
46
+ value: { type: "toggle-loop-safety" },
47
+ color: "cyan",
48
+ hint: "Prompt-guided: fewer report interruptions, fewer unnecessary subagents",
49
+ },
44
50
  { label: "", value: { type: "cancel" }, separator: true },
45
51
  { label: "Accounts", value: { type: "cancel" }, kind: "heading" },
46
- ...accounts.map((account) => {
52
+ ...input.accounts.map((account) => {
47
53
  const statusBadge = getStatusBadge(account.status);
48
54
  const currentBadge = account.isCurrent ? ` ${ANSI.cyan}*${ANSI.reset}` : "";
49
55
  const format = (s) => s?.unlimited ? "∞" : s?.remaining !== undefined && s?.entitlement !== undefined ? `${s.remaining}/${s.entitlement}` : "?";
@@ -69,6 +75,14 @@ export async function showMenu(accounts, refresh, lastQuotaRefresh) {
69
75
  { label: "Danger zone", value: { type: "cancel" }, kind: "heading" },
70
76
  { label: "Remove all accounts", value: { type: "remove-all" }, color: "red" },
71
77
  ];
78
+ }
79
+ export async function showMenu(accounts, refresh, lastQuotaRefresh, loopSafetyEnabled = false) {
80
+ const items = buildMenuItems({
81
+ accounts,
82
+ refresh,
83
+ lastQuotaRefresh,
84
+ loopSafetyEnabled,
85
+ });
72
86
  while (true) {
73
87
  const result = await select(items, {
74
88
  message: "GitHub Copilot accounts",
package/dist/ui/select.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ANSI, isTTY, parseKey } from "./ansi";
1
+ import { ANSI, isTTY, parseKey } from "./ansi.js";
2
2
  const ESCAPE_TIMEOUT_MS = 50;
3
3
  const ANSI_REGEX = new RegExp("\\x1b\\[[0-9;]*m", "g");
4
4
  const ANSI_LEADING_REGEX = new RegExp("^\\x1b\\[[0-9;]*m");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-copilot-account-switcher",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "GitHub Copilot account switcher plugin for OpenCode",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -32,6 +32,7 @@
32
32
  ],
33
33
  "scripts": {
34
34
  "build": "tsc -p tsconfig.build.json",
35
+ "test": "npm run build && node --test",
35
36
  "typecheck": "tsc --noEmit",
36
37
  "prepublishOnly": "npm run build"
37
38
  },