opencode-dux 1.1.0 → 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/dist/index.js CHANGED
@@ -23595,29 +23595,29 @@ async function runBackgroundUpdateCheck(ctx, autoUpdate) {
23595
23595
  }
23596
23596
  log(`[auto-update-checker] Update available (${channel}): ${currentVersion} → ${latestVersion}`);
23597
23597
  if (pluginInfo.isPinned) {
23598
- showToast(ctx, `OMO-Slim ${latestVersion}`, `v${latestVersion} available.
23598
+ showToast(ctx, `Opencode Dux ${latestVersion}`, `Update from v${currentVersion} to v${latestVersion} available.
23599
23599
  Version is pinned. Update your plugin config to apply.`, "info", 8000);
23600
23600
  log(`[auto-update-checker] Version is pinned; skipping auto-update.`);
23601
23601
  return;
23602
23602
  }
23603
23603
  if (!autoUpdate) {
23604
- showToast(ctx, `OMO-Slim ${latestVersion}`, `v${latestVersion} available. Auto-update is disabled.`, "info", 8000);
23604
+ showToast(ctx, `Opencode Dux ${latestVersion}`, `Update from v${currentVersion} to v${latestVersion} available. Auto-update is disabled.`, "info", 8000);
23605
23605
  log("[auto-update-checker] Auto-update disabled, notification only");
23606
23606
  return;
23607
23607
  }
23608
23608
  const installDir = preparePackageUpdate(latestVersion, PACKAGE_NAME);
23609
23609
  if (!installDir) {
23610
- showToast(ctx, `OMO-Slim ${latestVersion}`, `v${latestVersion} available. Auto-update could not prepare the active install.`, "info", 8000);
23610
+ showToast(ctx, `Opencode Dux ${latestVersion}`, `Update from v${currentVersion} to v${latestVersion} available. Auto-update could not prepare the active install.`, "info", 8000);
23611
23611
  log("[auto-update-checker] Failed to prepare install root for auto-update");
23612
23612
  return;
23613
23613
  }
23614
23614
  const installSuccess = await runBunInstallSafe(installDir);
23615
23615
  if (installSuccess) {
23616
- showToast(ctx, "OMO-Slim Updated!", `v${currentVersion} v${latestVersion}
23616
+ showToast(ctx, "Opencode Dux Updated!", `Updated from v${currentVersion} to v${latestVersion}
23617
23617
  Restart OpenCode to apply.`, "success", 8000);
23618
23618
  log(`[auto-update-checker] Update installed: ${currentVersion} → ${latestVersion}`);
23619
23619
  } else {
23620
- showToast(ctx, `OMO-Slim ${latestVersion}`, `v${latestVersion} available, but auto-update failed to install it. Check logs or retry manually.`, "error", 8000);
23620
+ showToast(ctx, `Opencode Dux ${latestVersion}`, `Update from v${currentVersion} to v${latestVersion} available, but auto-update failed to install it. Check logs or retry manually.`, "error", 8000);
23621
23621
  log("[auto-update-checker] bun install failed; update not installed");
23622
23622
  }
23623
23623
  }
@@ -24677,7 +24677,7 @@ function parseSnapshot(value) {
24677
24677
  return null;
24678
24678
  const activeSubscriptionByProvider = {};
24679
24679
  if (parsed.activeSubscriptionByProvider) {
24680
- for (const provider of ["opencode-go", "neuralwatt"]) {
24680
+ for (const provider of ["opencode-go", "neuralwatt", "codex"]) {
24681
24681
  const name = parsed.activeSubscriptionByProvider[provider];
24682
24682
  if (typeof name === "string" && name.length > 0) {
24683
24683
  activeSubscriptionByProvider[provider] = name;
@@ -26978,10 +26978,81 @@ function setAccountKey(name, provider, apiKey) {
26978
26978
  if (!account)
26979
26979
  return false;
26980
26980
  account.provider = provider;
26981
- account.apiKey = apiKey;
26981
+ if (account.provider === "codex") {
26982
+ account.accessToken = apiKey;
26983
+ } else {
26984
+ account.apiKey = apiKey;
26985
+ }
26982
26986
  writeAccountsFile(file);
26983
26987
  return true;
26984
26988
  }
26989
+ // src/subscriptions/codex-scraper.ts
26990
+ var CODEX_WHAM_URL = "https://chatgpt.com/backend-api/wham/usage";
26991
+ var EMPTY_WINDOW = {
26992
+ usagePercent: 0,
26993
+ percentRemaining: 100,
26994
+ resetInSec: 0,
26995
+ resetTimeIso: ""
26996
+ };
26997
+ var EMPTY_CREDITS = { hasCredits: false, unlimited: false, balance: 0 };
26998
+ function windowFromApi(w) {
26999
+ if (!w)
27000
+ return { ...EMPTY_WINDOW };
27001
+ const usagePercent = Math.max(0, Math.min(100, w.used_percent));
27002
+ return {
27003
+ usagePercent,
27004
+ percentRemaining: 100 - usagePercent,
27005
+ resetInSec: Math.max(0, w.limit_window_seconds),
27006
+ resetTimeIso: w.reset_at ? new Date(w.reset_at * 1000).toISOString() : ""
27007
+ };
27008
+ }
27009
+ async function scrapeCodexQuota(accessToken, signal) {
27010
+ const accountName = "";
27011
+ try {
27012
+ const res = await fetch(CODEX_WHAM_URL, {
27013
+ headers: {
27014
+ Authorization: `Bearer ${accessToken}`,
27015
+ Accept: "application/json"
27016
+ },
27017
+ signal
27018
+ });
27019
+ if (!res.ok) {
27020
+ return {
27021
+ provider: "codex",
27022
+ accountName,
27023
+ fetchedAt: Date.now(),
27024
+ error: `Codex API returned ${res.status} ${res.statusText}`,
27025
+ primaryWindow: { ...EMPTY_WINDOW },
27026
+ secondaryWindow: { ...EMPTY_WINDOW },
27027
+ credits: { ...EMPTY_CREDITS }
27028
+ };
27029
+ }
27030
+ const data = await res.json();
27031
+ return {
27032
+ provider: "codex",
27033
+ accountName,
27034
+ fetchedAt: Date.now(),
27035
+ primaryWindow: windowFromApi(data.rate_limit?.primary_window),
27036
+ secondaryWindow: windowFromApi(data.rate_limit?.secondary_window),
27037
+ credits: {
27038
+ hasCredits: data.credits?.has_credits ?? false,
27039
+ unlimited: data.credits?.unlimited ?? false,
27040
+ balance: data.credits?.balance ?? 0
27041
+ }
27042
+ };
27043
+ } catch (err) {
27044
+ const message = err instanceof Error ? err.message : String(err);
27045
+ return {
27046
+ provider: "codex",
27047
+ accountName,
27048
+ fetchedAt: Date.now(),
27049
+ error: `Codex fetch failed: ${message}`,
27050
+ primaryWindow: { ...EMPTY_WINDOW },
27051
+ secondaryWindow: { ...EMPTY_WINDOW },
27052
+ credits: { ...EMPTY_CREDITS }
27053
+ };
27054
+ }
27055
+ }
26985
27056
  // src/subscriptions/neuralwatt-scraper.ts
26986
27057
  var NEURALWATT_QUOTA_URL = "https://api.neuralwatt.com/v1/quota";
26987
27058
  var EMPTY_BALANCE = {
@@ -27166,9 +27237,9 @@ import * as path14 from "node:path";
27166
27237
  var SUBSCRIPTIONS_COMMAND = "subscriptions";
27167
27238
  var DEFAULT_REFRESH_INTERVAL_MS = 60000;
27168
27239
  var DEFAULT_PERIODIC_INTERVAL_MS = 600000;
27169
- var PROVIDERS = ["opencode-go", "neuralwatt"];
27240
+ var PROVIDERS = ["opencode-go", "neuralwatt", "codex"];
27170
27241
  function parseProvider(raw) {
27171
- if (raw === "opencode-go" || raw === "neuralwatt")
27242
+ if (raw === "opencode-go" || raw === "neuralwatt" || raw === "codex")
27172
27243
  return raw;
27173
27244
  return;
27174
27245
  }
@@ -27247,6 +27318,31 @@ class UsageService {
27247
27318
  const entry = await scrapeQuota(account.workspaceId, account.authCookie, controller.signal);
27248
27319
  entry.accountName = account.name;
27249
27320
  return entry;
27321
+ } else if (account.provider === "codex") {
27322
+ if (!account.accessToken?.trim()) {
27323
+ return {
27324
+ provider: "codex",
27325
+ accountName: account.name,
27326
+ fetchedAt: Date.now(),
27327
+ error: "Missing Codex access token. Re-add with /subscriptions add-codex.",
27328
+ primaryWindow: {
27329
+ usagePercent: 0,
27330
+ percentRemaining: 100,
27331
+ resetInSec: 0,
27332
+ resetTimeIso: ""
27333
+ },
27334
+ secondaryWindow: {
27335
+ usagePercent: 0,
27336
+ percentRemaining: 100,
27337
+ resetInSec: 0,
27338
+ resetTimeIso: ""
27339
+ },
27340
+ credits: { hasCredits: false, unlimited: false, balance: 0 }
27341
+ };
27342
+ }
27343
+ const entry = await scrapeCodexQuota(account.accessToken, controller.signal);
27344
+ entry.accountName = account.name;
27345
+ return entry;
27250
27346
  } else {
27251
27347
  if (!account.apiKey?.trim()) {
27252
27348
  return {
@@ -27299,7 +27395,13 @@ class UsageService {
27299
27395
  const accounts = this.getAccounts();
27300
27396
  for (const provider of PROVIDERS) {
27301
27397
  const key = auth[provider]?.key;
27302
- const match = typeof key === "string" && key.length > 0 ? accounts.find((account) => account.provider === provider && account.apiKey === key) : undefined;
27398
+ const match = typeof key === "string" && key.length > 0 ? accounts.find((account) => {
27399
+ if (account.provider !== provider)
27400
+ return false;
27401
+ if (account.provider === "codex")
27402
+ return account.accessToken === key;
27403
+ return account.apiKey === key;
27404
+ }) : undefined;
27303
27405
  if (match) {
27304
27406
  activeByProvider[provider] = match.name;
27305
27407
  recordActiveSubscriptionForProvider(provider, match.name);
@@ -27372,6 +27474,19 @@ class UsageService {
27372
27474
  output.parts.push(createInternalAgentTextPart(`✅ Added Neuralwatt account "${name}".`));
27373
27475
  break;
27374
27476
  }
27477
+ case "add-codex": {
27478
+ const [_, name, ...tokenParts] = parts;
27479
+ const accessToken = tokenParts.join(" ");
27480
+ if (!name || !accessToken) {
27481
+ output.parts.push(createInternalAgentTextPart(`Usage: /subscriptions add-codex <name> <access-token>
27482
+ ` + "Example: /subscriptions add-codex my-codex eyJhbGci..."));
27483
+ return;
27484
+ }
27485
+ saveAccount({ provider: "codex", name, accessToken });
27486
+ this.refresh(true).catch(() => {});
27487
+ output.parts.push(createInternalAgentTextPart(`✅ Added Codex account "${name}".`));
27488
+ break;
27489
+ }
27375
27490
  case "remove":
27376
27491
  case "rm": {
27377
27492
  const [_, name] = parts;
@@ -27417,18 +27532,20 @@ class UsageService {
27417
27532
  const accounts = this.getAccounts();
27418
27533
  const activeByProvider = this.syncActiveAccounts();
27419
27534
  if (accounts.length === 0) {
27420
- output.parts.push(createInternalAgentTextPart("No accounts configured. Use /subscriptions add-opencode-go or /subscriptions add-neuralwatt to add one."));
27535
+ output.parts.push(createInternalAgentTextPart("No accounts configured. Use /subscriptions add-opencode-go, /subscriptions add-neuralwatt, or /subscriptions add-codex to add one."));
27421
27536
  return;
27422
27537
  }
27423
27538
  const lines = ["### Subscription Accounts", ""];
27424
27539
  for (const acct of accounts) {
27425
27540
  const isActive = activeByProvider[acct.provider] === acct.name;
27426
27541
  const star = isActive ? "★ " : " ";
27427
- const providerLabel = acct.provider === "opencode-go" ? "OpenCode Go" : "Neuralwatt";
27542
+ const providerLabel = acct.provider === "opencode-go" ? "OpenCode Go" : acct.provider === "codex" ? "Codex" : "Neuralwatt";
27428
27543
  lines.push(`${star}${acct.name} (${providerLabel})`);
27429
27544
  if (acct.provider === "opencode-go") {
27430
27545
  lines.push(` workspace: ${acct.workspaceId}`);
27431
27546
  lines.push(` cookie: ${maskCookie(acct.authCookie)}`);
27547
+ } else if (acct.provider === "codex") {
27548
+ lines.push(` access-token: ${maskCookie(acct.accessToken)}`);
27432
27549
  } else {
27433
27550
  lines.push(` api-key: ${maskCookie(acct.apiKey)}`);
27434
27551
  }
@@ -27446,6 +27563,7 @@ class UsageService {
27446
27563
  lines.push("Commands:");
27447
27564
  lines.push(" /subscriptions add-opencode-go <name> <workspace-id> <auth-cookie>");
27448
27565
  lines.push(" /subscriptions add-neuralwatt <name> <api-key>");
27566
+ lines.push(" /subscriptions add-codex <name> <access-token>");
27449
27567
  lines.push(" /subscriptions remove <name>");
27450
27568
  lines.push(" /subscriptions edit <name> <new-auth-cookie>");
27451
27569
  lines.push(" /subscriptions set-key <name> <api-key>");
@@ -27480,7 +27598,7 @@ class UsageService {
27480
27598
  const provider = parseProvider(providerRaw);
27481
27599
  if (!provider || !name) {
27482
27600
  output.parts.push(createInternalAgentTextPart(`Usage: /subscriptions switch <provider> <name>
27483
- ` + `Providers: opencode-go, neuralwatt
27601
+ ` + `Providers: opencode-go, neuralwatt, codex
27484
27602
  ` + "Example: /subscriptions switch opencode-go personal"));
27485
27603
  return;
27486
27604
  }
@@ -27489,9 +27607,16 @@ class UsageService {
27489
27607
  output.parts.push(createInternalAgentTextPart(`Account "${name}" not found for provider "${provider}".`));
27490
27608
  return;
27491
27609
  }
27492
- if (!account.apiKey) {
27493
- output.parts.push(createInternalAgentTextPart(`Account "${name}" has no API key set. Use /subscriptions set-key ${name} <api-key> first.`));
27494
- return;
27610
+ if (account.provider === "codex") {
27611
+ if (!account.accessToken) {
27612
+ output.parts.push(createInternalAgentTextPart(`Account "${name}" has no access token set. Use /subscriptions add-codex to re-add.`));
27613
+ return;
27614
+ }
27615
+ } else {
27616
+ if (!account.apiKey) {
27617
+ output.parts.push(createInternalAgentTextPart(`Account "${name}" has no API key set. Use /subscriptions set-key ${name} <api-key> first.`));
27618
+ return;
27619
+ }
27495
27620
  }
27496
27621
  const activeByProvider = this.syncActiveAccounts();
27497
27622
  if (activeByProvider[account.provider] === name) {
@@ -27499,9 +27624,10 @@ class UsageService {
27499
27624
  return;
27500
27625
  }
27501
27626
  try {
27627
+ const key = account.provider === "codex" ? account.accessToken : account.apiKey ?? "";
27502
27628
  await this.client.auth.set({
27503
27629
  path: { id: account.provider },
27504
- body: { type: "api", key: account.apiKey }
27630
+ body: { type: "api", key }
27505
27631
  });
27506
27632
  } catch {
27507
27633
  output.parts.push(createInternalAgentTextPart("⚠ Failed to update auth. The key was not applied."));
@@ -27530,6 +27656,7 @@ class UsageService {
27530
27656
  ` + `Commands:
27531
27657
  ` + ` /subscriptions add-opencode-go <name> <workspace-id> <auth-cookie> Add an OpenCode Go account
27532
27658
  ` + ` /subscriptions add-neuralwatt <name> <api-key> Add a Neuralwatt account
27659
+ ` + ` /subscriptions add-codex <name> <access-token> Add a Codex account
27533
27660
  ` + ` /subscriptions remove <name> Remove an account
27534
27661
  ` + ` /subscriptions edit <name> <new-auth-cookie> Update auth cookie (OpenCode Go)
27535
27662
  ` + ` /subscriptions set-key <name> <api-key> Set API key for switching
@@ -27547,7 +27674,7 @@ class UsageService {
27547
27674
  }
27548
27675
  if (!configCommand?.[SUBSCRIPTIONS_COMMAND]) {
27549
27676
  opencodeConfig.command[SUBSCRIPTIONS_COMMAND] = {
27550
- template: "Manage subscription accounts (add-opencode-go, add-neuralwatt, remove, list, edit, set-key, switch, refresh)",
27677
+ template: "Manage subscription accounts (add-opencode-go, add-neuralwatt, add-codex, remove, list, edit, set-key, switch, refresh)",
27551
27678
  description: "Add, remove, list, edit, set-key, switch, or refresh subscription accounts for usage tracking in the sidebar"
27552
27679
  };
27553
27680
  }
@@ -0,0 +1,12 @@
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
+ import type { CodexUsageEntry } from './types';
9
+ /**
10
+ * Fetch Codex quota data via the WHAM usage API.
11
+ */
12
+ export declare function scrapeCodexQuota(accessToken: string, signal?: AbortSignal): Promise<CodexUsageEntry>;
@@ -7,7 +7,8 @@
7
7
  */
8
8
  export type { StoredAccount } from './accounts-store';
9
9
  export { getAccount, getAccountsByProvider, loadAccounts, loadAccountsResult, maskCookie, removeAccount, saveAccount, setAccountKey, updateAccountCookie, } from './accounts-store';
10
+ export { scrapeCodexQuota } from './codex-scraper';
10
11
  export { scrapeNeuralwattQuota } from './neuralwatt-scraper';
11
12
  export { scrapeQuota, scrapeUsagePage } from './opencode-go-scraper';
12
- export type { NeuralwattUsageEntry, OpenCodeGoUsageEntry, SubscriptionUsageEntry, UsageDetail, UsageWindow, } from './types';
13
+ export type { CodexUsageEntry, NeuralwattUsageEntry, OpenCodeGoUsageEntry, SubscriptionUsageEntry, UsageDetail, UsageWindow, } from './types';
13
14
  export { createUsageService, UsageService } from './usage-service';
@@ -5,7 +5,7 @@
5
5
  * as discriminated unions on the `provider` field.
6
6
  */
7
7
  /** Provider discriminator. */
8
- export type SubscriptionProvider = 'opencode-go' | 'neuralwatt';
8
+ export type SubscriptionProvider = 'opencode-go' | 'neuralwatt' | 'codex';
9
9
  export interface OpenCodeGoAccount {
10
10
  provider: 'opencode-go';
11
11
  name: string;
@@ -18,7 +18,13 @@ export interface NeuralwattAccount {
18
18
  name: string;
19
19
  apiKey: string;
20
20
  }
21
- export type StoredAccount = OpenCodeGoAccount | NeuralwattAccount;
21
+ export interface CodexAccount {
22
+ provider: 'codex';
23
+ name: string;
24
+ accessToken: string;
25
+ refreshToken?: string;
26
+ }
27
+ export type StoredAccount = OpenCodeGoAccount | NeuralwattAccount | CodexAccount;
22
28
  /** Per-time-window usage data scraped from the OpenCode Go dashboard. */
23
29
  export interface UsageWindow {
24
30
  /** Usage percentage [0..100] */
@@ -94,7 +100,23 @@ export interface NeuralwattUsageEntry {
94
100
  /** Error message if the fetch failed. */
95
101
  error?: string;
96
102
  }
97
- export type SubscriptionUsageEntry = OpenCodeGoUsageEntry | NeuralwattUsageEntry;
103
+ export interface CodexUsageEntry {
104
+ provider: 'codex';
105
+ accountName: string;
106
+ fetchedAt: number;
107
+ error?: string;
108
+ /** 5-hour rolling window (primary_window from API) */
109
+ primaryWindow: UsageWindow;
110
+ /** 7-day rolling window (secondary_window from API) */
111
+ secondaryWindow: UsageWindow;
112
+ /** Credit balance info */
113
+ credits: {
114
+ hasCredits: boolean;
115
+ unlimited: boolean;
116
+ balance: number;
117
+ };
118
+ }
119
+ export type SubscriptionUsageEntry = OpenCodeGoUsageEntry | NeuralwattUsageEntry | CodexUsageEntry;
98
120
  /** Detailed usage data from the /usage page. */
99
121
  export interface UsageDetail {
100
122
  /** Total number of API calls. */
package/dist/tui.js CHANGED
@@ -456,7 +456,7 @@ function parseSnapshot(value) {
456
456
  return null;
457
457
  const activeSubscriptionByProvider = {};
458
458
  if (parsed.activeSubscriptionByProvider) {
459
- for (const provider of ["opencode-go", "neuralwatt"]) {
459
+ for (const provider of ["opencode-go", "neuralwatt", "codex"]) {
460
460
  const name = parsed.activeSubscriptionByProvider[provider];
461
461
  if (typeof name === "string" && name.length > 0) {
462
462
  activeSubscriptionByProvider[provider] = name;
@@ -1291,6 +1291,44 @@ function renderNeuralwattUsage(entry, rows, theme) {
1291
1291
  pushNeuralwattMonthlyTokensRow(rows, theme, u);
1292
1292
  }
1293
1293
  }
1294
+ function renderCodexUsage(entry, rows, theme) {
1295
+ if (entry.primaryWindow) {
1296
+ const w = entry.primaryWindow;
1297
+ const usageColor = getUsageColor(w.percentRemaining);
1298
+ const bar = renderUsageBar(w.percentRemaining);
1299
+ const pct = w.percentRemaining.toFixed(0).padStart(3);
1300
+ const timeLeft = formatUsageTime(w.resetTimeIso);
1301
+ rows.push(box({ width: "100%", flexDirection: "row", justifyContent: "space-between" }, [
1302
+ box({ flexDirection: "row" }, [
1303
+ text({ fg: theme.accent }, ["5H "]),
1304
+ text({ fg: usageColor || theme.text }, [bar]),
1305
+ text({ fg: usageColor || theme.textMuted }, [` ${pct}%`])
1306
+ ]),
1307
+ text({ fg: theme.textMuted }, [timeLeft])
1308
+ ]));
1309
+ }
1310
+ if (entry.secondaryWindow) {
1311
+ const w = entry.secondaryWindow;
1312
+ const usageColor = getUsageColor(w.percentRemaining);
1313
+ const bar = renderUsageBar(w.percentRemaining);
1314
+ const pct = w.percentRemaining.toFixed(0).padStart(3);
1315
+ const timeLeft = formatUsageTime(w.resetTimeIso);
1316
+ rows.push(box({ width: "100%", flexDirection: "row", justifyContent: "space-between" }, [
1317
+ box({ flexDirection: "row" }, [
1318
+ text({ fg: theme.accent }, ["7D "]),
1319
+ text({ fg: usageColor || theme.text }, [bar]),
1320
+ text({ fg: usageColor || theme.textMuted }, [` ${pct}%`])
1321
+ ]),
1322
+ text({ fg: theme.textMuted }, [timeLeft])
1323
+ ]));
1324
+ }
1325
+ const balance = entry.credits.balance;
1326
+ rows.push(box({ width: "100%", flexDirection: "row" }, [
1327
+ text({ fg: theme.text }, [
1328
+ entry.credits.unlimited ? "\uD83D\uDCB0 Unlimited credits" : `\uD83D\uDCB0 $${balance.toFixed(2)} credits`
1329
+ ])
1330
+ ]));
1331
+ }
1294
1332
  function renderSubscriptionPanel(snapshot, theme) {
1295
1333
  const usage = snapshot.subscriptionUsage ?? {};
1296
1334
  const usageEntries = Object.entries(usage).sort(([, a], [, b]) => {
@@ -1306,7 +1344,7 @@ function renderSubscriptionPanel(snapshot, theme) {
1306
1344
  const name = entry.accountName;
1307
1345
  const activeName = snapshot.activeSubscriptionByProvider?.[entry.provider];
1308
1346
  const isActive = activeName === name;
1309
- const providerLabel = entry.provider === "neuralwatt" ? " [nw]" : " [go]";
1347
+ const providerLabel = entry.provider === "neuralwatt" ? " [nw]" : entry.provider === "codex" ? " [cx]" : " [go]";
1310
1348
  if (!isFirstAccount) {
1311
1349
  rows.push(box({ width: "100%", height: 1 }));
1312
1350
  }
@@ -1331,6 +1369,8 @@ function renderSubscriptionPanel(snapshot, theme) {
1331
1369
  renderOpenCodeGoBars(entry, rows, theme);
1332
1370
  } else if (entry.provider === "neuralwatt") {
1333
1371
  renderNeuralwattUsage(entry, rows, theme);
1372
+ } else if (entry.provider === "codex") {
1373
+ renderCodexUsage(entry, rows, theme);
1334
1374
  } else {
1335
1375
  rows.push(text({ fg: "#F39C12" }, [
1336
1376
  " ⚠️ Provider field missing - re-add account with /subscriptions"
@@ -1821,7 +1861,6 @@ function renderSidebar(snapshot, theme) {
1821
1861
  text({ fg: theme.accent }, ["opencode-dux"]),
1822
1862
  text({ fg: theme.accent }, [`v${PLUGIN_VERSION}`])
1823
1863
  ]),
1824
- box({ width: "100%", height: 1 }),
1825
1864
  box({
1826
1865
  width: "100%",
1827
1866
  flexDirection: "row",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-dux",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Agent orchestration, management, and operations plugin for OpenCode",
5
5
  "main": "index.ts",
6
6
  "types": "src/index.ts",
@@ -170,8 +170,8 @@ describe('auto-update-checker/index', () => {
170
170
  );
171
171
  expect(showToast).toHaveBeenCalledWith({
172
172
  body: {
173
- title: 'OMO-Slim Updated!',
174
- message: 'v0.9.1 v0.9.11\nRestart OpenCode to apply.',
173
+ title: 'Opencode Dux Updated!',
174
+ message: 'Updated from v0.9.1 to v0.9.11\nRestart OpenCode to apply.',
175
175
  variant: 'success',
176
176
  duration: 8000,
177
177
  },
@@ -199,8 +199,8 @@ describe('auto-update-checker/index', () => {
199
199
 
200
200
  expect(showToast).toHaveBeenCalledWith({
201
201
  body: {
202
- title: 'OMO-Slim 0.9.11',
203
- message: 'v0.9.11 available. Auto-update is disabled.',
202
+ title: 'Opencode Dux 0.9.11',
203
+ message: 'Update from v0.9.1 to v0.9.11 available. Auto-update is disabled.',
204
204
  variant: 'info',
205
205
  duration: 8000,
206
206
  },
@@ -230,9 +230,8 @@ describe('auto-update-checker/index', () => {
230
230
  expect(crossSpawnMock).not.toHaveBeenCalled();
231
231
  expect(showToast).toHaveBeenCalledWith({
232
232
  body: {
233
- title: 'OMO-Slim 0.9.11',
234
- message:
235
- 'v0.9.11 available. Auto-update could not prepare the active install.',
233
+ title: 'Opencode Dux 0.9.11',
234
+ message: 'Update from v0.9.1 to v0.9.11 available. Auto-update could not prepare the active install.',
236
235
  variant: 'info',
237
236
  duration: 8000,
238
237
  },
@@ -271,9 +270,8 @@ describe('auto-update-checker/index', () => {
271
270
  );
272
271
  expect(showToast).toHaveBeenCalledWith({
273
272
  body: {
274
- title: 'OMO-Slim 0.9.11',
275
- message:
276
- 'v0.9.11 available, but auto-update failed to install it. Check logs or retry manually.',
273
+ title: 'Opencode Dux 0.9.11',
274
+ message: 'Update from v0.9.1 to v0.9.11 available, but auto-update failed to install it. Check logs or retry manually.',
277
275
  variant: 'error',
278
276
  duration: 8000,
279
277
  },
@@ -101,8 +101,8 @@ async function runBackgroundUpdateCheck(
101
101
  if (pluginInfo.isPinned) {
102
102
  showToast(
103
103
  ctx,
104
- `OMO-Slim ${latestVersion}`,
105
- `v${latestVersion} available.\nVersion is pinned. Update your plugin config to apply.`,
104
+ `Opencode Dux ${latestVersion}`,
105
+ `Update from v${currentVersion} to v${latestVersion} available.\nVersion is pinned. Update your plugin config to apply.`,
106
106
  'info',
107
107
  8000,
108
108
  );
@@ -113,8 +113,8 @@ async function runBackgroundUpdateCheck(
113
113
  if (!autoUpdate) {
114
114
  showToast(
115
115
  ctx,
116
- `OMO-Slim ${latestVersion}`,
117
- `v${latestVersion} available. Auto-update is disabled.`,
116
+ `Opencode Dux ${latestVersion}`,
117
+ `Update from v${currentVersion} to v${latestVersion} available. Auto-update is disabled.`,
118
118
  'info',
119
119
  8000,
120
120
  );
@@ -126,8 +126,8 @@ async function runBackgroundUpdateCheck(
126
126
  if (!installDir) {
127
127
  showToast(
128
128
  ctx,
129
- `OMO-Slim ${latestVersion}`,
130
- `v${latestVersion} available. Auto-update could not prepare the active install.`,
129
+ `Opencode Dux ${latestVersion}`,
130
+ `Update from v${currentVersion} to v${latestVersion} available. Auto-update could not prepare the active install.`,
131
131
  'info',
132
132
  8000,
133
133
  );
@@ -140,8 +140,8 @@ async function runBackgroundUpdateCheck(
140
140
  if (installSuccess) {
141
141
  showToast(
142
142
  ctx,
143
- 'OMO-Slim Updated!',
144
- `v${currentVersion} v${latestVersion}\nRestart OpenCode to apply.`,
143
+ 'Opencode Dux Updated!',
144
+ `Updated from v${currentVersion} to v${latestVersion}\nRestart OpenCode to apply.`,
145
145
  'success',
146
146
  8000,
147
147
  );
@@ -151,8 +151,8 @@ async function runBackgroundUpdateCheck(
151
151
  } else {
152
152
  showToast(
153
153
  ctx,
154
- `OMO-Slim ${latestVersion}`,
155
- `v${latestVersion} available, but auto-update failed to install it. Check logs or retry manually.`,
154
+ `Opencode Dux ${latestVersion}`,
155
+ `Update from v${currentVersion} to v${latestVersion} available, but auto-update failed to install it. Check logs or retry manually.`,
156
156
  'error',
157
157
  8000,
158
158
  );
@@ -13,7 +13,13 @@
13
13
  import * as fs from 'node:fs';
14
14
  import * as os from 'node:os';
15
15
  import * as path from 'node:path';
16
- import type { StoredAccount, SubscriptionProvider } from './types';
16
+ import type {
17
+ CodexAccount,
18
+ NeuralwattAccount,
19
+ OpenCodeGoAccount,
20
+ StoredAccount,
21
+ SubscriptionProvider,
22
+ } from './types';
17
23
 
18
24
  // Re-export for consumers
19
25
  export type { StoredAccount };
@@ -178,7 +184,11 @@ export function setAccountKey(
178
184
  const account = file.accounts.find((a) => a.name === name);
179
185
  if (!account) return false;
180
186
  account.provider = provider as SubscriptionProvider;
181
- account.apiKey = apiKey;
187
+ if (account.provider === 'codex') {
188
+ (account as CodexAccount).accessToken = apiKey;
189
+ } else {
190
+ (account as OpenCodeGoAccount | NeuralwattAccount).apiKey = apiKey;
191
+ }
182
192
  writeAccountsFile(file);
183
193
  return true;
184
194
  }