opencode-copilot-account-switcher 0.1.2 → 0.1.3
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 +16 -8
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/loop-safety-plugin.d.ts +21 -0
- package/dist/loop-safety-plugin.js +39 -0
- package/dist/plugin-actions.d.ts +7 -0
- package/dist/plugin-actions.js +7 -0
- package/dist/plugin-hooks.d.ts +6 -0
- package/dist/plugin-hooks.js +8 -0
- package/dist/plugin.js +38 -32
- package/dist/store.d.ts +4 -1
- package/dist/store.js +23 -3
- package/dist/ui/confirm.js +1 -1
- package/dist/ui/menu.d.ts +13 -1
- package/dist/ui/menu.js +23 -9
- package/dist/ui/select.js +1 -1
- package/package.json +2 -1
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
|
|
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 — 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** — inject a stricter question-first Copilot policy with fewer report interruptions and fewer unnecessary subagent calls
|
|
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, reduces report interruptions, and avoids unnecessary subagent calls
|
|
103
105
|
- **Switch account**
|
|
104
106
|
- **Remove account**
|
|
105
107
|
- **Remove all**
|
|
106
108
|
|
|
109
|
+
If you want stricter question-first reporting and fewer unnecessary subagent calls in GitHub Copilot sessions, enable Guided Loop Safety from the account menu.
|
|
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**
|
|
137
|
+
在 **OpenCode** 中管理并切换多个 **GitHub Copilot** 账号。本插件提供**账号切换、配额查询**以及可选的 **Guided Loop Safety** 模式,**完全依赖官方 `github-copilot` provider**,无需修改模型配置。
|
|
134
138
|
|
|
135
139
|
## 功能一览
|
|
136
140
|
|
|
137
141
|
- **多账号管理** — 添加多个 Copilot 账号,随时切换
|
|
138
142
|
- **配额查询** — 查看每个账号的剩余额度
|
|
139
143
|
- **导入认证** — 可从 OpenCode 认证存储导入
|
|
144
|
+
- **Guided Loop Safety** — 为 Copilot 注入更严格的 question-first 提示词规则,减少汇报打断,并避免不必要的子代理调用
|
|
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 会话更严格地优先使用 `question` 工具汇报、减少汇报打断,并避免不必要的子代理调用,可以在账号菜单中开启 Guided Loop Safety。
|
|
232
|
+
|
|
225
233
|
---
|
|
226
234
|
|
|
227
235
|
## 存储位置
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -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- 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- 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,39 @@
|
|
|
1
|
+
import { readStoreSafe } from "./store.js";
|
|
2
|
+
export const LOOP_SAFETY_POLICY = `Guided Loop Safety Policy
|
|
3
|
+
- When the question tool is available and permitted in the current session, all user-facing reports must be delivered through the question tool.
|
|
4
|
+
- 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.
|
|
5
|
+
- Direct assistant text is allowed only when the question tool is unavailable, denied, or absent from the current session.
|
|
6
|
+
- When reporting multiple related items, prefer a single question tool call with multiple well-grouped questions instead of multiple separate interruptions.
|
|
7
|
+
- Group related items into clear question batches such as current progress, key findings, and next-step choices.
|
|
8
|
+
- For long or complex reports, split the report into paginated or sequential question batches instead of overloading one large message.
|
|
9
|
+
- Present the highest-priority information first and defer secondary details to later question batches when needed.
|
|
10
|
+
- Even when no explicit decision is required, prefer brief question-tool status updates over direct assistant text whenever the tool is available.
|
|
11
|
+
- Avoid unnecessary question frequency; combine small related updates when a single question call can cover them clearly.
|
|
12
|
+
- Dispatching task or subagent work is expensive and should be avoided unless it materially improves the result.
|
|
13
|
+
- 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.
|
|
14
|
+
- If task or subagent delegation is used, keep the number minimal and explain the reason briefly through the question tool when available.`;
|
|
15
|
+
export function isCopilotProvider(providerID) {
|
|
16
|
+
return providerID === "github-copilot" || providerID === "github-copilot-enterprise";
|
|
17
|
+
}
|
|
18
|
+
export function applyLoopSafetyPolicy(input) {
|
|
19
|
+
if (!input.enabled)
|
|
20
|
+
return input.system;
|
|
21
|
+
if (!isCopilotProvider(input.providerID))
|
|
22
|
+
return input.system;
|
|
23
|
+
if (input.system.includes(LOOP_SAFETY_POLICY))
|
|
24
|
+
return input.system;
|
|
25
|
+
return [...input.system, LOOP_SAFETY_POLICY];
|
|
26
|
+
}
|
|
27
|
+
export function createLoopSafetySystemTransform(loadStore = readStoreSafe) {
|
|
28
|
+
return async (input, output) => {
|
|
29
|
+
const store = await loadStore().catch(() => undefined);
|
|
30
|
+
const next = applyLoopSafetyPolicy({
|
|
31
|
+
providerID: input.model.providerID,
|
|
32
|
+
enabled: store?.loopSafetyEnabled === true,
|
|
33
|
+
system: output.system,
|
|
34
|
+
});
|
|
35
|
+
if (next.length === output.system.length)
|
|
36
|
+
return;
|
|
37
|
+
output.system.push(LOOP_SAFETY_POLICY);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -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 {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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
|
|
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
|
|
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);
|
package/dist/ui/confirm.js
CHANGED
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
|
|
29
|
-
const quotaHint = lastQuotaRefresh ? `last ${formatRelativeTime(lastQuotaRefresh)}` : undefined;
|
|
30
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-copilot-account-switcher",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
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
|
},
|