opencode-dux 1.0.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.
- package/LICENSE +21 -0
- package/README.md +452 -0
- package/dist/agents/descriptions.d.ts +6 -0
- package/dist/agents/designer.d.ts +2 -0
- package/dist/agents/explorer.d.ts +2 -0
- package/dist/agents/fixer.d.ts +2 -0
- package/dist/agents/index.d.ts +22 -0
- package/dist/agents/interpreter.d.ts +2 -0
- package/dist/agents/librarian.d.ts +2 -0
- package/dist/agents/oracle.d.ts +2 -0
- package/dist/agents/orchestrator.d.ts +27 -0
- package/dist/agents/overrides.d.ts +18 -0
- package/dist/agents/prompt-blocks.d.ts +97 -0
- package/dist/agents/steward.d.ts +3 -0
- package/dist/cli/config-io.d.ts +24 -0
- package/dist/cli/config-manager.d.ts +4 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +1006 -0
- package/dist/cli/install.d.ts +2 -0
- package/dist/cli/mcps.d.ts +13 -0
- package/dist/cli/model-key-normalization.d.ts +1 -0
- package/dist/cli/paths.d.ts +35 -0
- package/dist/cli/providers.d.ts +137 -0
- package/dist/cli/skills.d.ts +22 -0
- package/dist/cli/system.d.ts +5 -0
- package/dist/cli/types.d.ts +38 -0
- package/dist/config/constants.d.ts +12 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/loader.d.ts +40 -0
- package/dist/config/runtime-preset.d.ts +12 -0
- package/dist/config/schema.d.ts +281 -0
- package/dist/config/utils.d.ts +10 -0
- package/dist/discovery/local/types.d.ts +79 -0
- package/dist/discovery/local.d.ts +73 -0
- package/dist/discovery/mcp-servers.d.ts +88 -0
- package/dist/discovery/skills.d.ts +94 -0
- package/dist/hooks/apply-patch/codec.d.ts +7 -0
- package/dist/hooks/apply-patch/errors.d.ts +25 -0
- package/dist/hooks/apply-patch/execution-context.d.ts +27 -0
- package/dist/hooks/apply-patch/index.d.ts +15 -0
- package/dist/hooks/apply-patch/matching.d.ts +26 -0
- package/dist/hooks/apply-patch/operations.d.ts +3 -0
- package/dist/hooks/apply-patch/patch.d.ts +2 -0
- package/dist/hooks/apply-patch/prepared-changes.d.ts +17 -0
- package/dist/hooks/apply-patch/resolution.d.ts +19 -0
- package/dist/hooks/apply-patch/rewrite.d.ts +7 -0
- package/dist/hooks/apply-patch/test-helpers.d.ts +6 -0
- package/dist/hooks/apply-patch/types.d.ts +80 -0
- package/dist/hooks/auto-update-checker/cache.d.ts +11 -0
- package/dist/hooks/auto-update-checker/checker.d.ts +32 -0
- package/dist/hooks/auto-update-checker/constants.d.ts +11 -0
- package/dist/hooks/auto-update-checker/index.d.ts +18 -0
- package/dist/hooks/auto-update-checker/types.d.ts +22 -0
- package/dist/hooks/chat-headers.d.ts +16 -0
- package/dist/hooks/context-pressure-reminder/index.d.ts +33 -0
- package/dist/hooks/delegate-task-retry/guidance.d.ts +2 -0
- package/dist/hooks/delegate-task-retry/hook.d.ts +8 -0
- package/dist/hooks/delegate-task-retry/index.d.ts +4 -0
- package/dist/hooks/delegate-task-retry/patterns.d.ts +11 -0
- package/dist/hooks/filter-available-skills/index.d.ts +32 -0
- package/dist/hooks/foreground-fallback/index.d.ts +72 -0
- package/dist/hooks/image-hook.d.ts +5 -0
- package/dist/hooks/index.d.ts +14 -0
- package/dist/hooks/json-error-recovery/hook.d.ts +18 -0
- package/dist/hooks/json-error-recovery/index.d.ts +1 -0
- package/dist/hooks/phase-reminder/index.d.ts +26 -0
- package/dist/hooks/post-file-tool-nudge/index.d.ts +19 -0
- package/dist/hooks/task-session-manager/index.d.ts +52 -0
- package/dist/hooks/todo-continuation/index.d.ts +53 -0
- package/dist/hooks/todo-continuation/todo-hygiene.d.ts +35 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +31782 -0
- package/dist/mcp/context7.d.ts +6 -0
- package/dist/mcp/grep-app.d.ts +6 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/types.d.ts +12 -0
- package/dist/mcp/websearch.d.ts +9 -0
- package/dist/skills/registry.d.ts +29 -0
- package/dist/subscriptions/accounts-store.d.ts +57 -0
- package/dist/subscriptions/index.d.ts +13 -0
- package/dist/subscriptions/neuralwatt-scraper.d.ts +14 -0
- package/dist/subscriptions/opencode-go-scraper.d.ts +27 -0
- package/dist/subscriptions/types.d.ts +115 -0
- package/dist/subscriptions/usage-service.d.ts +74 -0
- package/dist/tools/ast-grep/cli.d.ts +15 -0
- package/dist/tools/ast-grep/constants.d.ts +25 -0
- package/dist/tools/ast-grep/downloader.d.ts +5 -0
- package/dist/tools/ast-grep/index.d.ts +10 -0
- package/dist/tools/ast-grep/tools.d.ts +3 -0
- package/dist/tools/ast-grep/types.d.ts +30 -0
- package/dist/tools/ast-grep/utils.d.ts +4 -0
- package/dist/tools/delegate.d.ts +14 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/preset-manager.d.ts +27 -0
- package/dist/tools/smartfetch/binary.d.ts +3 -0
- package/dist/tools/smartfetch/cache.d.ts +6 -0
- package/dist/tools/smartfetch/constants.d.ts +12 -0
- package/dist/tools/smartfetch/index.d.ts +3 -0
- package/dist/tools/smartfetch/network.d.ts +38 -0
- package/dist/tools/smartfetch/secondary-model.d.ts +28 -0
- package/dist/tools/smartfetch/tool.d.ts +3 -0
- package/dist/tools/smartfetch/types.d.ts +122 -0
- package/dist/tools/smartfetch/utils.d.ts +18 -0
- package/dist/tui-state.d.ts +168 -0
- package/dist/tui.d.ts +37 -0
- package/dist/tui.js +1896 -0
- package/dist/utils/agent-variant.d.ts +63 -0
- package/dist/utils/compat.d.ts +30 -0
- package/dist/utils/env.d.ts +1 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/internal-initiator.d.ts +6 -0
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/polling.d.ts +21 -0
- package/dist/utils/session-manager.d.ts +55 -0
- package/dist/utils/session.d.ts +90 -0
- package/dist/utils/subagent-depth.d.ts +35 -0
- package/dist/utils/system-collapse.d.ts +6 -0
- package/dist/utils/task.d.ts +4 -0
- package/dist/utils/zip-extractor.d.ts +1 -0
- package/index.ts +1 -0
- package/opencode-dux.schema.json +634 -0
- package/package.json +103 -0
- package/src/agents/descriptions.ts +55 -0
- package/src/agents/designer.test.ts +86 -0
- package/src/agents/designer.ts +154 -0
- package/src/agents/display-name.test.ts +186 -0
- package/src/agents/explorer.test.ts +79 -0
- package/src/agents/explorer.ts +144 -0
- package/src/agents/fixer.test.ts +79 -0
- package/src/agents/fixer.ts +145 -0
- package/src/agents/index.test.ts +472 -0
- package/src/agents/index.ts +248 -0
- package/src/agents/interpreter.ts +136 -0
- package/src/agents/librarian.test.ts +80 -0
- package/src/agents/librarian.ts +145 -0
- package/src/agents/oracle.test.ts +89 -0
- package/src/agents/oracle.ts +184 -0
- package/src/agents/orchestrator.test.ts +116 -0
- package/src/agents/orchestrator.ts +574 -0
- package/src/agents/overrides.ts +95 -0
- package/src/agents/prompt-blocks.test.ts +114 -0
- package/src/agents/prompt-blocks.ts +640 -0
- package/src/agents/steward.ts +146 -0
- package/src/cli/config-io.test.ts +536 -0
- package/src/cli/config-io.ts +473 -0
- package/src/cli/config-manager.test.ts +141 -0
- package/src/cli/config-manager.ts +4 -0
- package/src/cli/index.ts +88 -0
- package/src/cli/install.ts +282 -0
- package/src/cli/mcps.test.ts +62 -0
- package/src/cli/mcps.ts +39 -0
- package/src/cli/model-key-normalization.test.ts +21 -0
- package/src/cli/model-key-normalization.ts +60 -0
- package/src/cli/paths.test.ts +167 -0
- package/src/cli/paths.ts +144 -0
- package/src/cli/providers.test.ts +118 -0
- package/src/cli/providers.ts +141 -0
- package/src/cli/skills.test.ts +111 -0
- package/src/cli/skills.ts +103 -0
- package/src/cli/system.test.ts +91 -0
- package/src/cli/system.ts +180 -0
- package/src/cli/types.ts +43 -0
- package/src/config/constants.ts +58 -0
- package/src/config/index.ts +4 -0
- package/src/config/loader.test.ts +1194 -0
- package/src/config/loader.ts +269 -0
- package/src/config/model-resolution.test.ts +176 -0
- package/src/config/runtime-preset.test.ts +61 -0
- package/src/config/runtime-preset.ts +37 -0
- package/src/config/schema.ts +248 -0
- package/src/config/utils.test.ts +41 -0
- package/src/config/utils.ts +23 -0
- package/src/discovery/local/types.ts +85 -0
- package/src/discovery/local.ts +322 -0
- package/src/discovery/mcp-servers.ts +804 -0
- package/src/discovery/skills.ts +959 -0
- package/src/hooks/apply-patch/codec.test.ts +184 -0
- package/src/hooks/apply-patch/codec.ts +352 -0
- package/src/hooks/apply-patch/errors.ts +117 -0
- package/src/hooks/apply-patch/execution-context.ts +432 -0
- package/src/hooks/apply-patch/hook.test.ts +768 -0
- package/src/hooks/apply-patch/index.ts +126 -0
- package/src/hooks/apply-patch/matching.test.ts +215 -0
- package/src/hooks/apply-patch/matching.ts +586 -0
- package/src/hooks/apply-patch/operations.test.ts +1535 -0
- package/src/hooks/apply-patch/operations.ts +3 -0
- package/src/hooks/apply-patch/patch.ts +9 -0
- package/src/hooks/apply-patch/prepared-changes.ts +400 -0
- package/src/hooks/apply-patch/resolution.test.ts +420 -0
- package/src/hooks/apply-patch/resolution.ts +437 -0
- package/src/hooks/apply-patch/rewrite.ts +496 -0
- package/src/hooks/apply-patch/test-helpers.ts +52 -0
- package/src/hooks/apply-patch/types.ts +111 -0
- package/src/hooks/auto-update-checker/cache.test.ts +179 -0
- package/src/hooks/auto-update-checker/cache.ts +188 -0
- package/src/hooks/auto-update-checker/checker.test.ts +159 -0
- package/src/hooks/auto-update-checker/checker.ts +308 -0
- package/src/hooks/auto-update-checker/constants.ts +33 -0
- package/src/hooks/auto-update-checker/index.test.ts +282 -0
- package/src/hooks/auto-update-checker/index.ts +225 -0
- package/src/hooks/auto-update-checker/types.ts +26 -0
- package/src/hooks/chat-headers.test.ts +236 -0
- package/src/hooks/chat-headers.ts +97 -0
- package/src/hooks/context-pressure-reminder/index.test.ts +179 -0
- package/src/hooks/context-pressure-reminder/index.ts +137 -0
- package/src/hooks/delegate-task-retry/guidance.ts +41 -0
- package/src/hooks/delegate-task-retry/hook.ts +23 -0
- package/src/hooks/delegate-task-retry/index.test.ts +38 -0
- package/src/hooks/delegate-task-retry/index.ts +7 -0
- package/src/hooks/delegate-task-retry/patterns.ts +79 -0
- package/src/hooks/filter-available-skills/index.test.ts +297 -0
- package/src/hooks/filter-available-skills/index.ts +160 -0
- package/src/hooks/foreground-fallback/index.test.ts +624 -0
- package/src/hooks/foreground-fallback/index.ts +374 -0
- package/src/hooks/image-hook.ts +6 -0
- package/src/hooks/index.ts +17 -0
- package/src/hooks/json-error-recovery/hook.ts +73 -0
- package/src/hooks/json-error-recovery/index.test.ts +111 -0
- package/src/hooks/json-error-recovery/index.ts +6 -0
- package/src/hooks/phase-reminder/index.test.ts +74 -0
- package/src/hooks/phase-reminder/index.ts +85 -0
- package/src/hooks/post-file-tool-nudge/index.test.ts +94 -0
- package/src/hooks/post-file-tool-nudge/index.ts +63 -0
- package/src/hooks/task-session-manager/index.test.ts +833 -0
- package/src/hooks/task-session-manager/index.ts +434 -0
- package/src/hooks/todo-continuation/index.test.ts +3026 -0
- package/src/hooks/todo-continuation/index.ts +878 -0
- package/src/hooks/todo-continuation/todo-hygiene.test.ts +204 -0
- package/src/hooks/todo-continuation/todo-hygiene.ts +207 -0
- package/src/index.ts +1672 -0
- package/src/mcp/context7.ts +14 -0
- package/src/mcp/grep-app.ts +11 -0
- package/src/mcp/index.test.ts +96 -0
- package/src/mcp/index.ts +66 -0
- package/src/mcp/types.ts +16 -0
- package/src/mcp/websearch.ts +47 -0
- package/src/skills/codemap/README.md +60 -0
- package/src/skills/codemap/SKILL.md +174 -0
- package/src/skills/codemap/scripts/codemap.mjs +483 -0
- package/src/skills/codemap/scripts/codemap.test.ts +129 -0
- package/src/skills/registry.ts +218 -0
- package/src/skills/simplify/README.md +19 -0
- package/src/skills/simplify/SKILL.md +138 -0
- package/src/subscriptions/accounts-store.test.ts +236 -0
- package/src/subscriptions/accounts-store.ts +184 -0
- package/src/subscriptions/index.ts +30 -0
- package/src/subscriptions/neuralwatt-scraper.ts +108 -0
- package/src/subscriptions/opencode-go-scraper.ts +301 -0
- package/src/subscriptions/types.ts +145 -0
- package/src/subscriptions/usage-service.test.ts +202 -0
- package/src/subscriptions/usage-service.ts +651 -0
- package/src/tools/ast-grep/cli.ts +257 -0
- package/src/tools/ast-grep/constants.ts +214 -0
- package/src/tools/ast-grep/downloader.ts +131 -0
- package/src/tools/ast-grep/index.ts +24 -0
- package/src/tools/ast-grep/tools.ts +117 -0
- package/src/tools/ast-grep/types.ts +51 -0
- package/src/tools/ast-grep/utils.ts +126 -0
- package/src/tools/delegate-handoff.test.ts +18 -0
- package/src/tools/delegate.ts +508 -0
- package/src/tools/index.ts +8 -0
- package/src/tools/preset-manager.test.ts +795 -0
- package/src/tools/preset-manager.ts +332 -0
- package/src/tools/smartfetch/binary.ts +58 -0
- package/src/tools/smartfetch/cache.test.ts +34 -0
- package/src/tools/smartfetch/cache.ts +112 -0
- package/src/tools/smartfetch/constants.ts +29 -0
- package/src/tools/smartfetch/index.ts +8 -0
- package/src/tools/smartfetch/network.test.ts +178 -0
- package/src/tools/smartfetch/network.ts +614 -0
- package/src/tools/smartfetch/secondary-model.test.ts +85 -0
- package/src/tools/smartfetch/secondary-model.ts +276 -0
- package/src/tools/smartfetch/tool.test.ts +60 -0
- package/src/tools/smartfetch/tool.ts +832 -0
- package/src/tools/smartfetch/types.ts +135 -0
- package/src/tools/smartfetch/utils.test.ts +24 -0
- package/src/tools/smartfetch/utils.ts +456 -0
- package/src/tui-state.test.ts +867 -0
- package/src/tui-state.ts +1255 -0
- package/src/tui.test.ts +336 -0
- package/src/tui.ts +1539 -0
- package/src/utils/agent-variant.test.ts +244 -0
- package/src/utils/agent-variant.ts +187 -0
- package/src/utils/compat.ts +91 -0
- package/src/utils/env.ts +12 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/internal-initiator.ts +28 -0
- package/src/utils/logger.test.ts +220 -0
- package/src/utils/logger.ts +136 -0
- package/src/utils/polling.test.ts +191 -0
- package/src/utils/polling.ts +67 -0
- package/src/utils/session-manager.test.ts +173 -0
- package/src/utils/session-manager.ts +356 -0
- package/src/utils/session.test.ts +110 -0
- package/src/utils/session.ts +389 -0
- package/src/utils/subagent-depth.test.ts +170 -0
- package/src/utils/subagent-depth.ts +75 -0
- package/src/utils/system-collapse.test.ts +86 -0
- package/src/utils/system-collapse.ts +24 -0
- package/src/utils/task.test.ts +24 -0
- package/src/utils/task.ts +20 -0
- package/src/utils/zip-extractor.ts +102 -0
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage service for multi-provider subscription tracking.
|
|
3
|
+
*
|
|
4
|
+
* Manages account storage, refresh lifecycle, rate limiting, and the
|
|
5
|
+
* /subscriptions slash command.
|
|
6
|
+
*
|
|
7
|
+
* Accounts are stored locally (not in plugin config) to keep auth tokens
|
|
8
|
+
* out of version control and the published schema.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import * as os from 'node:os';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import type { PluginInput } from '@opencode-ai/plugin';
|
|
15
|
+
import {
|
|
16
|
+
recordActiveSubscriptionForProvider,
|
|
17
|
+
recordSubscriptionUsage,
|
|
18
|
+
removeSubscriptionUsageEntry,
|
|
19
|
+
} from '../tui-state';
|
|
20
|
+
import { createInternalAgentTextPart } from '../utils';
|
|
21
|
+
import {
|
|
22
|
+
getAccount,
|
|
23
|
+
loadAccounts,
|
|
24
|
+
loadAccountsResult,
|
|
25
|
+
maskCookie,
|
|
26
|
+
removeAccount,
|
|
27
|
+
type StoredAccount,
|
|
28
|
+
saveAccount,
|
|
29
|
+
setAccountKey,
|
|
30
|
+
updateAccountCookie,
|
|
31
|
+
} from './accounts-store';
|
|
32
|
+
import { scrapeNeuralwattQuota } from './neuralwatt-scraper';
|
|
33
|
+
import { scrapeQuota } from './opencode-go-scraper';
|
|
34
|
+
import type { SubscriptionProvider, SubscriptionUsageEntry } from './types';
|
|
35
|
+
|
|
36
|
+
const SUBSCRIPTIONS_COMMAND = 'subscriptions';
|
|
37
|
+
const DEFAULT_REFRESH_INTERVAL_MS = 60_000;
|
|
38
|
+
const DEFAULT_PERIODIC_INTERVAL_MS = 600_000; // 10 minutes
|
|
39
|
+
const PROVIDERS: SubscriptionProvider[] = ['opencode-go', 'neuralwatt'];
|
|
40
|
+
|
|
41
|
+
function parseProvider(
|
|
42
|
+
raw: string | undefined,
|
|
43
|
+
): SubscriptionProvider | undefined {
|
|
44
|
+
if (raw === 'opencode-go' || raw === 'neuralwatt') return raw;
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class UsageService {
|
|
49
|
+
private client: PluginInput['client'];
|
|
50
|
+
private lastRefresh = 0;
|
|
51
|
+
private pendingRefresh: Promise<SubscriptionUsageEntry[]> | null = null;
|
|
52
|
+
private cached: SubscriptionUsageEntry[] = [];
|
|
53
|
+
private refreshIntervalMs: number;
|
|
54
|
+
private periodicTimer: ReturnType<typeof setInterval> | null = null;
|
|
55
|
+
private periodicIntervalMs: number;
|
|
56
|
+
|
|
57
|
+
constructor(
|
|
58
|
+
client: PluginInput['client'],
|
|
59
|
+
refreshIntervalMs = DEFAULT_REFRESH_INTERVAL_MS,
|
|
60
|
+
periodicIntervalMs = DEFAULT_PERIODIC_INTERVAL_MS,
|
|
61
|
+
) {
|
|
62
|
+
this.client = client;
|
|
63
|
+
this.refreshIntervalMs = refreshIntervalMs;
|
|
64
|
+
this.periodicIntervalMs = periodicIntervalMs;
|
|
65
|
+
this.startPeriodicRefresh();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Get accounts from local storage. */
|
|
69
|
+
private getAccounts(): StoredAccount[] {
|
|
70
|
+
return loadAccounts();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private getAccountsResult() {
|
|
74
|
+
return loadAccountsResult();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Refresh all accounts' usage data, respecting rate limit unless forced.
|
|
79
|
+
* Returns the scraped results.
|
|
80
|
+
*/
|
|
81
|
+
async refresh(force = false): Promise<SubscriptionUsageEntry[]> {
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
if (!force && now - this.lastRefresh < this.refreshIntervalMs) {
|
|
84
|
+
return this.cached;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Deduplicate concurrent refresh calls
|
|
88
|
+
if (this.pendingRefresh) {
|
|
89
|
+
return this.pendingRefresh;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.pendingRefresh = this._doRefresh();
|
|
93
|
+
try {
|
|
94
|
+
this.cached = await this.pendingRefresh;
|
|
95
|
+
return this.cached;
|
|
96
|
+
} finally {
|
|
97
|
+
this.pendingRefresh = null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private async _doRefresh(): Promise<SubscriptionUsageEntry[]> {
|
|
102
|
+
this.resetPeriodicTimer();
|
|
103
|
+
const accountsResult = this.getAccountsResult();
|
|
104
|
+
if (!accountsResult.ok) {
|
|
105
|
+
return this.cached;
|
|
106
|
+
}
|
|
107
|
+
const accounts = accountsResult.accounts;
|
|
108
|
+
|
|
109
|
+
if (accounts.length === 0) {
|
|
110
|
+
recordSubscriptionUsage([]);
|
|
111
|
+
this.lastRefresh = Date.now();
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const controller = new AbortController();
|
|
116
|
+
const timeout = setTimeout(() => controller.abort(), 10_000);
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const results = await Promise.allSettled(
|
|
120
|
+
accounts.map(async (account) => {
|
|
121
|
+
if (account.provider === 'opencode-go') {
|
|
122
|
+
if (!account.authCookie?.trim()) {
|
|
123
|
+
return {
|
|
124
|
+
provider: 'opencode-go',
|
|
125
|
+
accountName: account.name,
|
|
126
|
+
workspaceId: account.workspaceId,
|
|
127
|
+
fetchedAt: Date.now(),
|
|
128
|
+
error:
|
|
129
|
+
'Missing OpenCode Go cookie. Re-add with /subscriptions add-opencode-go or update via /subscriptions edit.',
|
|
130
|
+
} as SubscriptionUsageEntry;
|
|
131
|
+
}
|
|
132
|
+
if (!account.apiKey?.trim()) {
|
|
133
|
+
return {
|
|
134
|
+
provider: 'opencode-go',
|
|
135
|
+
accountName: account.name,
|
|
136
|
+
workspaceId: account.workspaceId,
|
|
137
|
+
fetchedAt: Date.now(),
|
|
138
|
+
error:
|
|
139
|
+
'Missing OpenCode Go API key. Run /subscriptions set-key <name> <api-key>.',
|
|
140
|
+
} as SubscriptionUsageEntry;
|
|
141
|
+
}
|
|
142
|
+
const entry = await scrapeQuota(
|
|
143
|
+
account.workspaceId,
|
|
144
|
+
account.authCookie,
|
|
145
|
+
controller.signal,
|
|
146
|
+
);
|
|
147
|
+
entry.accountName = account.name;
|
|
148
|
+
return entry as SubscriptionUsageEntry;
|
|
149
|
+
} else {
|
|
150
|
+
// neuralwatt
|
|
151
|
+
if (!account.apiKey?.trim()) {
|
|
152
|
+
return {
|
|
153
|
+
provider: 'neuralwatt',
|
|
154
|
+
accountName: account.name,
|
|
155
|
+
fetchedAt: Date.now(),
|
|
156
|
+
error:
|
|
157
|
+
'Missing Neuralwatt API key. Re-add with /subscriptions add-neuralwatt.',
|
|
158
|
+
} as SubscriptionUsageEntry;
|
|
159
|
+
}
|
|
160
|
+
const entry = await scrapeNeuralwattQuota(
|
|
161
|
+
account.apiKey,
|
|
162
|
+
controller.signal,
|
|
163
|
+
);
|
|
164
|
+
entry.accountName = account.name;
|
|
165
|
+
return entry as SubscriptionUsageEntry;
|
|
166
|
+
}
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const entries: SubscriptionUsageEntry[] = [];
|
|
171
|
+
for (let i = 0; i < results.length; i++) {
|
|
172
|
+
const result = results[i];
|
|
173
|
+
if (result.status === 'fulfilled') {
|
|
174
|
+
entries.push(result.value);
|
|
175
|
+
} else {
|
|
176
|
+
const account = accounts[i];
|
|
177
|
+
entries.push({
|
|
178
|
+
provider: account.provider,
|
|
179
|
+
accountName: account.name,
|
|
180
|
+
workspaceId:
|
|
181
|
+
account.provider === 'opencode-go' ? account.workspaceId : '',
|
|
182
|
+
fetchedAt: Date.now(),
|
|
183
|
+
error: `Scrape failed: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`,
|
|
184
|
+
} as SubscriptionUsageEntry);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Persist to tui-state for the TUI sidebar to read
|
|
189
|
+
recordSubscriptionUsage(entries);
|
|
190
|
+
this.lastRefresh = Date.now();
|
|
191
|
+
|
|
192
|
+
return entries;
|
|
193
|
+
} finally {
|
|
194
|
+
clearTimeout(timeout);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Called when orchestrator goes idle - triggers a non-forced refresh.
|
|
200
|
+
*/
|
|
201
|
+
onOrchestratorIdle(): void {
|
|
202
|
+
// Sync active account from auth.json (handles external edits)
|
|
203
|
+
this.syncActiveAccounts();
|
|
204
|
+
// Fire-and-forget refresh (rate-limited internally)
|
|
205
|
+
this.refresh(false).catch(() => {
|
|
206
|
+
// Best-effort: errors are captured in the entries
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Sync the active account by comparing stored API keys against
|
|
212
|
+
* auth.json. If a stored account's apiKey matches the opencode-go
|
|
213
|
+
* key in auth.json, that account is marked active. Otherwise active
|
|
214
|
+
* is cleared. This keeps the sidebar accurate even if auth.json
|
|
215
|
+
* was edited externally.
|
|
216
|
+
*/
|
|
217
|
+
syncActiveAccounts(): Partial<Record<SubscriptionProvider, string>> {
|
|
218
|
+
const authPath = path.join(
|
|
219
|
+
process.env.XDG_DATA_HOME ?? path.join(os.homedir(), '.local', 'share'),
|
|
220
|
+
'opencode',
|
|
221
|
+
'auth.json',
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
let auth: Record<string, { type: string; key?: string }> = {};
|
|
225
|
+
try {
|
|
226
|
+
const raw = fs.readFileSync(authPath, 'utf8');
|
|
227
|
+
auth = JSON.parse(raw) as Record<string, { type: string; key?: string }>;
|
|
228
|
+
} catch {
|
|
229
|
+
// auth.json doesn't exist or can't be read
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const activeByProvider: Partial<Record<SubscriptionProvider, string>> = {};
|
|
233
|
+
const accounts = this.getAccounts();
|
|
234
|
+
for (const provider of PROVIDERS) {
|
|
235
|
+
const key = auth[provider]?.key;
|
|
236
|
+
const match =
|
|
237
|
+
typeof key === 'string' && key.length > 0
|
|
238
|
+
? accounts.find(
|
|
239
|
+
(account) =>
|
|
240
|
+
account.provider === provider && account.apiKey === key,
|
|
241
|
+
)
|
|
242
|
+
: undefined;
|
|
243
|
+
if (match) {
|
|
244
|
+
activeByProvider[provider] = match.name;
|
|
245
|
+
recordActiveSubscriptionForProvider(provider, match.name);
|
|
246
|
+
} else {
|
|
247
|
+
recordActiveSubscriptionForProvider(provider, null);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return activeByProvider;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Start the periodic background refresh timer.
|
|
255
|
+
*/
|
|
256
|
+
private startPeriodicRefresh(): void {
|
|
257
|
+
this.periodicTimer = setInterval(() => {
|
|
258
|
+
this.refresh(false).catch(() => {
|
|
259
|
+
// Best-effort: errors are captured in the entries
|
|
260
|
+
});
|
|
261
|
+
}, this.periodicIntervalMs);
|
|
262
|
+
// Don't block Node exit
|
|
263
|
+
if (this.periodicTimer && typeof this.periodicTimer.unref === 'function') {
|
|
264
|
+
this.periodicTimer.unref();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Reset the periodic timer - called after any actual refresh to
|
|
270
|
+
* restart the countdown.
|
|
271
|
+
*/
|
|
272
|
+
private resetPeriodicTimer(): void {
|
|
273
|
+
if (this.periodicTimer !== null) {
|
|
274
|
+
clearInterval(this.periodicTimer);
|
|
275
|
+
this.periodicTimer = null;
|
|
276
|
+
}
|
|
277
|
+
this.startPeriodicRefresh();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Clean up the periodic timer. Call when the plugin is shutting down.
|
|
282
|
+
*/
|
|
283
|
+
dispose(): void {
|
|
284
|
+
if (this.periodicTimer !== null) {
|
|
285
|
+
clearInterval(this.periodicTimer);
|
|
286
|
+
this.periodicTimer = null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Handle slash command: /subscriptions.
|
|
292
|
+
*/
|
|
293
|
+
async handleCommandExecuteBefore(
|
|
294
|
+
input: {
|
|
295
|
+
command: string;
|
|
296
|
+
sessionID: string;
|
|
297
|
+
arguments: string;
|
|
298
|
+
},
|
|
299
|
+
output: { parts: Array<{ type: string; text?: string }> },
|
|
300
|
+
): Promise<void> {
|
|
301
|
+
if (input.command === SUBSCRIPTIONS_COMMAND) {
|
|
302
|
+
await this.handleSubscriptionsCommand(input, output);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private async handleSubscriptionsCommand(
|
|
307
|
+
input: {
|
|
308
|
+
command: string;
|
|
309
|
+
sessionID: string;
|
|
310
|
+
arguments: string;
|
|
311
|
+
},
|
|
312
|
+
output: { parts: Array<{ type: string; text?: string }> },
|
|
313
|
+
): Promise<void> {
|
|
314
|
+
const args = input.arguments.trim();
|
|
315
|
+
const parts = args.split(/\s+/);
|
|
316
|
+
const subcommand = parts[0]?.toLowerCase();
|
|
317
|
+
|
|
318
|
+
switch (subcommand) {
|
|
319
|
+
case 'add-opencode-go':
|
|
320
|
+
case 'add': {
|
|
321
|
+
// 'add' defaults to opencode-go for backward compat
|
|
322
|
+
const [_, name, workspaceId, ...cookieParts] = parts;
|
|
323
|
+
const authCookie = cookieParts.join(' ');
|
|
324
|
+
if (!name || !workspaceId || !authCookie) {
|
|
325
|
+
output.parts.push(
|
|
326
|
+
createInternalAgentTextPart(
|
|
327
|
+
'Usage: /subscriptions add-opencode-go <name> <workspace-id> <auth-cookie>\n' +
|
|
328
|
+
'Example: /subscriptions add-opencode-go personal wrk_xxx Fe26.2...',
|
|
329
|
+
),
|
|
330
|
+
);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
saveAccount({
|
|
334
|
+
provider: 'opencode-go',
|
|
335
|
+
name,
|
|
336
|
+
workspaceId,
|
|
337
|
+
authCookie,
|
|
338
|
+
});
|
|
339
|
+
// Refresh to update sidebar immediately
|
|
340
|
+
this.refresh(true).catch(() => {});
|
|
341
|
+
output.parts.push(
|
|
342
|
+
createInternalAgentTextPart(
|
|
343
|
+
`✅ Added OpenCode Go account "${name}".`,
|
|
344
|
+
),
|
|
345
|
+
);
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
case 'add-neuralwatt': {
|
|
350
|
+
const [_, name, ...keyParts] = parts;
|
|
351
|
+
const apiKey = keyParts.join(' ');
|
|
352
|
+
if (!name || !apiKey) {
|
|
353
|
+
output.parts.push(
|
|
354
|
+
createInternalAgentTextPart(
|
|
355
|
+
'Usage: /subscriptions add-neuralwatt <name> <api-key>\n' +
|
|
356
|
+
'Example: /subscriptions add-neuralwatt my-neuralwatt sk-...',
|
|
357
|
+
),
|
|
358
|
+
);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
saveAccount({ provider: 'neuralwatt', name, apiKey });
|
|
362
|
+
// Refresh to update sidebar immediately
|
|
363
|
+
this.refresh(true).catch(() => {});
|
|
364
|
+
output.parts.push(
|
|
365
|
+
createInternalAgentTextPart(`✅ Added Neuralwatt account "${name}".`),
|
|
366
|
+
);
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
case 'remove':
|
|
371
|
+
case 'rm': {
|
|
372
|
+
const [_, name] = parts;
|
|
373
|
+
if (!name) {
|
|
374
|
+
output.parts.push(
|
|
375
|
+
createInternalAgentTextPart('Usage: /subscriptions remove <name>'),
|
|
376
|
+
);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const account = getAccount(name);
|
|
380
|
+
const activeByProvider = this.syncActiveAccounts();
|
|
381
|
+
const removed = removeAccount(name);
|
|
382
|
+
if (removed && account) {
|
|
383
|
+
const wasActive = activeByProvider[account.provider] === name;
|
|
384
|
+
if (wasActive) {
|
|
385
|
+
output.parts.push(
|
|
386
|
+
createInternalAgentTextPart(
|
|
387
|
+
`✅ Removed account "${name}" (was active for ${account.provider}).`,
|
|
388
|
+
),
|
|
389
|
+
);
|
|
390
|
+
} else {
|
|
391
|
+
output.parts.push(
|
|
392
|
+
createInternalAgentTextPart(`✅ Removed account "${name}".`),
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
// Clear sidebar entry immediately
|
|
396
|
+
removeSubscriptionUsageEntry(account.provider, name);
|
|
397
|
+
this.syncActiveAccounts();
|
|
398
|
+
} else {
|
|
399
|
+
output.parts.push(
|
|
400
|
+
createInternalAgentTextPart(`Account "${name}" not found.`),
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
case 'edit': {
|
|
407
|
+
const [_, name, ...cookieParts] = parts;
|
|
408
|
+
const authCookie = cookieParts.join(' ');
|
|
409
|
+
if (!name || !authCookie) {
|
|
410
|
+
output.parts.push(
|
|
411
|
+
createInternalAgentTextPart(
|
|
412
|
+
'Usage: /subscriptions edit <name> <new-auth-cookie>',
|
|
413
|
+
),
|
|
414
|
+
);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
const updated = updateAccountCookie(name, authCookie);
|
|
418
|
+
if (updated) {
|
|
419
|
+
output.parts.push(
|
|
420
|
+
createInternalAgentTextPart(
|
|
421
|
+
`✅ Updated auth cookie for "${name}".`,
|
|
422
|
+
),
|
|
423
|
+
);
|
|
424
|
+
this.refresh(true).catch(() => {});
|
|
425
|
+
} else {
|
|
426
|
+
output.parts.push(
|
|
427
|
+
createInternalAgentTextPart(
|
|
428
|
+
`Account "${name}" not found or is not an OpenCode Go account.`,
|
|
429
|
+
),
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
case 'list':
|
|
436
|
+
case 'ls': {
|
|
437
|
+
const accounts = this.getAccounts();
|
|
438
|
+
const activeByProvider = this.syncActiveAccounts();
|
|
439
|
+
if (accounts.length === 0) {
|
|
440
|
+
output.parts.push(
|
|
441
|
+
createInternalAgentTextPart(
|
|
442
|
+
'No accounts configured. Use /subscriptions add-opencode-go or /subscriptions add-neuralwatt to add one.',
|
|
443
|
+
),
|
|
444
|
+
);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
const lines = ['### Subscription Accounts', ''];
|
|
448
|
+
for (const acct of accounts) {
|
|
449
|
+
const isActive = activeByProvider[acct.provider] === acct.name;
|
|
450
|
+
const star = isActive ? '★ ' : ' ';
|
|
451
|
+
const providerLabel =
|
|
452
|
+
acct.provider === 'opencode-go' ? 'OpenCode Go' : 'Neuralwatt';
|
|
453
|
+
lines.push(`${star}${acct.name} (${providerLabel})`);
|
|
454
|
+
if (acct.provider === 'opencode-go') {
|
|
455
|
+
lines.push(` workspace: ${acct.workspaceId}`);
|
|
456
|
+
lines.push(` cookie: ${maskCookie(acct.authCookie)}`);
|
|
457
|
+
} else {
|
|
458
|
+
lines.push(` api-key: ${maskCookie(acct.apiKey)}`);
|
|
459
|
+
}
|
|
460
|
+
if (acct.provider === 'opencode-go' && acct.apiKey) {
|
|
461
|
+
lines.push(` provider-key: opencode-go (key set)`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
lines.push('');
|
|
465
|
+
lines.push('Active by provider:');
|
|
466
|
+
for (const provider of PROVIDERS) {
|
|
467
|
+
const activeName = activeByProvider[provider];
|
|
468
|
+
lines.push(` ${provider}: ${activeName ? `★ ${activeName}` : '-'}`);
|
|
469
|
+
}
|
|
470
|
+
lines.push('');
|
|
471
|
+
lines.push('Commands:');
|
|
472
|
+
lines.push(
|
|
473
|
+
' /subscriptions add-opencode-go <name> <workspace-id> <auth-cookie>',
|
|
474
|
+
);
|
|
475
|
+
lines.push(' /subscriptions add-neuralwatt <name> <api-key>');
|
|
476
|
+
lines.push(' /subscriptions remove <name>');
|
|
477
|
+
lines.push(' /subscriptions edit <name> <new-auth-cookie>');
|
|
478
|
+
lines.push(' /subscriptions set-key <name> <api-key>');
|
|
479
|
+
lines.push(' /subscriptions switch <provider> <name>');
|
|
480
|
+
lines.push(' /subscriptions list');
|
|
481
|
+
lines.push(' /subscriptions refresh');
|
|
482
|
+
output.parts.push(createInternalAgentTextPart(lines.join('\n')));
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
case 'set-key': {
|
|
487
|
+
const [_, name, ...keyParts] = parts;
|
|
488
|
+
const apiKey = keyParts.join(' ');
|
|
489
|
+
if (!name || !apiKey) {
|
|
490
|
+
output.parts.push(
|
|
491
|
+
createInternalAgentTextPart(
|
|
492
|
+
'Usage: /subscriptions set-key <name> <api-key>\n' +
|
|
493
|
+
'Example: /subscriptions set-key personal sk-...\n' +
|
|
494
|
+
'After setting a key, use /subscriptions switch <provider> <name> to activate it.',
|
|
495
|
+
),
|
|
496
|
+
);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const account = getAccount(name);
|
|
500
|
+
const provider = account?.provider ?? 'opencode-go';
|
|
501
|
+
const updated = setAccountKey(name, provider, apiKey);
|
|
502
|
+
if (updated) {
|
|
503
|
+
output.parts.push(
|
|
504
|
+
createInternalAgentTextPart(
|
|
505
|
+
`✅ Set API key for "${name}". Use /subscriptions switch <provider> ${name} to activate.`,
|
|
506
|
+
),
|
|
507
|
+
);
|
|
508
|
+
} else {
|
|
509
|
+
output.parts.push(
|
|
510
|
+
createInternalAgentTextPart(`Account "${name}" not found.`),
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
case 'switch': {
|
|
517
|
+
const [_, providerRaw, name] = parts;
|
|
518
|
+
const provider = parseProvider(providerRaw);
|
|
519
|
+
if (!provider || !name) {
|
|
520
|
+
output.parts.push(
|
|
521
|
+
createInternalAgentTextPart(
|
|
522
|
+
'Usage: /subscriptions switch <provider> <name>\n' +
|
|
523
|
+
'Providers: opencode-go, neuralwatt\n' +
|
|
524
|
+
'Example: /subscriptions switch opencode-go personal',
|
|
525
|
+
),
|
|
526
|
+
);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const account = this.getAccounts().find(
|
|
530
|
+
(candidate) =>
|
|
531
|
+
candidate.provider === provider && candidate.name === name,
|
|
532
|
+
);
|
|
533
|
+
if (!account) {
|
|
534
|
+
output.parts.push(
|
|
535
|
+
createInternalAgentTextPart(
|
|
536
|
+
`Account "${name}" not found for provider "${provider}".`,
|
|
537
|
+
),
|
|
538
|
+
);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (!account.apiKey) {
|
|
542
|
+
output.parts.push(
|
|
543
|
+
createInternalAgentTextPart(
|
|
544
|
+
`Account "${name}" has no API key set. Use /subscriptions set-key ${name} <api-key> first.`,
|
|
545
|
+
),
|
|
546
|
+
);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const activeByProvider = this.syncActiveAccounts();
|
|
550
|
+
// No-op if already active for this provider
|
|
551
|
+
if (activeByProvider[account.provider] === name) {
|
|
552
|
+
output.parts.push(
|
|
553
|
+
createInternalAgentTextPart(
|
|
554
|
+
`Account "${name}" is already active for ${account.provider}.`,
|
|
555
|
+
),
|
|
556
|
+
);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
try {
|
|
560
|
+
// Write the API key to OpenCode auth.json via SDK's auth.set()
|
|
561
|
+
await this.client.auth.set({
|
|
562
|
+
path: { id: account.provider },
|
|
563
|
+
body: { type: 'api', key: account.apiKey },
|
|
564
|
+
});
|
|
565
|
+
} catch {
|
|
566
|
+
output.parts.push(
|
|
567
|
+
createInternalAgentTextPart(
|
|
568
|
+
'⚠ Failed to update auth. The key was not applied.',
|
|
569
|
+
),
|
|
570
|
+
);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
this.syncActiveAccounts();
|
|
574
|
+
// Show restart toast
|
|
575
|
+
this.client.tui
|
|
576
|
+
.showToast({
|
|
577
|
+
body: {
|
|
578
|
+
title: 'Account Switched',
|
|
579
|
+
message: `Switched to "${name}". Restart for new API key.`,
|
|
580
|
+
variant: 'success',
|
|
581
|
+
duration: 5000,
|
|
582
|
+
},
|
|
583
|
+
})
|
|
584
|
+
.catch(() => {});
|
|
585
|
+
output.parts.push(
|
|
586
|
+
createInternalAgentTextPart(
|
|
587
|
+
`✅ Switched ${account.provider} to account "${name}".`,
|
|
588
|
+
),
|
|
589
|
+
);
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
case 'refresh': {
|
|
594
|
+
await this.refresh(true);
|
|
595
|
+
output.parts.push(
|
|
596
|
+
createInternalAgentTextPart('✅ Refreshed all accounts.'),
|
|
597
|
+
);
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
default: {
|
|
602
|
+
output.parts.push(
|
|
603
|
+
createInternalAgentTextPart(
|
|
604
|
+
'Subscription Account Management\n\n' +
|
|
605
|
+
'Commands:\n' +
|
|
606
|
+
' /subscriptions add-opencode-go <name> <workspace-id> <auth-cookie> Add an OpenCode Go account\n' +
|
|
607
|
+
' /subscriptions add-neuralwatt <name> <api-key> Add a Neuralwatt account\n' +
|
|
608
|
+
' /subscriptions remove <name> Remove an account\n' +
|
|
609
|
+
' /subscriptions edit <name> <new-auth-cookie> Update auth cookie (OpenCode Go)\n' +
|
|
610
|
+
' /subscriptions set-key <name> <api-key> Set API key for switching\n' +
|
|
611
|
+
' /subscriptions switch <provider> <name> Switch active account for provider\n' +
|
|
612
|
+
' /subscriptions list List all accounts\n' +
|
|
613
|
+
' /subscriptions refresh Force refresh all',
|
|
614
|
+
),
|
|
615
|
+
);
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Register /subscriptions command in OpenCode config.
|
|
623
|
+
*/
|
|
624
|
+
registerCommand(opencodeConfig: Record<string, unknown>): void {
|
|
625
|
+
const configCommand = opencodeConfig.command as
|
|
626
|
+
| Record<string, unknown>
|
|
627
|
+
| undefined;
|
|
628
|
+
if (!opencodeConfig.command) {
|
|
629
|
+
opencodeConfig.command = {};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (!configCommand?.[SUBSCRIPTIONS_COMMAND]) {
|
|
633
|
+
(opencodeConfig.command as Record<string, unknown>)[
|
|
634
|
+
SUBSCRIPTIONS_COMMAND
|
|
635
|
+
] = {
|
|
636
|
+
template:
|
|
637
|
+
'Manage subscription accounts (add-opencode-go, add-neuralwatt, remove, list, edit, set-key, switch, refresh)',
|
|
638
|
+
description:
|
|
639
|
+
'Add, remove, list, edit, set-key, switch, or refresh subscription accounts for usage tracking in the sidebar',
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
export function createUsageService(
|
|
646
|
+
client: PluginInput['client'],
|
|
647
|
+
refreshIntervalMs?: number,
|
|
648
|
+
periodicIntervalMs?: number,
|
|
649
|
+
): UsageService {
|
|
650
|
+
return new UsageService(client, refreshIntervalMs, periodicIntervalMs);
|
|
651
|
+
}
|