opencode-dux 1.1.1 → 1.2.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/README.md +5 -3
- package/dist/index.js +145 -18
- package/dist/subscriptions/codex-scraper.d.ts +12 -0
- package/dist/subscriptions/index.d.ts +2 -1
- package/dist/subscriptions/types.d.ts +25 -3
- package/dist/tui.js +42 -2
- package/package.json +1 -1
- package/src/hooks/auto-update-checker/index.test.ts +8 -10
- package/src/hooks/auto-update-checker/index.ts +10 -10
- package/src/subscriptions/accounts-store.ts +12 -2
- package/src/subscriptions/codex-scraper.ts +98 -0
- package/src/subscriptions/index.ts +2 -0
- package/src/subscriptions/types.ts +170 -145
- package/src/subscriptions/usage-service.ts +92 -18
- package/src/tui-state.ts +1 -1
- package/src/tui.ts +62 -1
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex quota API scraper.
|
|
3
|
+
*
|
|
4
|
+
* Fetches usage data from the Codex (chatgpt.com) WHAM usage API using Bearer
|
|
5
|
+
* token authentication. Returns structured quota data including usage windows
|
|
6
|
+
* and credit balance.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { CodexUsageEntry, UsageWindow } from './types';
|
|
10
|
+
|
|
11
|
+
const CODEX_WHAM_URL = 'https://chatgpt.com/backend-api/wham/usage';
|
|
12
|
+
|
|
13
|
+
const EMPTY_WINDOW: UsageWindow = {
|
|
14
|
+
usagePercent: 0,
|
|
15
|
+
percentRemaining: 100,
|
|
16
|
+
resetInSec: 0,
|
|
17
|
+
resetTimeIso: '',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const EMPTY_CREDITS = { hasCredits: false, unlimited: false, balance: 0 };
|
|
21
|
+
|
|
22
|
+
function windowFromApi(
|
|
23
|
+
w: { used_percent: number; reset_at: number; limit_window_seconds: number } | undefined,
|
|
24
|
+
): UsageWindow {
|
|
25
|
+
if (!w) return { ...EMPTY_WINDOW };
|
|
26
|
+
const usagePercent = Math.max(0, Math.min(100, w.used_percent));
|
|
27
|
+
return {
|
|
28
|
+
usagePercent,
|
|
29
|
+
percentRemaining: 100 - usagePercent,
|
|
30
|
+
resetInSec: Math.max(0, w.limit_window_seconds),
|
|
31
|
+
resetTimeIso: w.reset_at ? new Date(w.reset_at * 1000).toISOString() : '',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Fetch Codex quota data via the WHAM usage API.
|
|
37
|
+
*/
|
|
38
|
+
export async function scrapeCodexQuota(
|
|
39
|
+
accessToken: string,
|
|
40
|
+
signal?: AbortSignal,
|
|
41
|
+
): Promise<CodexUsageEntry> {
|
|
42
|
+
const accountName = ''; // filled by caller
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const res = await fetch(CODEX_WHAM_URL, {
|
|
46
|
+
headers: {
|
|
47
|
+
Authorization: `Bearer ${accessToken}`,
|
|
48
|
+
Accept: 'application/json',
|
|
49
|
+
},
|
|
50
|
+
signal,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
return {
|
|
55
|
+
provider: 'codex',
|
|
56
|
+
accountName,
|
|
57
|
+
fetchedAt: Date.now(),
|
|
58
|
+
error: `Codex API returned ${res.status} ${res.statusText}`,
|
|
59
|
+
primaryWindow: { ...EMPTY_WINDOW },
|
|
60
|
+
secondaryWindow: { ...EMPTY_WINDOW },
|
|
61
|
+
credits: { ...EMPTY_CREDITS },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const data = await res.json() as {
|
|
66
|
+
plan_type?: string;
|
|
67
|
+
rate_limit?: {
|
|
68
|
+
primary_window?: { used_percent: number; reset_at: number; limit_window_seconds: number };
|
|
69
|
+
secondary_window?: { used_percent: number; reset_at: number; limit_window_seconds: number };
|
|
70
|
+
};
|
|
71
|
+
credits?: { has_credits: boolean; unlimited: boolean; balance: number };
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
provider: 'codex',
|
|
76
|
+
accountName,
|
|
77
|
+
fetchedAt: Date.now(),
|
|
78
|
+
primaryWindow: windowFromApi(data.rate_limit?.primary_window),
|
|
79
|
+
secondaryWindow: windowFromApi(data.rate_limit?.secondary_window),
|
|
80
|
+
credits: {
|
|
81
|
+
hasCredits: data.credits?.has_credits ?? false,
|
|
82
|
+
unlimited: data.credits?.unlimited ?? false,
|
|
83
|
+
balance: data.credits?.balance ?? 0,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
} catch (err: unknown) {
|
|
87
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
88
|
+
return {
|
|
89
|
+
provider: 'codex',
|
|
90
|
+
accountName,
|
|
91
|
+
fetchedAt: Date.now(),
|
|
92
|
+
error: `Codex fetch failed: ${message}`,
|
|
93
|
+
primaryWindow: { ...EMPTY_WINDOW },
|
|
94
|
+
secondaryWindow: { ...EMPTY_WINDOW },
|
|
95
|
+
credits: { ...EMPTY_CREDITS },
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -18,9 +18,11 @@ export {
|
|
|
18
18
|
setAccountKey,
|
|
19
19
|
updateAccountCookie,
|
|
20
20
|
} from './accounts-store';
|
|
21
|
+
export { scrapeCodexQuota } from './codex-scraper';
|
|
21
22
|
export { scrapeNeuralwattQuota } from './neuralwatt-scraper';
|
|
22
23
|
export { scrapeQuota, scrapeUsagePage } from './opencode-go-scraper';
|
|
23
24
|
export type {
|
|
25
|
+
CodexUsageEntry,
|
|
24
26
|
NeuralwattUsageEntry,
|
|
25
27
|
OpenCodeGoUsageEntry,
|
|
26
28
|
SubscriptionUsageEntry,
|
|
@@ -1,145 +1,170 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type definitions for multi-provider subscription usage tracking.
|
|
3
|
-
*
|
|
4
|
-
* Supports OpenCode Go (dashboard scraping) and Neuralwatt (REST API)
|
|
5
|
-
* as discriminated unions on the `provider` field.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/** Provider discriminator. */
|
|
9
|
-
export type SubscriptionProvider = 'opencode-go' | 'neuralwatt';
|
|
10
|
-
|
|
11
|
-
// ── Account definitions (discriminated union) ──
|
|
12
|
-
|
|
13
|
-
export interface OpenCodeGoAccount {
|
|
14
|
-
provider: 'opencode-go';
|
|
15
|
-
name: string;
|
|
16
|
-
workspaceId: string;
|
|
17
|
-
authCookie: string;
|
|
18
|
-
apiKey?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface NeuralwattAccount {
|
|
22
|
-
provider: 'neuralwatt';
|
|
23
|
-
name: string;
|
|
24
|
-
apiKey: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
export interface
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
export interface
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for multi-provider subscription usage tracking.
|
|
3
|
+
*
|
|
4
|
+
* Supports OpenCode Go (dashboard scraping) and Neuralwatt (REST API)
|
|
5
|
+
* as discriminated unions on the `provider` field.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Provider discriminator. */
|
|
9
|
+
export type SubscriptionProvider = 'opencode-go' | 'neuralwatt' | 'codex';
|
|
10
|
+
|
|
11
|
+
// ── Account definitions (discriminated union) ──
|
|
12
|
+
|
|
13
|
+
export interface OpenCodeGoAccount {
|
|
14
|
+
provider: 'opencode-go';
|
|
15
|
+
name: string;
|
|
16
|
+
workspaceId: string;
|
|
17
|
+
authCookie: string;
|
|
18
|
+
apiKey?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface NeuralwattAccount {
|
|
22
|
+
provider: 'neuralwatt';
|
|
23
|
+
name: string;
|
|
24
|
+
apiKey: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CodexAccount {
|
|
28
|
+
provider: 'codex';
|
|
29
|
+
name: string;
|
|
30
|
+
accessToken: string;
|
|
31
|
+
refreshToken?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type StoredAccount = OpenCodeGoAccount | NeuralwattAccount | CodexAccount;
|
|
35
|
+
|
|
36
|
+
// ── Usage window (OpenCode Go) ──
|
|
37
|
+
|
|
38
|
+
/** Per-time-window usage data scraped from the OpenCode Go dashboard. */
|
|
39
|
+
export interface UsageWindow {
|
|
40
|
+
/** Usage percentage [0..100] */
|
|
41
|
+
usagePercent: number;
|
|
42
|
+
/** Seconds until usage resets */
|
|
43
|
+
resetInSec: number;
|
|
44
|
+
/** Remaining percentage [0..100] */
|
|
45
|
+
percentRemaining: number;
|
|
46
|
+
/** ISO reset timestamp */
|
|
47
|
+
resetTimeIso: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── OpenCode Go usage entry ──
|
|
51
|
+
|
|
52
|
+
/** Snapshot entry per OpenCode Go account - stored in tui-state.json. */
|
|
53
|
+
export interface OpenCodeGoUsageEntry {
|
|
54
|
+
provider: 'opencode-go';
|
|
55
|
+
/** Display name for this account (from config). */
|
|
56
|
+
accountName: string;
|
|
57
|
+
/** OpenCode Go workspace ID. */
|
|
58
|
+
workspaceId: string;
|
|
59
|
+
/** Rolling (~5h) usage window, when present. */
|
|
60
|
+
rolling?: UsageWindow;
|
|
61
|
+
/** Weekly usage window, when present. */
|
|
62
|
+
weekly?: UsageWindow;
|
|
63
|
+
/** Monthly usage window, when present. */
|
|
64
|
+
monthly?: UsageWindow;
|
|
65
|
+
/** Timestamp when data was fetched. */
|
|
66
|
+
fetchedAt: number;
|
|
67
|
+
/** Error message if the scrape failed for this account. */
|
|
68
|
+
error?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── Neuralwatt types ──
|
|
72
|
+
|
|
73
|
+
export interface NeuralwattBalance {
|
|
74
|
+
credits_remaining_usd: number;
|
|
75
|
+
total_credits_usd: number;
|
|
76
|
+
credits_used_usd: number;
|
|
77
|
+
accounting_method: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface NeuralwattUsagePeriod {
|
|
81
|
+
cost_usd: number;
|
|
82
|
+
requests: number;
|
|
83
|
+
tokens: number;
|
|
84
|
+
energy_kwh: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface NeuralwattUsage {
|
|
88
|
+
lifetime: NeuralwattUsagePeriod;
|
|
89
|
+
current_month: NeuralwattUsagePeriod;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface NeuralwattSubscription {
|
|
93
|
+
plan: string;
|
|
94
|
+
status: 'active' | 'canceling' | 'past_due' | 'paused' | 'trialing';
|
|
95
|
+
billing_interval: string | null;
|
|
96
|
+
current_period_start: string | null;
|
|
97
|
+
current_period_end: string | null;
|
|
98
|
+
auto_renew: boolean | null;
|
|
99
|
+
kwh_included: number | null;
|
|
100
|
+
kwh_used: number | null;
|
|
101
|
+
kwh_remaining: number | null;
|
|
102
|
+
in_overage: boolean | null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Snapshot entry per Neuralwatt account - stored in tui-state.json. */
|
|
106
|
+
export interface NeuralwattUsageEntry {
|
|
107
|
+
provider: 'neuralwatt';
|
|
108
|
+
/** Display name for this account. */
|
|
109
|
+
accountName: string;
|
|
110
|
+
/** ISO timestamp from the API response. */
|
|
111
|
+
snapshot_at: string;
|
|
112
|
+
/** Credit balance. */
|
|
113
|
+
balance: NeuralwattBalance;
|
|
114
|
+
/** Usage data (lifetime + current month). */
|
|
115
|
+
usage: NeuralwattUsage;
|
|
116
|
+
/** Subscription details, null if no active subscription. */
|
|
117
|
+
subscription: NeuralwattSubscription | null;
|
|
118
|
+
/** Timestamp when data was fetched. */
|
|
119
|
+
fetchedAt: number;
|
|
120
|
+
/** Error message if the fetch failed. */
|
|
121
|
+
error?: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ── Codex usage entry ──
|
|
125
|
+
|
|
126
|
+
export interface CodexUsageEntry {
|
|
127
|
+
provider: 'codex';
|
|
128
|
+
accountName: string;
|
|
129
|
+
fetchedAt: number;
|
|
130
|
+
error?: string;
|
|
131
|
+
/** 5-hour rolling window (primary_window from API) */
|
|
132
|
+
primaryWindow: UsageWindow;
|
|
133
|
+
/** 7-day rolling window (secondary_window from API) */
|
|
134
|
+
secondaryWindow: UsageWindow;
|
|
135
|
+
/** Credit balance info */
|
|
136
|
+
credits: {
|
|
137
|
+
hasCredits: boolean;
|
|
138
|
+
unlimited: boolean;
|
|
139
|
+
balance: number;
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export type SubscriptionUsageEntry =
|
|
144
|
+
| OpenCodeGoUsageEntry
|
|
145
|
+
| NeuralwattUsageEntry
|
|
146
|
+
| CodexUsageEntry;
|
|
147
|
+
|
|
148
|
+
// ── Detailed usage (OpenCode Go /usage page) ──
|
|
149
|
+
|
|
150
|
+
/** Detailed usage data from the /usage page. */
|
|
151
|
+
export interface UsageDetail {
|
|
152
|
+
/** Total number of API calls. */
|
|
153
|
+
totalCalls: number;
|
|
154
|
+
/** Total estimated cost in USD. */
|
|
155
|
+
totalCost: number;
|
|
156
|
+
/** Per-model breakdown. */
|
|
157
|
+
perModel: Array<{
|
|
158
|
+
model: string;
|
|
159
|
+
calls: number;
|
|
160
|
+
cost: number;
|
|
161
|
+
}>;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ── Config ──
|
|
165
|
+
|
|
166
|
+
/** Config for the subscription tracking feature. */
|
|
167
|
+
export interface SubscriptionsConfig {
|
|
168
|
+
/** Minimum interval between auto-refreshes in ms (default: 60000). */
|
|
169
|
+
refreshIntervalMs: number;
|
|
170
|
+
}
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
setAccountKey,
|
|
30
30
|
updateAccountCookie,
|
|
31
31
|
} from './accounts-store';
|
|
32
|
+
import { scrapeCodexQuota } from './codex-scraper';
|
|
32
33
|
import { scrapeNeuralwattQuota } from './neuralwatt-scraper';
|
|
33
34
|
import { scrapeQuota } from './opencode-go-scraper';
|
|
34
35
|
import type { SubscriptionProvider, SubscriptionUsageEntry } from './types';
|
|
@@ -36,12 +37,12 @@ import type { SubscriptionProvider, SubscriptionUsageEntry } from './types';
|
|
|
36
37
|
const SUBSCRIPTIONS_COMMAND = 'subscriptions';
|
|
37
38
|
const DEFAULT_REFRESH_INTERVAL_MS = 60_000;
|
|
38
39
|
const DEFAULT_PERIODIC_INTERVAL_MS = 600_000; // 10 minutes
|
|
39
|
-
const PROVIDERS: SubscriptionProvider[] = ['opencode-go', 'neuralwatt'];
|
|
40
|
+
const PROVIDERS: SubscriptionProvider[] = ['opencode-go', 'neuralwatt', 'codex'];
|
|
40
41
|
|
|
41
42
|
function parseProvider(
|
|
42
43
|
raw: string | undefined,
|
|
43
44
|
): SubscriptionProvider | undefined {
|
|
44
|
-
if (raw === 'opencode-go' || raw === 'neuralwatt') return raw;
|
|
45
|
+
if (raw === 'opencode-go' || raw === 'neuralwatt' || raw === 'codex') return raw;
|
|
45
46
|
return undefined;
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -146,6 +147,35 @@ export class UsageService {
|
|
|
146
147
|
);
|
|
147
148
|
entry.accountName = account.name;
|
|
148
149
|
return entry as SubscriptionUsageEntry;
|
|
150
|
+
} else if (account.provider === 'codex') {
|
|
151
|
+
if (!account.accessToken?.trim()) {
|
|
152
|
+
return {
|
|
153
|
+
provider: 'codex',
|
|
154
|
+
accountName: account.name,
|
|
155
|
+
fetchedAt: Date.now(),
|
|
156
|
+
error:
|
|
157
|
+
'Missing Codex access token. Re-add with /subscriptions add-codex.',
|
|
158
|
+
primaryWindow: {
|
|
159
|
+
usagePercent: 0,
|
|
160
|
+
percentRemaining: 100,
|
|
161
|
+
resetInSec: 0,
|
|
162
|
+
resetTimeIso: '',
|
|
163
|
+
},
|
|
164
|
+
secondaryWindow: {
|
|
165
|
+
usagePercent: 0,
|
|
166
|
+
percentRemaining: 100,
|
|
167
|
+
resetInSec: 0,
|
|
168
|
+
resetTimeIso: '',
|
|
169
|
+
},
|
|
170
|
+
credits: { hasCredits: false, unlimited: false, balance: 0 },
|
|
171
|
+
} as SubscriptionUsageEntry;
|
|
172
|
+
}
|
|
173
|
+
const entry = await scrapeCodexQuota(
|
|
174
|
+
account.accessToken,
|
|
175
|
+
controller.signal,
|
|
176
|
+
);
|
|
177
|
+
entry.accountName = account.name;
|
|
178
|
+
return entry as SubscriptionUsageEntry;
|
|
149
179
|
} else {
|
|
150
180
|
// neuralwatt
|
|
151
181
|
if (!account.apiKey?.trim()) {
|
|
@@ -235,10 +265,11 @@ export class UsageService {
|
|
|
235
265
|
const key = auth[provider]?.key;
|
|
236
266
|
const match =
|
|
237
267
|
typeof key === 'string' && key.length > 0
|
|
238
|
-
? accounts.find(
|
|
239
|
-
(account)
|
|
240
|
-
|
|
241
|
-
|
|
268
|
+
? accounts.find((account): boolean => {
|
|
269
|
+
if (account.provider !== provider) return false;
|
|
270
|
+
if (account.provider === 'codex') return account.accessToken === key;
|
|
271
|
+
return account.apiKey === key;
|
|
272
|
+
})
|
|
242
273
|
: undefined;
|
|
243
274
|
if (match) {
|
|
244
275
|
activeByProvider[provider] = match.name;
|
|
@@ -367,6 +398,26 @@ export class UsageService {
|
|
|
367
398
|
break;
|
|
368
399
|
}
|
|
369
400
|
|
|
401
|
+
case 'add-codex': {
|
|
402
|
+
const [_, name, ...tokenParts] = parts;
|
|
403
|
+
const accessToken = tokenParts.join(' ');
|
|
404
|
+
if (!name || !accessToken) {
|
|
405
|
+
output.parts.push(
|
|
406
|
+
createInternalAgentTextPart(
|
|
407
|
+
'Usage: /subscriptions add-codex <name> <access-token>\n' +
|
|
408
|
+
'Example: /subscriptions add-codex my-codex eyJhbGci...',
|
|
409
|
+
),
|
|
410
|
+
);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
saveAccount({ provider: 'codex', name, accessToken });
|
|
414
|
+
this.refresh(true).catch(() => {});
|
|
415
|
+
output.parts.push(
|
|
416
|
+
createInternalAgentTextPart(`✅ Added Codex account "${name}".`),
|
|
417
|
+
);
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
|
|
370
421
|
case 'remove':
|
|
371
422
|
case 'rm': {
|
|
372
423
|
const [_, name] = parts;
|
|
@@ -439,7 +490,7 @@ export class UsageService {
|
|
|
439
490
|
if (accounts.length === 0) {
|
|
440
491
|
output.parts.push(
|
|
441
492
|
createInternalAgentTextPart(
|
|
442
|
-
'No accounts configured. Use /subscriptions add-opencode-go or /subscriptions add-
|
|
493
|
+
'No accounts configured. Use /subscriptions add-opencode-go, /subscriptions add-neuralwatt, or /subscriptions add-codex to add one.',
|
|
443
494
|
),
|
|
444
495
|
);
|
|
445
496
|
return;
|
|
@@ -449,11 +500,17 @@ export class UsageService {
|
|
|
449
500
|
const isActive = activeByProvider[acct.provider] === acct.name;
|
|
450
501
|
const star = isActive ? '★ ' : ' ';
|
|
451
502
|
const providerLabel =
|
|
452
|
-
acct.provider === 'opencode-go'
|
|
503
|
+
acct.provider === 'opencode-go'
|
|
504
|
+
? 'OpenCode Go'
|
|
505
|
+
: acct.provider === 'codex'
|
|
506
|
+
? 'Codex'
|
|
507
|
+
: 'Neuralwatt';
|
|
453
508
|
lines.push(`${star}${acct.name} (${providerLabel})`);
|
|
454
509
|
if (acct.provider === 'opencode-go') {
|
|
455
510
|
lines.push(` workspace: ${acct.workspaceId}`);
|
|
456
511
|
lines.push(` cookie: ${maskCookie(acct.authCookie)}`);
|
|
512
|
+
} else if (acct.provider === 'codex') {
|
|
513
|
+
lines.push(` access-token: ${maskCookie(acct.accessToken)}`);
|
|
457
514
|
} else {
|
|
458
515
|
lines.push(` api-key: ${maskCookie(acct.apiKey)}`);
|
|
459
516
|
}
|
|
@@ -473,6 +530,7 @@ export class UsageService {
|
|
|
473
530
|
' /subscriptions add-opencode-go <name> <workspace-id> <auth-cookie>',
|
|
474
531
|
);
|
|
475
532
|
lines.push(' /subscriptions add-neuralwatt <name> <api-key>');
|
|
533
|
+
lines.push(' /subscriptions add-codex <name> <access-token>');
|
|
476
534
|
lines.push(' /subscriptions remove <name>');
|
|
477
535
|
lines.push(' /subscriptions edit <name> <new-auth-cookie>');
|
|
478
536
|
lines.push(' /subscriptions set-key <name> <api-key>');
|
|
@@ -520,7 +578,7 @@ export class UsageService {
|
|
|
520
578
|
output.parts.push(
|
|
521
579
|
createInternalAgentTextPart(
|
|
522
580
|
'Usage: /subscriptions switch <provider> <name>\n' +
|
|
523
|
-
'Providers: opencode-go, neuralwatt\n' +
|
|
581
|
+
'Providers: opencode-go, neuralwatt, codex\n' +
|
|
524
582
|
'Example: /subscriptions switch opencode-go personal',
|
|
525
583
|
),
|
|
526
584
|
);
|
|
@@ -538,13 +596,24 @@ export class UsageService {
|
|
|
538
596
|
);
|
|
539
597
|
return;
|
|
540
598
|
}
|
|
541
|
-
if (
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
599
|
+
if (account.provider === 'codex') {
|
|
600
|
+
if (!account.accessToken) {
|
|
601
|
+
output.parts.push(
|
|
602
|
+
createInternalAgentTextPart(
|
|
603
|
+
`Account "${name}" has no access token set. Use /subscriptions add-codex to re-add.`,
|
|
604
|
+
),
|
|
605
|
+
);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
} else {
|
|
609
|
+
if (!account.apiKey) {
|
|
610
|
+
output.parts.push(
|
|
611
|
+
createInternalAgentTextPart(
|
|
612
|
+
`Account "${name}" has no API key set. Use /subscriptions set-key ${name} <api-key> first.`,
|
|
613
|
+
),
|
|
614
|
+
);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
548
617
|
}
|
|
549
618
|
const activeByProvider = this.syncActiveAccounts();
|
|
550
619
|
// No-op if already active for this provider
|
|
@@ -558,9 +627,13 @@ export class UsageService {
|
|
|
558
627
|
}
|
|
559
628
|
try {
|
|
560
629
|
// Write the API key to OpenCode auth.json via SDK's auth.set()
|
|
630
|
+
const key: string =
|
|
631
|
+
account.provider === 'codex'
|
|
632
|
+
? account.accessToken
|
|
633
|
+
: (account.apiKey ?? '');
|
|
561
634
|
await this.client.auth.set({
|
|
562
635
|
path: { id: account.provider },
|
|
563
|
-
body: { type: 'api', key
|
|
636
|
+
body: { type: 'api', key },
|
|
564
637
|
});
|
|
565
638
|
} catch {
|
|
566
639
|
output.parts.push(
|
|
@@ -605,6 +678,7 @@ export class UsageService {
|
|
|
605
678
|
'Commands:\n' +
|
|
606
679
|
' /subscriptions add-opencode-go <name> <workspace-id> <auth-cookie> Add an OpenCode Go account\n' +
|
|
607
680
|
' /subscriptions add-neuralwatt <name> <api-key> Add a Neuralwatt account\n' +
|
|
681
|
+
' /subscriptions add-codex <name> <access-token> Add a Codex account\n' +
|
|
608
682
|
' /subscriptions remove <name> Remove an account\n' +
|
|
609
683
|
' /subscriptions edit <name> <new-auth-cookie> Update auth cookie (OpenCode Go)\n' +
|
|
610
684
|
' /subscriptions set-key <name> <api-key> Set API key for switching\n' +
|
|
@@ -634,7 +708,7 @@ export class UsageService {
|
|
|
634
708
|
SUBSCRIPTIONS_COMMAND
|
|
635
709
|
] = {
|
|
636
710
|
template:
|
|
637
|
-
'Manage subscription accounts (add-opencode-go, add-neuralwatt, remove, list, edit, set-key, switch, refresh)',
|
|
711
|
+
'Manage subscription accounts (add-opencode-go, add-neuralwatt, add-codex, remove, list, edit, set-key, switch, refresh)',
|
|
638
712
|
description:
|
|
639
713
|
'Add, remove, list, edit, set-key, switch, or refresh subscription accounts for usage tracking in the sidebar',
|
|
640
714
|
};
|
package/src/tui-state.ts
CHANGED
|
@@ -616,7 +616,7 @@ function parseSnapshot(value: string): TuiSnapshot | null {
|
|
|
616
616
|
Record<SubscriptionProvider, string>
|
|
617
617
|
> = {};
|
|
618
618
|
if (parsed.activeSubscriptionByProvider) {
|
|
619
|
-
for (const provider of ['opencode-go', 'neuralwatt'] as const) {
|
|
619
|
+
for (const provider of ['opencode-go', 'neuralwatt', 'codex'] as const) {
|
|
620
620
|
const name = parsed.activeSubscriptionByProvider[provider];
|
|
621
621
|
if (typeof name === 'string' && name.length > 0) {
|
|
622
622
|
activeSubscriptionByProvider[provider] = name;
|