@hienlh/ppm 0.19.3 → 0.19.5

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.
Files changed (92) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +54 -1
  3. package/assets/skills/ppm/SKILL.md +1 -1
  4. package/assets/skills/ppm/references/http-api.md +6 -1
  5. package/dist/web/assets/accounts-settings-section-CmLGNwUl.js +1 -0
  6. package/dist/web/assets/{ai-resource-editor-BqFT1EUB.js → ai-resource-editor-TVV8rOsT.js} +1 -1
  7. package/dist/web/assets/{ai-settings-section-CY6spbEx.js → ai-settings-section-CutZAI-J.js} +1 -1
  8. package/dist/web/assets/{ai-settings-section-Po3K8j-g.js → ai-settings-section-Whh5k2zL.js} +1 -1
  9. package/dist/web/assets/{appearance-settings-section-L9Z-nej_.js → appearance-settings-section-DbkXixUe.js} +1 -1
  10. package/dist/web/assets/chat-tab-BYuPaeUJ.js +15 -0
  11. package/dist/web/assets/code-editor-4Lk47HbX.js +7 -0
  12. package/dist/web/assets/{conflict-editor-BoRPwL3L.js → conflict-editor-B-HnPZX7.js} +1 -1
  13. package/dist/web/assets/{csv-preview-BpoJqZQi.js → csv-preview-CMIxkng3.js} +1 -1
  14. package/dist/web/assets/{database-viewer-I-9cER1L.js → database-viewer-CM6O4tar.js} +1 -1
  15. package/dist/web/assets/{diff-viewer-_xHZsxvr.js → diff-viewer-nL4cAp1u.js} +1 -1
  16. package/dist/web/assets/explorer-body-CuLBY282.js +2 -0
  17. package/dist/web/assets/{explorer-window-content-DD6Z15pm.js → explorer-window-content-Cr8HYEIZ.js} +1 -1
  18. package/dist/web/assets/{extension-manager-section-Cu9D9Udt.js → extension-manager-section-D_E1LteV.js} +1 -1
  19. package/dist/web/assets/{extension-webview-Diz2OzVq.js → extension-webview-BKVWrl6R.js} +1 -1
  20. package/dist/web/assets/{files-settings-section-B1e7BgKQ.js → files-settings-section-DzuWb19a.js} +1 -1
  21. package/dist/web/assets/{general-settings-section-DWdHHRDy.js → general-settings-section-c096J38r.js} +1 -1
  22. package/dist/web/assets/{git-log-panel-CMMsvU64.js → git-log-panel-DRLGWrO2.js} +1 -1
  23. package/dist/web/assets/{glide-data-grid-pKQuyYFj.js → glide-data-grid-CUtOJQyB.js} +11 -11
  24. package/dist/web/assets/{group-chat-tab-U5u0Loii.js → group-chat-tab-Cano41wo.js} +1 -1
  25. package/dist/web/assets/{index--KWVW-Ga.js → index-C_s5DxFQ.js} +4 -4
  26. package/dist/web/assets/index-nP3uxXvR.css +2 -0
  27. package/dist/web/assets/{jira-watcher-section-uA5oyB8s.js → jira-watcher-section-B2-_4QW_.js} +1 -1
  28. package/dist/web/assets/{keybindings-store-Ccf0etMJ.js → keybindings-store-ehg0D6FR.js} +1 -1
  29. package/dist/web/assets/{keyboard-shortcuts-section-B7IxJHiI.js → keyboard-shortcuts-section-DMVcFwLJ.js} +1 -1
  30. package/dist/web/assets/{markdown-renderer-CY1epGRc.js → markdown-renderer-CKIBtz-k.js} +1 -1
  31. package/dist/web/assets/{markdown-renderer-DDcIGQEd.js → markdown-renderer-pDOFHEG7.js} +1 -1
  32. package/dist/web/assets/{mobile-explorer-sheet-CqkwbCRl.js → mobile-explorer-sheet-DFGPwNp0.js} +1 -1
  33. package/dist/web/assets/{notification-store-BNSYk3E7.js → notification-store-CwLJtN2t.js} +1 -1
  34. package/dist/web/assets/{notifications-settings-section-v_628h4B.js → notifications-settings-section-C66s2JId.js} +1 -1
  35. package/dist/web/assets/{pdf-preview-CI3q1MnH.js → pdf-preview-0AwTBfeC.js} +1 -1
  36. package/dist/web/assets/{postgres-viewer-jaR0FYJ_.js → postgres-viewer-hDHYMHmG.js} +2 -2
  37. package/dist/web/assets/{ppmbot-settings-section-BET4VmKb.js → ppmbot-settings-section-DV6TMOfs.js} +1 -1
  38. package/dist/web/assets/{process-table-D1r-Gupi.js → process-table-CmvwqGrQ.js} +1 -1
  39. package/dist/web/assets/proxy-settings-section-DjtXwOV_.js +1 -0
  40. package/dist/web/assets/{query-audit-section-BA5rT6Sh.js → query-audit-section-BgXdBaE1.js} +1 -1
  41. package/dist/web/assets/{remote-desktop-mobile-sheet-CPa3XElX.js → remote-desktop-mobile-sheet-Cakf7i6s.js} +2 -2
  42. package/dist/web/assets/{remote-desktop-mobile-view-De_a4skd.js → remote-desktop-mobile-view-DO5NJufM.js} +1 -1
  43. package/dist/web/assets/{remote-desktop-readiness-gate-CPrDIv2d.js → remote-desktop-readiness-gate-CODu8fsP.js} +1 -1
  44. package/dist/web/assets/{remote-desktop-window-content-CG1StfCE.js → remote-desktop-window-content-DVhp7Y9T.js} +1 -1
  45. package/dist/web/assets/{schedules-settings-section-B2f7V7Xr.js → schedules-settings-section-IfwILuXE.js} +1 -1
  46. package/dist/web/assets/{settings-body-omv04jbb.js → settings-body-NYJVObzu.js} +2 -2
  47. package/dist/web/assets/{settings-tab-NdnBTqyj.js → settings-tab-CPOI69SL.js} +1 -1
  48. package/dist/web/assets/{settings-window-content-Chou1JGa.js → settings-window-content-BuuLt4qg.js} +1 -1
  49. package/dist/web/assets/{sql-query-editor-Dk9y3jqy.js → sql-query-editor-BSwL4jf9.js} +1 -1
  50. package/dist/web/assets/{sqlite-viewer-4xFhkLYy.js → sqlite-viewer-Cy-H-h03.js} +1 -1
  51. package/dist/web/assets/{system-monitor-body-B_1iMVOI.js → system-monitor-body-B3jG3f5P.js} +2 -2
  52. package/dist/web/assets/{system-monitor-tab-B2j9mWBW.js → system-monitor-tab-Dt4wtPR0.js} +1 -1
  53. package/dist/web/assets/{system-monitor-window-content-BOjq53uU.js → system-monitor-window-content-5Kkti6yp.js} +1 -1
  54. package/dist/web/assets/{tab-host-window-content-CW_dlYlW.js → tab-host-window-content-BnwmQM0I.js} +1 -1
  55. package/dist/web/assets/{team-member-sheet-CkR4qO7V.js → team-member-sheet-B4TEEtVT.js} +2 -2
  56. package/dist/web/assets/{team-member-window-content-CmF5ia3n.js → team-member-window-content-CHevmy9F.js} +1 -1
  57. package/dist/web/assets/{terminal-tab-HXZkicIc.js → terminal-tab-DVb1AfrI.js} +2 -2
  58. package/dist/web/assets/{tool-cards-jA1BTLZj.js → tool-cards-DIF_JcFj.js} +4 -4
  59. package/dist/web/assets/use-accounts-data--JHaCIoT.js +1 -0
  60. package/dist/web/assets/{use-monaco-theme-BcQqKVbE.js → use-monaco-theme-xqDIVJcg.js} +1 -1
  61. package/dist/web/assets/{use-remote-desktop-display-choice-DY5BWRTo.js → use-remote-desktop-display-choice-DJFX_arw.js} +1 -1
  62. package/dist/web/assets/{use-websocket-DetaY8OI.js → use-websocket-DufdN3ZA.js} +1 -1
  63. package/dist/web/assets/{video-preview-C0uH2228.js → video-preview-DExfNDxI.js} +1 -1
  64. package/dist/web/index.html +2 -2
  65. package/dist/web/sw.js +1 -1
  66. package/package.json +1 -1
  67. package/src/providers/codex-app-server/codex-event-mapper.ts +42 -1
  68. package/src/providers/codex-app-server/codex-provider.ts +22 -4
  69. package/src/server/routes/proxy.ts +60 -0
  70. package/src/services/codex-account.service.ts +4 -2
  71. package/src/services/prod-db-guard.ts +98 -0
  72. package/src/services/proxy-agent-anthropic-bridge.ts +104 -0
  73. package/src/services/proxy-agent-bridge.ts +147 -0
  74. package/src/services/proxy-agent-turn.ts +172 -0
  75. package/src/services/proxy-anthropic-format.ts +157 -0
  76. package/src/services/proxy-image-bridge.ts +186 -0
  77. package/src/services/proxy-openai-bridge.ts +4 -38
  78. package/src/services/proxy-openai-format.ts +174 -0
  79. package/src/services/proxy.service.ts +54 -0
  80. package/src/web/components/chat/usage-badge.tsx +41 -4
  81. package/src/web/components/settings/accounts/account-card.tsx +27 -3
  82. package/src/web/components/settings/accounts/accounts-pane-header.tsx +22 -2
  83. package/src/web/components/settings/accounts/claude-accounts-section.tsx +4 -3
  84. package/src/web/components/settings/accounts/codex-accounts-section.tsx +11 -4
  85. package/src/web/components/settings/proxy-settings-section.tsx +55 -23
  86. package/dist/web/assets/accounts-settings-section-LtSVxNNp.js +0 -1
  87. package/dist/web/assets/chat-tab-BIO-VhWT.js +0 -15
  88. package/dist/web/assets/code-editor-B9lVjElM.js +0 -7
  89. package/dist/web/assets/explorer-body-DapwsOnp.js +0 -2
  90. package/dist/web/assets/index-CvGx7wBQ.css +0 -2
  91. package/dist/web/assets/proxy-settings-section-0DvNh1B-.js +0 -3
  92. package/dist/web/assets/use-accounts-data-BI_5qNvr.js +0 -1
@@ -65,23 +65,43 @@ export function AccountsPaneMessage({ message, onDismiss }: {
65
65
  );
66
66
  }
67
67
 
68
+ /**
69
+ * The row accounts are laid out in: a sideways scroller, one card per account.
70
+ *
71
+ * Horizontal everywhere — the chat panel and both settings panes — because the point of the
72
+ * list is comparing accounts, and stacked vertically you scroll past one to reach the next.
73
+ * Shared so the three callers cannot end up scrolling in different directions.
74
+ */
75
+ export function AccountCardRow({ children }: { children: React.ReactNode }) {
76
+ return (
77
+ // Negative margin then padding: cards scroll edge to edge while still clearing the
78
+ // pane's own padding at rest. Snap so a swipe lands on a card rather than between two.
79
+ <div className="flex gap-2 overflow-x-auto pb-1 -mx-1 px-1 snap-x snap-mandatory scrollbar-thin">
80
+ {children}
81
+ </div>
82
+ );
83
+ }
84
+
68
85
  /**
69
86
  * The card one account is drawn in.
70
87
  *
71
88
  * Shared so a Claude row and a Codex row are the same object on screen even though what goes
72
89
  * inside them differs.
73
90
  */
74
- export function AccountCardShell({ active, flash, children, ...rest }: {
91
+ export function AccountCardShell({ active, flash, dense, children, ...rest }: {
75
92
  active?: boolean;
76
93
  /** Brief highlight when this account's numbers just changed. */
77
94
  flash?: boolean;
95
+ /** Tighter padding, for a card that has to fit a fixed narrow width. */
96
+ dense?: boolean;
78
97
  children: React.ReactNode;
79
98
  } & React.HTMLAttributes<HTMLDivElement>) {
80
99
  return (
81
100
  <div
82
101
  {...rest}
83
102
  className={[
84
- "rounded-md border p-3 space-y-2 transition-colors duration-500",
103
+ "rounded-md border transition-colors duration-500",
104
+ dense ? "p-2.5 space-y-1.5" : "p-3 space-y-2",
85
105
  flash ? "bg-primary/10 border-primary/40" : "",
86
106
  active ? "border-primary/30 bg-primary/5" : "border-border/50",
87
107
  rest.className ?? "",
@@ -10,7 +10,7 @@
10
10
  import { useState } from "react";
11
11
  import { Download, KeyRound, Loader2, Plus, Settings, Upload, FlaskConical } from "lucide-react";
12
12
  import { Button } from "@/components/ui/button";
13
- import { AccountsPaneHeader, AccountsPaneMessage } from "./accounts-pane-header";
13
+ import { AccountCardRow, AccountsPaneHeader, AccountsPaneMessage } from "./accounts-pane-header";
14
14
  import { patchAccount, deleteAccount, type OAuthProfileData } from "../../../lib/api-settings";
15
15
  import { AccountProfilePanel } from "./account-profile-panel";
16
16
  import { AccountDeleteConfirm } from "./account-delete-confirm";
@@ -117,9 +117,10 @@ export function ClaudeAccountsSection() {
117
117
  <p className="text-xs text-muted-foreground">Add one to start using PPM.</p>
118
118
  </div>
119
119
  ) : (
120
- <div className="space-y-2">
120
+ <AccountCardRow>
121
121
  {usages.map((entry) => (
122
122
  <AccountCard
123
+ layout="strip"
123
124
  key={entry.accountId}
124
125
  entry={entry}
125
126
  isActive={entry.accountId === activeAccountId}
@@ -132,7 +133,7 @@ export function ClaudeAccountsSection() {
132
133
  flash={flashIds.has(entry.accountId)}
133
134
  />
134
135
  ))}
135
- </div>
136
+ </AccountCardRow>
136
137
  )}
137
138
  </section>
138
139
 
@@ -9,7 +9,7 @@
9
9
  import { useState } from "react";
10
10
  import { Download, KeyRound, Loader2, Plus, Settings, Trash2, Upload } from "lucide-react";
11
11
  import { Button } from "@/components/ui/button";
12
- import { AccountCardShell, AccountsPaneHeader, AccountsPaneMessage } from "./accounts-pane-header";
12
+ import { AccountCardRow, AccountCardShell, AccountsPaneHeader, AccountsPaneMessage } from "./accounts-pane-header";
13
13
  import { AccountUsageBar } from "./account-bucket-row";
14
14
  import { CodexAddAccountDialog } from "./codex-add-account-dialog";
15
15
  import { CodexBackupDialog } from "./codex-backup-dialog";
@@ -70,11 +70,18 @@ export function CodexAccountsSection() {
70
70
  </p>
71
71
  </div>
72
72
  ) : (
73
- <div className="space-y-2">
73
+ <AccountCardRow>
74
74
  {c.accounts.map((a) => {
75
75
  const u = c.usages[a.id] ?? {};
76
76
  return (
77
- <AccountCardShell key={a.id} data-testid="account-card" data-account-id={a.id}>
77
+ <AccountCardShell
78
+ key={a.id}
79
+ dense
80
+ // Matches the Claude card that also carries a control beside the name.
81
+ className="min-w-[300px] shrink-0 snap-start"
82
+ data-testid="account-card"
83
+ data-account-id={a.id}
84
+ >
78
85
  <div className="flex items-center gap-2">
79
86
  <span className="text-sm font-medium truncate flex-1 min-w-0">{a.label}</span>
80
87
  <span className="text-[10px] uppercase tracking-wide text-text-subtle border border-border rounded px-1 shrink-0">
@@ -98,7 +105,7 @@ export function CodexAccountsSection() {
98
105
  </AccountCardShell>
99
106
  );
100
107
  })}
101
- </div>
108
+ </AccountCardRow>
102
109
  )}
103
110
  </section>
104
111
 
@@ -4,11 +4,22 @@ import { Button } from "@/components/ui/button";
4
4
  import { Input } from "@/components/ui/input";
5
5
  import { Label } from "@/components/ui/label";
6
6
  import { Switch } from "@/components/ui/switch";
7
- import { getProxySettings, updateProxySettings, type ProxySettings } from "@/lib/api-settings";
7
+ import { getProxySettings, updateProxySettings, getAISettings, type ProxySettings } from "@/lib/api-settings";
8
8
  import { copyToClipboard } from "@/lib/clipboard";
9
9
  import { ProxyTestButton } from "./proxy-test-section";
10
10
 
11
11
  export function ProxySettingsSection() {
12
+ /** Provider the connection info targets; "" is the unscoped Claude path. */
13
+ const [provider, setProvider] = useState("");
14
+ /** Providers reachable as agents, minus the internal mock. */
15
+ const [agentProviders, setAgentProviders] = useState<string[]>([]);
16
+
17
+ useEffect(() => {
18
+ getAISettings()
19
+ .then((s) => setAgentProviders(Object.keys(s.providers ?? {}).filter((id) => id !== "mock" && id !== "claude")))
20
+ .catch(() => setAgentProviders([]));
21
+ }, []);
22
+
12
23
  const [settings, setSettings] = useState<ProxySettings | null>(null);
13
24
  const [loading, setLoading] = useState(true);
14
25
  const [saving, setSaving] = useState(false);
@@ -59,6 +70,16 @@ export function ProxySettingsSection() {
59
70
  const localEndpoint = settings.localEndpoint;
60
71
  const localBaseUrl = localEndpoint.replace(/\/proxy\/v1\/messages$/, "");
61
72
 
73
+ // Both dialects hang off one prefix, so selecting a provider moves them
74
+ // together and the card cannot show a mismatched pair.
75
+ // default: <root>/proxy → /v1/messages, /v1/chat/completions
76
+ // provider: <root>/proxy/codex → same two paths underneath
77
+ const prefix = `${hasTunnel ? settings.tunnelUrl : localBaseUrl}/proxy${provider ? `/${provider}` : ""}`;
78
+ const anthropicEndpoint = `${prefix}/v1/messages`;
79
+ const openAiEndpoint = `${prefix}/v1/chat/completions`;
80
+ const anthropicEnv = `ANTHROPIC_BASE_URL=${prefix}\nANTHROPIC_API_KEY=${settings.authKey}`;
81
+ const openAiEnv = `OPENAI_BASE_URL=${prefix}/v1\nOPENAI_API_KEY=${settings.authKey}`;
82
+
62
83
  return (
63
84
  <div className="space-y-4">
64
85
  <div className="space-y-1.5">
@@ -135,18 +156,43 @@ export function ProxySettingsSection() {
135
156
  <ProxyTestButton authKey={settings.authKey!} baseUrl={window.location.origin} />
136
157
  </div>
137
158
 
159
+ {/* Target provider — the only thing that differs between the two
160
+ dialects below, so both are derived from one prefix. */}
161
+ <div className="space-y-1">
162
+ <Label className="text-[10px] text-muted-foreground">Provider</Label>
163
+ <div className="flex flex-wrap gap-1.5">
164
+ {[{ id: "", label: "Claude (default)" }, ...agentProviders.map((id) => ({ id, label: id }))].map((p) => (
165
+ <button
166
+ key={p.id || "default"}
167
+ type="button"
168
+ onClick={() => setProvider(p.id)}
169
+ className={`text-[10px] px-2.5 min-h-[36px] rounded-md border transition-colors cursor-pointer ${
170
+ provider === p.id ? "border-primary bg-primary/15" : "border-border bg-muted/40 hover:bg-muted"
171
+ }`}
172
+ >
173
+ {p.label}
174
+ </button>
175
+ ))}
176
+ </div>
177
+ {provider && (
178
+ <p className="text-[10px] text-muted-foreground">
179
+ Runs the {provider} agent per request (read-only sandbox, one turn). Slower than the default path.
180
+ </p>
181
+ )}
182
+ </div>
183
+
138
184
  {/* Anthropic endpoint */}
139
185
  <div className="space-y-1">
140
186
  <Label className="text-[10px] text-muted-foreground">Anthropic Endpoint</Label>
141
187
  <div className="flex gap-1.5 items-center">
142
188
  <code className="text-[10px] font-mono bg-muted px-1.5 py-0.5 rounded flex-1 truncate">
143
- {hasTunnel ? settings.proxyEndpoint : localEndpoint}
189
+ {anthropicEndpoint}
144
190
  </code>
145
191
  <Button
146
192
  variant="ghost"
147
193
  size="sm"
148
194
  className="h-6 px-1.5 cursor-pointer shrink-0"
149
- onClick={() => handleCopy(hasTunnel ? settings.proxyEndpoint! : localEndpoint, "anthropic")}
195
+ onClick={() => handleCopy(anthropicEndpoint, "anthropic")}
150
196
  >
151
197
  {copied === "anthropic" ? "Copied!" : <Copy className="size-3" />}
152
198
  </Button>
@@ -158,15 +204,13 @@ export function ProxySettingsSection() {
158
204
  <Label className="text-[10px] text-muted-foreground">OpenAI-Compatible Endpoint</Label>
159
205
  <div className="flex gap-1.5 items-center">
160
206
  <code className="text-[10px] font-mono bg-muted px-1.5 py-0.5 rounded flex-1 truncate">
161
- {hasTunnel ? settings.openAiEndpoint : settings.localOpenAiEndpoint}
207
+ {openAiEndpoint}
162
208
  </code>
163
209
  <Button
164
210
  variant="ghost"
165
211
  size="sm"
166
212
  className="h-6 px-1.5 cursor-pointer shrink-0"
167
- onClick={() => handleCopy(
168
- hasTunnel ? settings.openAiEndpoint! : settings.localOpenAiEndpoint, "openai",
169
- )}
213
+ onClick={() => handleCopy(openAiEndpoint, "openai")}
170
214
  >
171
215
  {copied === "openai" ? "Copied!" : <Copy className="size-3" />}
172
216
  </Button>
@@ -183,18 +227,12 @@ export function ProxySettingsSection() {
183
227
  <div className="space-y-1 pt-1">
184
228
  <Label className="text-[10px] text-muted-foreground">Anthropic Format</Label>
185
229
  <div className="relative">
186
- <pre className="text-[9px] font-mono bg-muted p-2 rounded overflow-x-auto whitespace-pre">
187
- {`ANTHROPIC_BASE_URL=${hasTunnel ? settings.tunnelUrl + "/proxy" : localBaseUrl + "/proxy"}
188
- ANTHROPIC_API_KEY=${settings.authKey}`}
189
- </pre>
230
+ <pre className="text-[9px] font-mono bg-muted p-2 rounded overflow-x-auto whitespace-pre">{anthropicEnv}</pre>
190
231
  <Button
191
232
  variant="ghost"
192
233
  size="sm"
193
234
  className="absolute top-1 right-1 h-5 px-1 cursor-pointer"
194
- onClick={() => handleCopy(
195
- `ANTHROPIC_BASE_URL=${hasTunnel ? settings.tunnelUrl + "/proxy" : localBaseUrl + "/proxy"}\nANTHROPIC_API_KEY=${settings.authKey}`,
196
- "anthropic-env",
197
- )}
235
+ onClick={() => handleCopy(anthropicEnv, "anthropic-env")}
198
236
  >
199
237
  {copied === "anthropic-env" ? "Copied!" : <Copy className="size-2.5" />}
200
238
  </Button>
@@ -204,18 +242,12 @@ ANTHROPIC_API_KEY=${settings.authKey}`}
204
242
  <div className="space-y-1">
205
243
  <Label className="text-[10px] text-muted-foreground">OpenAI Format</Label>
206
244
  <div className="relative">
207
- <pre className="text-[9px] font-mono bg-muted p-2 rounded overflow-x-auto whitespace-pre">
208
- {`OPENAI_BASE_URL=${hasTunnel ? settings.tunnelUrl + "/proxy/v1" : localBaseUrl + "/proxy/v1"}
209
- OPENAI_API_KEY=${settings.authKey}`}
210
- </pre>
245
+ <pre className="text-[9px] font-mono bg-muted p-2 rounded overflow-x-auto whitespace-pre">{openAiEnv}</pre>
211
246
  <Button
212
247
  variant="ghost"
213
248
  size="sm"
214
249
  className="absolute top-1 right-1 h-5 px-1 cursor-pointer"
215
- onClick={() => handleCopy(
216
- `OPENAI_BASE_URL=${hasTunnel ? settings.tunnelUrl + "/proxy/v1" : localBaseUrl + "/proxy/v1"}\nOPENAI_API_KEY=${settings.authKey}`,
217
- "openai-env",
218
- )}
250
+ onClick={() => handleCopy(openAiEnv, "openai-env")}
219
251
  >
220
252
  {copied === "openai-env" ? "Copied!" : <Copy className="size-2.5" />}
221
253
  </Button>
@@ -1 +0,0 @@
1
- import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{S as t,x as n}from"./vendor-markdown-Ls5AcU91.js";import"./vendor-ui-CaeeFO0B.js";import{t as r}from"./copy-BewHj66E.js";import{t as i}from"./download-DG3_9xXy.js";import{t as a}from"./flask-conical-tp8gyZGh.js";import{t as o}from"./key-round-CQpDrDxg.js";import{t as s}from"./loader-circle-DPMrnLQ2.js";import{t as c}from"./x-YDu4m2Iq.js";import{n as l,r as u}from"./api-client-oY1tC3tM.js";import{n as d}from"./utils-C9OjCrou.js";import"./portal-container-context-a399Hd9F.js";import{n as f}from"./use-is-mobile-CYW3FJFE.js";import{n as p,t as m}from"./button-BZtEJcUv.js";import{t as h}from"./mobile-bottom-sheet-BD5b7KJF.js";import"./use-visual-viewport-rnvuooQZ.js";import{$t as g,B as _,H as v,J as y,Jn as b,Jt as x,Pn as S,Qt as C,U as w,V as T,Vn as E,W as D,Xt as O,Y as k,Yt as A,Zt as j,er as M,j as N,jn as P,rr as F,tn as I,xr as L,z as R}from"./index--KWVW-Ga.js";import{addAccount as ee,deleteAccount as te,exchangeOAuthCode as ne,getAISettings as re,getAccountSettings as ie,getOAuthUrl as ae,getUsageHistory as oe,importAccounts as se,patchAccount as ce,testAccountToken as le,testExport as ue,testRawToken as de,updateAccountSettings as z}from"./api-settings-CTMdz_VF.js";import{a as B,c as V,i as H,n as fe,r as pe,s as U,t as me}from"./use-accounts-data-BI_5qNvr.js";var W=e(t(),1),G=n(),K=[`Mon`,`Tue`,`Wed`,`Thu`,`Fri`,`Sat`,`Sun`],q=Array.from({length:24},(e,t)=>t);function he(e,t){let n=Array.from({length:7},()=>Array.from({length:24},()=>({sum:0,count:0,avg:0})));for(let r of e){let e=t===`5h`?r.five_hour_util:r.weekly_util;if(e==null)continue;let i=new Date(r.recorded_at+(r.recorded_at.endsWith(`Z`)?``:`Z`)),a=(i.getDay()+6)%7,o=i.getHours();n[a][o].sum+=e,n[a][o].count+=1}for(let e of n)for(let t of e)t.avg=t.count>0?t.sum/t.count:0;return n}function ge(e){return e.map(e=>{let t=e.reduce((e,t)=>e+t.sum,0),n=e.reduce((e,t)=>e+t.count,0);return n>0?t/n:0})}function _e(e){return q.map(t=>{let n=0,r=0;for(let i of e)n+=i[t].sum,r+=i[t].count;return r>0?n/r:0})}function J(e){return e===0?`bg-surface-elevated`:e<.3?`bg-success/30`:e<.5?`bg-success/60`:e<.7?`bg-warning/50`:e<.9?`bg-warning/80`:`bg-error/80`}function ve(e){return e<.3?`bg-success`:e<.7?`bg-warning`:`bg-error`}function ye({accountId:e}){let[t,n]=(0,W.useState)(null),[r,i]=(0,W.useState)(!0),[a,o]=(0,W.useState)(`5h`);(0,W.useEffect)(()=>{i(!0),oe(e).then(n).catch(()=>n([])).finally(()=>i(!1))},[e]);let c=(0,W.useMemo)(()=>t?he(t,a):null,[t,a]),l=(0,W.useMemo)(()=>c?ge(c):[],[c]),u=(0,W.useMemo)(()=>c?_e(c):[],[c]),d=Math.max(...l,.01);if(Math.max(...u,.01),r)return(0,G.jsx)(`div`,{className:`flex items-center justify-center py-3`,children:(0,G.jsx)(s,{className:`size-3 animate-spin text-text-subtle`})});if(!t||t.length===0)return(0,G.jsx)(`div`,{className:`text-[10px] text-text-subtle py-2 text-center`,children:`No usage history yet`});let f=t.length,p=new Set(t.map(e=>new Date(e.recorded_at+(e.recorded_at.endsWith(`Z`)?``:`Z`)).toDateString())).size;return(0,G.jsxs)(`div`,{className:`mt-2 space-y-2`,children:[(0,G.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,G.jsx)(`span`,{className:`text-[10px] font-medium text-text-subtle`,children:`Usage Pattern (7d)`}),(0,G.jsxs)(`div`,{className:`flex gap-0.5 text-[9px]`,children:[(0,G.jsx)(`button`,{onClick:()=>o(`5h`),className:`px-1.5 py-0.5 rounded cursor-pointer transition-colors ${a===`5h`?`bg-primary/15 text-primary`:`text-text-subtle hover:text-text-secondary`}`,title:`5-hour rolling window limit — resets every 5 hours`,children:`5h`}),(0,G.jsx)(`button`,{onClick:()=>o(`weekly`),className:`px-1.5 py-0.5 rounded cursor-pointer transition-colors ${a===`weekly`?`bg-primary/15 text-primary`:`text-text-subtle hover:text-text-secondary`}`,title:`Weekly limit — resets every 7 days`,children:`Wk`})]})]}),(0,G.jsxs)(`p`,{className:`text-[9px] text-text-subtle leading-tight`,children:[`Avg `,a===`5h`?`5-hour`:`weekly`,` limit usage over `,p,`d (`,f,` samples). Higher % = closer to rate limit. Hover cells for details.`]}),(0,G.jsxs)(`div`,{children:[(0,G.jsx)(`span`,{className:`text-[9px] text-text-subtle`,children:`Avg usage by day of week`}),(0,G.jsx)(`div`,{className:`flex flex-col gap-[2px] mt-0.5`,children:K.map((e,t)=>{let n=l[t]??0;return(0,G.jsxs)(`div`,{className:`flex items-center gap-1`,children:[(0,G.jsx)(`span`,{className:`text-[8px] text-text-subtle w-5 shrink-0 text-right tabular-nums`,children:e}),(0,G.jsx)(`div`,{className:`flex-1 h-2.5 bg-surface-elevated rounded-sm overflow-hidden`,children:(0,G.jsx)(`div`,{className:`h-full rounded-sm transition-all ${ve(n)}`,style:{width:`${Math.round(n/d*100)}%`}})}),(0,G.jsxs)(`span`,{className:`text-[8px] text-text-subtle w-6 shrink-0 text-right tabular-nums`,children:[Math.round(n*100),`%`]})]},e)})})]}),(0,G.jsxs)(`div`,{children:[(0,G.jsx)(`span`,{className:`text-[9px] text-text-subtle`,children:`Avg usage by hour (0h-23h)`}),(0,G.jsx)(`div`,{className:`flex gap-[1px] mt-0.5`,children:q.map(e=>{let t=u[e]??0;return(0,G.jsxs)(`div`,{className:`flex-1 flex flex-col items-center gap-[1px]`,children:[(0,G.jsx)(`div`,{className:`w-full aspect-square rounded-[2px] ${J(t)}`,title:`${e}:00 — avg ${Math.round(t*100)}% usage`}),e%6==0&&(0,G.jsx)(`span`,{className:`text-[7px] text-text-subtle tabular-nums`,children:e})]},e)})})]}),c&&(0,G.jsxs)(`div`,{children:[(0,G.jsx)(`span`,{className:`text-[9px] text-text-subtle`,children:`Day x Hour heatmap`}),(0,G.jsxs)(`div`,{className:`flex flex-col gap-[1px] mt-0.5`,children:[K.map((e,t)=>(0,G.jsxs)(`div`,{className:`flex items-center gap-[1px]`,children:[(0,G.jsx)(`span`,{className:`text-[7px] text-text-subtle w-4 shrink-0 text-right`,children:e.charAt(0)}),q.map(n=>{let r=c[t][n];return(0,G.jsx)(`div`,{className:`flex-1 aspect-square rounded-[1px] ${J(r.avg)}`,title:`${e} ${n}:00 — ${r.count>0?`avg ${Math.round(r.avg*100)}% (${r.count} samples)`:`no data`}`},n)})]},e)),(0,G.jsxs)(`div`,{className:`flex items-center gap-[1px]`,children:[(0,G.jsx)(`span`,{className:`w-4 shrink-0`}),q.map(e=>(0,G.jsx)(`div`,{className:`flex-1 text-center`,children:e%6==0&&(0,G.jsx)(`span`,{className:`text-[7px] text-text-subtle tabular-nums`,children:e})},e))]})]})]}),(0,G.jsxs)(`div`,{className:`flex items-center gap-1.5 text-[8px] text-text-subtle`,children:[(0,G.jsx)(`span`,{children:`Low`}),(0,G.jsxs)(`div`,{className:`flex gap-[2px]`,children:[(0,G.jsx)(`div`,{className:`size-2 rounded-[1px] bg-success/30`}),(0,G.jsx)(`div`,{className:`size-2 rounded-[1px] bg-success/60`}),(0,G.jsx)(`div`,{className:`size-2 rounded-[1px] bg-warning/50`}),(0,G.jsx)(`div`,{className:`size-2 rounded-[1px] bg-warning/80`}),(0,G.jsx)(`div`,{className:`size-2 rounded-[1px] bg-error/80`})]}),(0,G.jsx)(`span`,{children:`High`}),(0,G.jsx)(`span`,{className:`ml-1`,children:`|`}),(0,G.jsx)(`div`,{className:`size-2 rounded-[1px] bg-surface-elevated border border-border/30`}),(0,G.jsx)(`span`,{children:`No data`})]})]})}function be({profile:e,accountId:t,onClose:n}){let r=[[`Name`,e.account?.display_name],[`Email`,e.account?.email],[`Org`,e.organization?.name],[`Type`,e.organization?.organization_type],[`Tier`,e.organization?.rate_limit_tier],[`Status`,e.organization?.subscription_status]];return(0,G.jsxs)(`section`,{className:`space-y-2 border-t border-border pt-3`,"data-testid":`account-profile-panel`,children:[(0,G.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,G.jsx)(`h3`,{className:`text-sm font-medium`,children:`Profile`}),(0,G.jsx)(`button`,{className:`p-2 text-muted-foreground hover:text-foreground cursor-pointer`,onClick:n,"aria-label":`Close profile`,children:(0,G.jsx)(c,{className:`size-4`})})]}),(0,G.jsx)(`div`,{className:`grid grid-cols-[80px_1fr] gap-x-3 gap-y-1 text-xs`,children:r.map(([e,t])=>t?(0,G.jsxs)(`span`,{className:`contents`,children:[(0,G.jsx)(`span`,{className:`text-muted-foreground`,children:e}),(0,G.jsx)(`span`,{className:`break-all`,children:t})]},e):null)}),(0,G.jsx)(ye,{accountId:t})]})}function xe({display:e,onConfirm:t,onCancel:n}){let r=f(),i=(0,G.jsxs)(`div`,{className:`space-y-4`,"data-testid":`account-delete-confirm`,children:[(0,G.jsxs)(`p`,{className:`text-sm`,children:[`Remove `,(0,G.jsx)(`span`,{className:`font-medium`,children:e}),`?`]}),(0,G.jsx)(`p`,{className:`text-xs text-text-subtle`,children:`The stored token is deleted from this machine. Any export you already made keeps working.`}),(0,G.jsxs)(`div`,{className:`flex flex-col-reverse md:flex-row gap-2 md:justify-end pt-2`,children:[(0,G.jsx)(m,{variant:`outline`,onClick:n,className:`min-h-11`,children:`Cancel`}),(0,G.jsx)(m,{variant:`destructive`,autoFocus:!1,onClick:t,className:`min-h-11`,children:`Remove account`})]})]});return r?(0,G.jsx)(h,{open:!0,onClose:n,children:(0,G.jsxs)(`div`,{className:`px-4 pb-4`,children:[(0,G.jsx)(`h2`,{className:`text-base font-semibold mb-3`,children:`Remove account`}),i]})}):(0,G.jsx)(x,{open:!0,onOpenChange:e=>{e||n()},children:(0,G.jsxs)(A,{className:`sm:max-w-sm`,children:[(0,G.jsx)(C,{children:(0,G.jsx)(g,{className:`text-sm`,children:`Remove account`})}),i]})})}function Se({open:e,onOpenChange:t,onSuccess:n}){let[r,i]=(0,W.useState)(``),[a,o]=(0,W.useState)(``),[c,l]=(0,W.useState)(!1),[u,d]=(0,W.useState)(null),[f,p]=(0,W.useState)(null),[h,_]=(0,W.useState)(``),[v,b]=(0,W.useState)(!1),[S,w]=(0,W.useState)(`idle`);function T(){p(null),_(``),w(`idle`),d(null)}function E(){t(!1),T(),i(``),o(``),d(null)}async function D(){b(!0),d(null);try{let{url:e,state:t}=await ae();p(t),w(`waiting`),window.open(e,`_blank`)}catch(e){d(e.message)}b(!1)}async function M(){if(!(!h.trim()||!f)){b(!0),d(null);try{let e=h.trim();e.includes(`#`)&&(e=e.split(`#`)[0]??e);let t=await ne(e,f);E(),n(t?.status===`disabled`?`Signed in. This account is still disabled — enable it to use it.`:`Account connected via OAuth!`)}catch(e){d(e.message)}b(!1)}}async function N(){if(r.trim()){l(!0),d(null);try{let e=await ee({apiKey:r.trim(),label:a.trim()||void 0});E(),n(e?.status===`disabled`?`Token updated. This account is still disabled — enable it to use it.`:`Account added!`)}catch(e){d(e.message)}l(!1)}}let P=r.trim()?r.trim().startsWith(`sk-ant-oat`)?`OAuth token (Claude Max/Pro)`:r.trim().startsWith(`sk-ant-api`)?`API key`:`Unknown format`:``;return(0,G.jsx)(x,{open:e,onOpenChange:e=>{e||E()},children:(0,G.jsxs)(A,{className:`sm:max-w-md`,children:[(0,G.jsxs)(C,{children:[(0,G.jsx)(g,{className:`text-sm`,children:`Add Claude Account`}),(0,G.jsx)(O,{className:`text-xs leading-relaxed`,children:`Connect via OAuth (recommended) or paste a token manually.`})]}),(0,G.jsxs)(`div`,{className:`space-y-3`,children:[(0,G.jsxs)(`div`,{className:`rounded-md border p-3 space-y-2`,children:[(0,G.jsx)(`p`,{className:`text-[11px] font-medium`,children:`Recommended: Login with Claude`}),S===`idle`?(0,G.jsx)(m,{size:`sm`,className:`w-full h-8 text-xs`,onClick:D,disabled:v,children:v?(0,G.jsxs)(G.Fragment,{children:[(0,G.jsx)(s,{className:`size-3 animate-spin mr-1`}),` Opening...`]}):`Login with Claude`}):(0,G.jsxs)(`div`,{className:`space-y-2`,children:[(0,G.jsx)(`p`,{className:`text-[10px] text-muted-foreground`,children:`Authorize in the opened tab, then paste the code:`}),(0,G.jsx)(k,{placeholder:`Paste code here...`,value:h,onChange:e=>_(e.target.value),className:`text-xs h-8 font-mono`,autoFocus:!0}),(0,G.jsxs)(`div`,{className:`flex gap-1.5`,children:[(0,G.jsx)(m,{size:`sm`,className:`flex-1 h-7 text-xs`,onClick:M,disabled:!h.trim()||v,children:v?(0,G.jsxs)(G.Fragment,{children:[(0,G.jsx)(s,{className:`size-3 animate-spin mr-1`}),` Connecting...`]}):`Connect`}),(0,G.jsx)(m,{size:`sm`,variant:`ghost`,className:`h-7 text-xs`,onClick:T,children:`Cancel`})]})]})]}),(0,G.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,G.jsx)(`div`,{className:`flex-1 border-t`}),(0,G.jsx)(`span`,{className:`text-[10px] text-muted-foreground`,children:`or paste token`}),(0,G.jsx)(`div`,{className:`flex-1 border-t`})]}),(0,G.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,G.jsx)(y,{htmlFor:`add-token`,className:`text-xs`,children:`Token`}),(0,G.jsx)(k,{id:`add-token`,type:`password`,placeholder:`sk-ant-...`,value:r,onChange:e=>i(e.target.value),className:`text-xs h-8 font-mono`}),P&&(0,G.jsxs)(`p`,{className:`text-[10px] text-muted-foreground`,children:[`Detected: `,P]})]}),(0,G.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,G.jsx)(y,{htmlFor:`add-label`,className:`text-xs`,children:`Label (optional)`}),(0,G.jsx)(k,{id:`add-label`,placeholder:`e.g. Personal, Work`,value:a,onChange:e=>o(e.target.value),className:`text-xs h-8`})]})]}),u&&(0,G.jsx)(`div`,{className:`text-[11px] p-2 rounded bg-error/10 text-error`,children:u}),(0,G.jsxs)(j,{children:[(0,G.jsx)(m,{size:`sm`,variant:`outline`,className:`text-xs h-7`,onClick:E,children:`Cancel`}),(0,G.jsx)(m,{size:`sm`,className:`text-xs h-7`,onClick:N,disabled:!r.trim()||c,children:c?`Adding...`:`Add Token`})]})]})})}function Ce(e){let t=new Blob([e],{type:`application/json`}),n=document.createElement(`a`);n.href=URL.createObjectURL(t),n.download=`ppm-accounts-backup.json`,n.click(),URL.revokeObjectURL(n.href)}function we({open:e,onOpenChange:t,accounts:n,preselectId:a,onMessage:o}){let c=n.filter(e=>e.hasRefreshToken),[l,d]=(0,W.useState)(new Set),[f,p]=(0,W.useState)(``),[h,_]=(0,W.useState)(!1),[v,b]=(0,W.useState)(!1),[S,w]=(0,W.useState)(!1),[T,E]=(0,W.useState)(!1);e&&!T&&(d(a?new Set([a]):new Set(c.map(e=>e.id))),E(!0)),!e&&T&&E(!1);function D(){t(!1),p(``),_(!1),b(!1)}async function M(e){if(l.size===0)return;w(!0);let t=f.trim()||`ppm-hienlh`;try{let n={"Content-Type":`application/json`},r=u();r&&(n.Authorization=`Bearer ${r}`);let i=await fetch(`/api/accounts/export`,{method:`POST`,headers:n,body:JSON.stringify({password:t,accountIds:[...l],includeRefreshToken:h,refreshBeforeExport:v})});if(!i.ok){let e=await i.json();throw Error(e.error??`Export failed: ${i.status}`)}let a=await i.text();e&&await I(a)?o?.(`Backup copied to clipboard!`):(Ce(a),o?.(`Backup downloaded.`)),D()}catch{}w(!1)}let N=l.size>0&&!S;return(0,G.jsx)(x,{open:e,onOpenChange:e=>{e||D()},children:(0,G.jsxs)(A,{className:`sm:max-w-md`,children:[(0,G.jsxs)(C,{children:[(0,G.jsxs)(g,{className:`text-sm flex items-center gap-1.5`,children:[(0,G.jsx)(F,{className:`size-3.5`}),` Export Accounts`]}),(0,G.jsx)(O,{className:`text-xs`,children:`Select accounts and set a password to protect the backup.`})]}),(0,G.jsxs)(`div`,{className:`space-y-3`,children:[(0,G.jsxs)(`div`,{className:`space-y-1`,children:[(0,G.jsxs)(`div`,{className:`flex items-center justify-between mb-1`,children:[(0,G.jsx)(`p`,{className:`text-[11px] font-medium text-muted-foreground`,children:`Accounts to export`}),(0,G.jsx)(`button`,{className:`text-[10px] text-primary hover:underline cursor-pointer`,onClick:()=>d(l.size===c.length?new Set:new Set(c.map(e=>e.id))),children:l.size===c.length?`Deselect all`:`Select all`})]}),c.length===0?(0,G.jsx)(`p`,{className:`text-[10px] text-muted-foreground p-2 border rounded`,children:`No exportable accounts.`}):(0,G.jsx)(`div`,{className:`max-h-36 overflow-y-auto space-y-1 border rounded p-2`,children:c.map(e=>(0,G.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,G.jsx)(`input`,{type:`checkbox`,id:`exp-${e.id}`,checked:l.has(e.id),onChange:t=>{let n=new Set(l);t.target.checked?n.add(e.id):n.delete(e.id),d(n)},className:`size-3.5 accent-primary cursor-pointer`}),(0,G.jsx)(`label`,{htmlFor:`exp-${e.id}`,className:`text-xs cursor-pointer truncate`,children:e.label??e.email??e.id.slice(0,8)})]},e.id))})]}),(0,G.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,G.jsxs)(y,{className:`text-xs`,children:[`Password `,(0,G.jsx)(`span`,{className:`text-muted-foreground font-normal`,children:`(optional)`})]}),(0,G.jsx)(k,{type:`password`,placeholder:`Leave empty for default`,value:f,onChange:e=>p(e.target.value),className:`text-xs h-8`,autoComplete:`new-password`})]}),(0,G.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,G.jsx)(`input`,{type:`checkbox`,id:`exp-full`,checked:h,onChange:e=>_(e.target.checked),className:`size-3.5 accent-primary cursor-pointer`}),(0,G.jsx)(`label`,{htmlFor:`exp-full`,className:`text-[11px] cursor-pointer`,children:`Include refresh tokens (full transfer)`})]}),(0,G.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,G.jsx)(`input`,{type:`checkbox`,id:`exp-refresh`,checked:v,onChange:e=>b(e.target.checked),className:`size-3.5 accent-primary cursor-pointer`}),(0,G.jsx)(`label`,{htmlFor:`exp-refresh`,className:`text-[11px] cursor-pointer`,children:`Refresh tokens before export`})]}),h?(0,G.jsxs)(`div`,{className:`rounded-md border border-error/30 bg-error/5 p-2.5`,children:[(0,G.jsx)(`p`,{className:`text-[10px] font-medium text-error`,children:`Full transfer — source accounts will expire`}),(0,G.jsx)(`p`,{className:`text-[10px] text-muted-foreground`,children:`Refresh tokens included. Source machine expires in ~1h after target refreshes.`})]}):v?(0,G.jsx)(`div`,{className:`rounded-md border border-warning/30 bg-warning/5 p-2.5`,children:(0,G.jsx)(`p`,{className:`text-[10px] font-medium text-warning`,children:`Refresh before export — invalidates previous shares`})}):(0,G.jsx)(`div`,{className:`rounded-md border border-success/30 bg-success/5 p-2.5`,children:(0,G.jsx)(`p`,{className:`text-[10px] font-medium text-success`,children:`Share current token (safe)`})}),(0,G.jsx)(`p`,{className:`text-[10px] text-muted-foreground`,children:`Encrypted with AES-256-GCM + scrypt.`})]}),(0,G.jsxs)(j,{className:`gap-1.5 flex-col sm:flex-row`,children:[(0,G.jsx)(m,{size:`sm`,variant:`outline`,className:`text-xs h-7 cursor-pointer`,onClick:D,children:`Cancel`}),(0,G.jsxs)(m,{size:`sm`,variant:`outline`,className:`text-xs h-7 cursor-pointer`,disabled:!N,onClick:()=>M(!0),children:[(0,G.jsx)(r,{className:`size-3 mr-1`}),` Copy`]}),(0,G.jsx)(m,{size:`sm`,className:`text-xs h-7 cursor-pointer`,disabled:!N,onClick:()=>M(!1),children:S?(0,G.jsxs)(G.Fragment,{children:[(0,G.jsx)(s,{className:`size-3 animate-spin mr-1`}),` Exporting...`]}):(0,G.jsxs)(G.Fragment,{children:[(0,G.jsx)(i,{className:`size-3 mr-1`}),` Download`]})})]})]})})}function Te({open:e,onOpenChange:t,onSuccess:n}){let[r,i]=(0,W.useState)(``),[a,o]=(0,W.useState)(``),[c,l]=(0,W.useState)(!1),[u,d]=(0,W.useState)(null);function f(){t(!1),i(``),o(``),d(null)}async function p(){if(r.trim()){l(!0),d(null);try{let e=await se({data:r.trim(),password:a.trim()||`ppm-hienlh`});f(),n(`Imported ${e.imported} account(s)`)}catch(e){d(e.message||`Import failed`)}l(!1)}}return(0,G.jsx)(x,{open:e,onOpenChange:e=>{e||f()},children:(0,G.jsxs)(A,{className:`sm:max-w-md`,children:[(0,G.jsxs)(C,{children:[(0,G.jsxs)(g,{className:`text-sm flex items-center gap-1.5`,children:[(0,G.jsx)(F,{className:`size-3.5`}),` Import Accounts`]}),(0,G.jsx)(O,{className:`text-xs`,children:`Paste backup data and enter the export password. Imported accounts are temporary (~1h).`})]}),(0,G.jsxs)(`div`,{className:`space-y-3`,children:[(0,G.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,G.jsx)(y,{className:`text-xs`,children:`Backup data`}),(0,G.jsx)(`textarea`,{value:r,onChange:e=>i(e.target.value),placeholder:`Paste backup JSON here...`,rows:4,className:`w-full text-xs p-2 rounded border border-border bg-background font-mono resize-none focus:outline-none focus:ring-1 focus:ring-primary`})]}),(0,G.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,G.jsxs)(y,{className:`text-xs`,children:[`Password `,(0,G.jsx)(`span`,{className:`text-muted-foreground font-normal`,children:`(optional)`})]}),(0,G.jsx)(k,{type:`password`,placeholder:`Leave empty for default`,value:a,onChange:e=>o(e.target.value),className:`text-xs h-8`,autoComplete:`current-password`})]})]}),u&&(0,G.jsx)(`div`,{className:`text-[11px] p-2 rounded bg-error/10 text-error`,children:u}),(0,G.jsxs)(j,{children:[(0,G.jsx)(m,{size:`sm`,variant:`outline`,className:`text-xs h-7 cursor-pointer`,onClick:f,children:`Cancel`}),(0,G.jsx)(m,{size:`sm`,className:`text-xs h-7 cursor-pointer`,disabled:!r.trim()||c,onClick:p,children:c?(0,G.jsxs)(G.Fragment,{children:[(0,G.jsx)(s,{className:`size-3 animate-spin mr-1`}),` Importing...`]}):`Import`})]})]})})}var Y=typeof window<`u`?window.matchMedia(`(min-width: 768px)`):null;function Ee(e){return Y?.addEventListener(`change`,e),()=>Y?.removeEventListener(`change`,e)}function De(){return Y?.matches??!0}function X(){let[e,t]=(0,W.useState)(null),[n,r]=(0,W.useState)(!0);return(0,W.useEffect)(()=>{r(!0),ie().then(t).finally(()=>r(!1))},[]),n?(0,G.jsx)(`p`,{className:`text-xs text-text-subtle py-4 text-center`,children:`Loading...`}):e?(0,G.jsxs)(`div`,{className:`space-y-4`,children:[(0,G.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,G.jsx)(`label`,{className:`text-xs font-medium text-text-primary`,children:`Rotation Strategy`}),(0,G.jsxs)(R,{value:e.strategy,onValueChange:async e=>{t(await z({strategy:e}))},children:[(0,G.jsx)(v,{className:`w-full h-9 text-xs`,children:(0,G.jsx)(w,{})}),(0,G.jsxs)(_,{children:[(0,G.jsx)(T,{value:`round-robin`,children:`Round-robin`}),(0,G.jsx)(T,{value:`fill-first`,children:`Fill-first`}),(0,G.jsx)(T,{value:`lowest-usage`,children:`Lowest usage`})]})]}),(0,G.jsxs)(`p`,{className:`text-[10px] text-text-subtle`,children:[e.strategy===`round-robin`&&`Cycles through accounts evenly`,e.strategy===`fill-first`&&`Uses one account until its limit, then moves on`,e.strategy===`lowest-usage`&&`Picks the account with the lowest current usage`]})]}),(0,G.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,G.jsx)(`label`,{className:`text-xs font-medium text-text-primary`,children:`Max Retry`}),(0,G.jsx)(`input`,{type:`number`,min:0,value:e.maxRetry,className:`w-full h-9 text-xs border rounded-md px-3 bg-background`,onChange:async e=>{let n=parseInt(e.target.value,10);!isNaN(n)&&n>=0&&t(await z({maxRetry:n}))}}),(0,G.jsx)(`p`,{className:`text-[10px] text-text-subtle`,children:`How many accounts to try on failure. 0 = try all available accounts.`})]}),(0,G.jsxs)(`div`,{className:`space-y-1.5 border-t border-border pt-3`,children:[(0,G.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,G.jsx)(`label`,{className:`text-xs font-medium text-text-primary`,children:`Cooldown parking`}),(0,G.jsx)(D,{checked:e.cooldownEnabled,onCheckedChange:async e=>{t(await z({cooldownEnabled:e}))}})]}),(0,G.jsx)(`p`,{className:`text-[10px] text-text-subtle`,children:`Park a failing account (rate limit / usage limit / auth error) until it recovers. Off: accounts stay selectable — rotation still skips accounts already ≥95% on the 5-hour limit.`})]}),(0,G.jsxs)(`div`,{className:`flex items-center justify-between text-xs border-t border-border pt-3`,children:[(0,G.jsx)(`span`,{className:`text-text-subtle`,children:`Active accounts`}),(0,G.jsx)(`span`,{className:`font-medium text-text-primary`,children:e.activeCount})]})]}):(0,G.jsx)(`p`,{className:`text-xs text-text-subtle py-4 text-center`,children:`Failed to load settings`})}function Oe({open:e,onOpenChange:t}){let n=(0,W.useSyncExternalStore)(Ee,De);return e?n?(0,G.jsx)(x,{open:e,onOpenChange:t,children:(0,G.jsxs)(A,{className:`sm:max-w-sm`,children:[(0,G.jsx)(C,{children:(0,G.jsxs)(g,{className:`text-sm flex items-center gap-2`,children:[(0,G.jsx)(E,{className:`size-4`}),` Rotation & Retry`]})}),(0,G.jsx)(X,{})]})}):(0,G.jsxs)(G.Fragment,{children:[(0,G.jsx)(`div`,{className:`fixed inset-0 z-50 transition-opacity duration-200 opacity-100`,onClick:()=>t(!1),style:{backgroundColor:`rgba(0,0,0,0.5)`}}),(0,G.jsxs)(`div`,{className:d(`fixed bottom-0 left-0 right-0 z-50 bg-background rounded-t-2xl border-t border-border shadow-2xl`,`transition-transform duration-300 ease-out max-h-[85vh] overflow-y-auto`,`translate-y-0`),children:[(0,G.jsx)(`div`,{className:`flex justify-center pt-3 pb-1`,children:(0,G.jsx)(`div`,{className:`w-10 h-1 rounded-full bg-border`})}),(0,G.jsxs)(`div`,{className:`flex items-center justify-between px-4 py-2 border-b border-border`,children:[(0,G.jsxs)(`span`,{className:`text-sm font-semibold flex items-center gap-2`,children:[(0,G.jsx)(E,{className:`size-4`}),` Rotation & Retry`]}),(0,G.jsx)(`button`,{onClick:()=>t(!1),className:`flex items-center justify-center size-7 rounded-md hover:bg-surface-elevated transition-colors`,children:(0,G.jsx)(c,{className:`size-4`})})]}),(0,G.jsx)(`div`,{className:`px-4 py-4 pb-8`,children:(0,G.jsx)(X,{})})]})]}):null}var ke=p(`inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2`,{variants:{variant:{default:`border-transparent bg-primary text-primary-foreground shadow`,secondary:`border-transparent bg-secondary text-secondary-foreground`,destructive:`border-transparent bg-destructive text-destructive-foreground shadow`,outline:`text-foreground`}},defaultVariants:{variant:`default`}});function Z({className:e,variant:t,...n}){return(0,G.jsx)(`div`,{className:d(ke({variant:t}),e),...n})}function Ae(e){let t=Math.floor(Date.now()/1e3);return e>t?`${Math.floor((e-t)/60)}m`:`exp`}function je({accounts:e}){let[t,n]=(0,W.useState)([]),[r,i]=(0,W.useState)(!1),[a,o]=(0,W.useState)(!1),[c,l]=(0,W.useState)(new Map),u=e.filter(e=>e.hasRefreshToken);async function d(){if(u.length!==0){i(!0);try{let e=await ue(u.map(e=>e.id),a);n(t=>[...t,{round:t.length+1,time:new Date().toLocaleTimeString(),includeRefresh:a,items:e}])}catch{}i(!1)}}async function f(e,t){l(t=>new Map(t).set(e,{loading:!0}));try{let n=await de(t);l(t=>new Map(t).set(e,{loading:!1,...n}))}catch(t){l(n=>new Map(n).set(e,{loading:!1,status:`error`,error:t.message}))}}function p(e,t){return[{key:`r${e.round}-pre-${t.id}`,label:`Pre-export`,token:t.preExportTokenFull,preview:t.preExportToken,expires:t.preExportExpires},{key:`r${e.round}-exp-${t.id}`,label:`Exported`,token:t.exportedTokenFull,preview:t.exportedToken,expires:t.exportedExpires},{key:`r${e.round}-post-${t.id}`,label:`Post-export`,token:t.postExportTokenFull,preview:t.postExportToken,expires:t.postExportExpires}]}function h(){for(let e of t)for(let t of e.items)for(let n of p(e,t))n.token&&f(n.key,n.token)}return(0,G.jsxs)(`section`,{className:`border-t pt-4 space-y-2`,children:[(0,G.jsxs)(`div`,{className:`flex items-center justify-between gap-2`,children:[(0,G.jsx)(`p`,{className:`text-xs font-medium`,children:`Simulate export`}),t.length>0&&(0,G.jsxs)(`span`,{className:`text-[10px] text-muted-foreground`,children:[t.length,` round(s)`]})]}),(0,G.jsx)(`p`,{className:`text-[11px] text-muted-foreground`,children:`Every run appends a round. Earlier rounds are kept so tokens can be compared across runs.`}),u.length===0?(0,G.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`No OAuth account with a refresh token to export.`}):(0,G.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,G.jsxs)(`div`,{className:`flex items-center gap-1.5`,children:[(0,G.jsx)(D,{id:`sim-include-refresh`,checked:a,onCheckedChange:o,className:`cursor-pointer`}),(0,G.jsx)(y,{htmlFor:`sim-include-refresh`,className:`text-[11px] cursor-pointer`,children:`Include refresh tokens`})]}),(0,G.jsx)(m,{size:`sm`,className:`h-7 text-xs cursor-pointer`,disabled:r,onClick:()=>void d(),children:r?(0,G.jsxs)(G.Fragment,{children:[(0,G.jsx)(s,{className:`size-3 animate-spin mr-1`}),` Exporting...`]}):`Run export #${t.length+1}`})]}),t.length>0&&(0,G.jsxs)(`div`,{className:`space-y-3`,children:[(0,G.jsxs)(m,{size:`sm`,variant:`outline`,className:`w-full h-7 text-[11px] cursor-pointer`,onClick:h,children:[`Test all tokens (`,t.reduce((e,t)=>e+t.items.length*3,0),`)`]}),t.map(e=>(0,G.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,G.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,G.jsxs)(`p`,{className:`text-xs font-medium text-primary`,children:[`Round #`,e.round]}),(0,G.jsx)(`span`,{className:`text-[10px] text-muted-foreground`,children:e.time}),(0,G.jsx)(Z,{variant:e.includeRefresh?`destructive`:`secondary`,className:`text-[9px] px-1 py-0`,children:e.includeRefresh?`with refresh`:`access only`})]}),e.items.map(t=>(0,G.jsxs)(`div`,{className:`p-2 rounded-lg border bg-card space-y-1.5`,children:[(0,G.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,G.jsx)(`span`,{className:`text-[11px] font-medium`,children:t.label??t.email??t.id.slice(0,8)}),t.tokenChanged&&(0,G.jsx)(Z,{variant:`secondary`,className:`text-[9px] px-1 py-0`,children:`DB token changed`})]}),p(e,t).map(e=>{let t=c.get(e.key);return(0,G.jsxs)(`div`,{className:`flex items-center gap-1.5 text-[11px]`,children:[(0,G.jsx)(`span`,{className:`w-20 shrink-0 text-muted-foreground text-[10px]`,children:e.label}),(0,G.jsx)(`code`,{className:`flex-1 truncate font-mono text-[10px] bg-muted px-1 rounded`,children:e.preview??`N/A`}),e.expires&&(0,G.jsx)(`span`,{className:`text-[10px] text-muted-foreground shrink-0`,children:Ae(e.expires)}),e.token?(0,G.jsx)(m,{size:`sm`,variant:`outline`,className:`h-6 text-[10px] px-1.5 cursor-pointer shrink-0`,disabled:t?.loading,onClick:()=>void f(e.key,e.token),children:t?.loading?(0,G.jsx)(s,{className:`size-3 animate-spin`}):`Test`}):(0,G.jsx)(`span`,{className:`text-[10px] text-muted-foreground`,children:`-`}),t&&!t.loading&&(0,G.jsx)(`span`,{className:`text-[10px] font-medium shrink-0 ${t.status?.startsWith(`valid`)?`text-success`:`text-error`}`,children:t.status})]},e.key)})]},`r${e.round}-${t.id}`))]},e.round))]})]})}function Me(e){let t=Math.floor(Date.now()/1e3),n=Math.floor(Math.abs(e-t)/60);return e>t?`${n}m left`:`expired ${n}m ago`}function Ne({open:e,onOpenChange:t,accounts:n}){let[r,i]=(0,W.useState)(new Map),[o,c]=(0,W.useState)(!1);async function l(e){i(t=>new Map(t).set(e,{loading:!0}));try{let t=await le(e,o);i(n=>new Map(n).set(e,{loading:!1,result:t}))}catch(t){i(n=>new Map(n).set(e,{loading:!1,error:t.message}))}}function u(){for(let e of n.filter(e=>e.expiresAt!==null))l(e.id)}return(0,G.jsx)(x,{open:e,onOpenChange:t,children:(0,G.jsxs)(A,{className:`sm:max-w-xl max-h-[85vh] flex flex-col`,children:[(0,G.jsxs)(C,{children:[(0,G.jsxs)(g,{className:`text-sm flex items-center gap-1.5`,children:[(0,G.jsx)(a,{className:`size-4`}),` Token Test`]}),(0,G.jsx)(O,{className:`text-xs`,children:`Probe stored tokens for validity. Nothing is modified.`})]}),(0,G.jsxs)(`div`,{className:`space-y-4 overflow-y-auto flex-1 pr-1`,children:[(0,G.jsxs)(`section`,{className:`space-y-2`,children:[(0,G.jsxs)(`div`,{className:`flex items-center justify-between gap-2`,children:[(0,G.jsx)(`p`,{className:`text-xs font-medium`,children:`Current tokens`}),(0,G.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,G.jsxs)(`div`,{className:`flex items-center gap-1.5`,children:[(0,G.jsx)(D,{id:`token-test-refresh`,checked:o,onCheckedChange:c,className:`cursor-pointer`}),(0,G.jsx)(y,{htmlFor:`token-test-refresh`,className:`text-[11px] cursor-pointer`,children:`Also test refresh`})]}),(0,G.jsx)(m,{size:`sm`,variant:`outline`,className:`h-7 text-xs cursor-pointer`,onClick:u,children:`Test all`})]})]}),n.length===0&&(0,G.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`No accounts to test.`}),(0,G.jsx)(`div`,{className:`space-y-1.5`,children:n.map(e=>{let t=r.get(e.id),n=t?.result?.accessToken.status;return(0,G.jsxs)(`div`,{className:`p-2 rounded border bg-card space-y-1`,children:[(0,G.jsxs)(`div`,{className:`flex items-center justify-between gap-2`,children:[(0,G.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,G.jsx)(`span`,{className:`text-xs font-medium truncate block`,children:e.label??e.email??e.id.slice(0,8)}),e.expiresAt&&(0,G.jsx)(`span`,{className:`text-[10px] text-muted-foreground`,children:Me(e.expiresAt)})]}),(0,G.jsx)(m,{size:`sm`,variant:`outline`,className:`h-7 text-xs cursor-pointer shrink-0`,disabled:t?.loading,onClick:()=>void l(e.id),children:t?.loading?(0,G.jsx)(s,{className:`size-3 animate-spin`}):`Test`})]}),t&&!t.loading&&(0,G.jsxs)(`div`,{className:`text-[11px] pl-2 border-l-2 border-muted`,children:[t.error&&(0,G.jsx)(`p`,{className:`text-error`,children:t.error}),n&&(0,G.jsxs)(`span`,{className:n.startsWith(`valid`)?`text-success`:`text-error`,children:[n,t.result?.accessToken.code?` (${t.result.accessToken.code})`:``]})]})]},e.id)})})]}),(0,G.jsx)(je,{accounts:n})]}),(0,G.jsx)(j,{children:(0,G.jsx)(m,{size:`sm`,variant:`outline`,className:`text-xs cursor-pointer`,onClick:()=>t(!1),children:`Close`})})]})})}function Pe(){let{usages:e,accounts:t,activeAccountId:n,initialLoading:r,refreshing:c,flashIds:l,reload:u}=me(),[d,f]=(0,W.useState)(null),[p,h]=(0,W.useState)(null),[g,_]=(0,W.useState)(null),[v,y]=(0,W.useState)(null),[x,S]=(0,W.useState)(null),[C,w]=(0,W.useState)(null);function T(e){u(),e&&f(e)}async function D(e,t){h(e);try{await ce(e,{status:t===`disabled`?`active`:`disabled`})}catch(e){f(e.message)}h(null),u()}async function O(){if(g){try{await te(g.id),f(`Account "${g.display}" removed.`),u()}catch(e){f(`Failed to remove: ${e.message}`)}_(null)}}let k=new Map(t.map(e=>[e.id,e])),A=e[0]?.usage.lastFetchedAt;return(0,G.jsxs)(`div`,{className:`space-y-4`,children:[(0,G.jsx)(H,{description:`Connect multiple Claude accounts. PPM rotates between them automatically to avoid rate limits.`,onRefresh:()=>void u(),refreshing:c,disabled:r,actions:(0,G.jsxs)(G.Fragment,{children:[(0,G.jsxs)(m,{size:`sm`,className:`cursor-pointer gap-1.5`,onClick:()=>w(`add`),children:[(0,G.jsx)(b,{className:`size-4`}),` Add account`]}),(0,G.jsxs)(m,{size:`sm`,variant:`outline`,className:`cursor-pointer gap-1.5`,onClick:()=>{S(null),w(`export`)},children:[(0,G.jsx)(i,{className:`size-4`}),` Export`]}),(0,G.jsxs)(m,{size:`sm`,variant:`outline`,className:`cursor-pointer gap-1.5`,onClick:()=>w(`import`),children:[(0,G.jsx)(P,{className:`size-4`}),` Import`]}),(0,G.jsxs)(m,{size:`sm`,variant:`outline`,className:`cursor-pointer gap-1.5`,onClick:()=>w(`rotation`),children:[(0,G.jsx)(E,{className:`size-4`}),` Rotation`]}),(0,G.jsxs)(m,{size:`sm`,variant:`outline`,className:`cursor-pointer gap-1.5`,onClick:()=>w(`token-test`),children:[(0,G.jsx)(a,{className:`size-4`}),` Token test`]})]})}),(0,G.jsx)(B,{message:d,onDismiss:()=>f(null)}),(0,G.jsxs)(`section`,{className:`space-y-2`,children:[(0,G.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,G.jsx)(`h3`,{className:`text-sm font-medium`,children:`Accounts`}),A&&(0,G.jsxs)(`span`,{className:`text-[10px] text-muted-foreground`,children:[`usage `,V(new Date(A).getTime())]})]}),r?(0,G.jsxs)(`div`,{className:`flex items-center gap-2 py-6 text-xs text-muted-foreground`,children:[(0,G.jsx)(s,{className:`size-4 animate-spin`}),` Loading accounts...`]}):e.length===0?(0,G.jsxs)(`div`,{className:`flex flex-col items-center gap-2 py-8 text-center`,children:[(0,G.jsx)(o,{className:`size-5 text-muted-foreground`}),(0,G.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`No accounts connected yet.`}),(0,G.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Add one to start using PPM.`})]}):(0,G.jsx)(`div`,{className:`space-y-2`,children:e.map(e=>(0,G.jsx)(fe,{entry:e,isActive:e.accountId===n,accountInfo:k.get(e.accountId),onToggle:D,toggling:p===e.accountId,onDelete:(e,t)=>_({id:e,display:t}),onExport:e=>{S(e),w(`export`)},onViewProfile:(e,t)=>y({profile:e,accountId:t}),flash:l.has(e.accountId)},e.accountId))})]}),v&&(0,G.jsx)(be,{profile:v.profile,accountId:v.accountId,onClose:()=>y(null)}),g&&(0,G.jsx)(xe,{display:g.display,onConfirm:()=>void O(),onCancel:()=>_(null)}),(0,G.jsx)(Se,{open:C===`add`,onOpenChange:e=>w(e?`add`:null),onSuccess:T}),(0,G.jsx)(we,{open:C===`export`,onOpenChange:e=>{w(e?`export`:null),e||S(null)},accounts:t,preselectId:x,onMessage:f}),(0,G.jsx)(Te,{open:C===`import`,onOpenChange:e=>w(e?`import`:null),onSuccess:T}),(0,G.jsx)(Oe,{open:C===`rotation`,onOpenChange:e=>w(e?`rotation`:null)}),(0,G.jsx)(Ne,{open:C===`token-test`,onOpenChange:e=>w(e?`token-test`:null),accounts:t})]})}function Fe({open:e,onOpenChange:t,label:n,onLabelChange:r,apiKey:i,onApiKeyChange:a,adding:c,onAddApiKey:l,deviceWaiting:u,onStartDevice:d,device:f,error:p}){return(0,G.jsx)(x,{open:e,onOpenChange:t,children:(0,G.jsxs)(A,{className:`sm:max-w-md`,children:[(0,G.jsxs)(C,{children:[(0,G.jsx)(g,{className:`text-sm`,children:`Add Codex Account`}),(0,G.jsxs)(O,{className:`text-xs leading-relaxed`,children:[`Each account keeps its own login (its own `,(0,G.jsx)(`code`,{children:`CODEX_HOME`}),`).`]})]}),(0,G.jsxs)(`div`,{className:`space-y-3`,children:[(0,G.jsxs)(`div`,{className:`rounded-md border p-3 space-y-2`,children:[(0,G.jsx)(`p`,{className:`text-[11px] font-medium`,children:`Recommended: sign in with ChatGPT`}),(0,G.jsx)(m,{size:`sm`,className:`w-full cursor-pointer gap-1.5`,onClick:d,disabled:u,children:u?(0,G.jsxs)(G.Fragment,{children:[(0,G.jsx)(s,{className:`size-4 animate-spin`}),` Waiting for authorization...`]}):(0,G.jsxs)(G.Fragment,{children:[(0,G.jsx)(M,{className:`size-4`}),` Sign in with ChatGPT`]})}),f&&(0,G.jsxs)(`div`,{className:`rounded-md border border-primary/40 bg-primary/10 p-2.5 space-y-1.5`,children:[(0,G.jsx)(`p`,{className:`text-[11px]`,children:`Enter this code to authorize:`}),(0,G.jsx)(`div`,{className:`font-mono text-lg tracking-widest`,children:f.userCode}),(0,G.jsxs)(`a`,{href:f.verificationUrl,target:`_blank`,rel:`noreferrer`,className:`inline-flex items-center gap-1 text-primary hover:underline text-[11px]`,children:[(0,G.jsx)(L,{className:`size-3`}),` `,f.verificationUrl]})]})]}),(0,G.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,G.jsx)(`div`,{className:`flex-1 border-t`}),(0,G.jsx)(`span`,{className:`text-[10px] text-muted-foreground`,children:`or paste an API key`}),(0,G.jsx)(`div`,{className:`flex-1 border-t`})]}),(0,G.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,G.jsx)(y,{htmlFor:`codex-api-key`,className:`text-xs`,children:`OpenAI API key`}),(0,G.jsx)(k,{id:`codex-api-key`,type:`password`,placeholder:`sk-...`,value:i,onChange:e=>a(e.target.value),className:`text-xs font-mono`})]}),(0,G.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,G.jsx)(y,{htmlFor:`codex-label`,className:`text-xs`,children:`Label (optional)`}),(0,G.jsx)(k,{id:`codex-label`,placeholder:`e.g. Personal, Work`,value:n,onChange:e=>r(e.target.value),className:`text-xs`})]})]}),p&&(0,G.jsx)(`div`,{className:`text-[11px] p-2 rounded bg-error/10 text-error`,children:p}),(0,G.jsxs)(j,{children:[(0,G.jsx)(m,{size:`sm`,variant:`outline`,className:`text-xs cursor-pointer`,onClick:()=>t(!1),children:`Cancel`}),(0,G.jsxs)(m,{size:`sm`,className:`text-xs cursor-pointer gap-1.5`,onClick:l,disabled:!i.trim()||c,children:[c?(0,G.jsx)(s,{className:`size-3.5 animate-spin`}):(0,G.jsx)(o,{className:`size-3.5`}),`Add key`]})]})]})})}function Q({mode:e,onOpenChange:t,password:n,onPasswordChange:r,busy:a,onExport:o,onImport:c,error:l}){let u=(0,W.useRef)(null),d=e===`export`;return(0,G.jsx)(x,{open:e!==null,onOpenChange:t,children:(0,G.jsxs)(A,{className:`sm:max-w-md`,children:[(0,G.jsxs)(C,{children:[(0,G.jsxs)(g,{className:`text-sm flex items-center gap-1.5`,children:[(0,G.jsx)(F,{className:`size-4`}),` `,d?`Export Codex Accounts`:`Import Codex Accounts`]}),(0,G.jsx)(O,{className:`text-xs leading-relaxed`,children:d?`Writes a password-encrypted file containing each account's login.`:`Reads a backup file. The password must be the one it was exported with.`})]}),(0,G.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,G.jsx)(y,{htmlFor:`codex-backup-password`,className:`text-xs`,children:`Password`}),(0,G.jsx)(k,{id:`codex-backup-password`,type:`password`,placeholder:d?`Choose a password`:`Password used for the export`,value:n,onChange:e=>r(e.target.value),className:`text-xs`,autoComplete:d?`new-password`:`current-password`}),(0,G.jsx)(`p`,{className:`text-[10px] text-muted-foreground`,children:`A backup holds full logins — keep the file and its password together.`})]}),l&&(0,G.jsx)(`div`,{className:`text-[11px] p-2 rounded bg-error/10 text-error`,children:l}),(0,G.jsx)(`input`,{ref:u,type:`file`,accept:`application/json,.json`,className:`hidden`,onChange:e=>{let t=e.target.files?.[0];e.target.value=``,t&&c(t)}}),(0,G.jsxs)(j,{children:[(0,G.jsx)(m,{size:`sm`,variant:`outline`,className:`text-xs cursor-pointer`,onClick:()=>t(!1),children:`Cancel`}),(0,G.jsxs)(m,{size:`sm`,className:`text-xs cursor-pointer gap-1.5`,disabled:!n.trim()||a,onClick:()=>d?o():u.current?.click(),children:[a?(0,G.jsx)(s,{className:`size-3.5 animate-spin`}):d?(0,G.jsx)(i,{className:`size-3.5`}):(0,G.jsx)(P,{className:`size-3.5`}),d?`Download backup`:`Choose file`]})]})]})})}var Ie=[{value:`round-robin`,label:`Round-robin`,hint:`Take the next account each time.`},{value:`fill-first`,label:`Fill-first`,hint:`Stay on one account until it runs out.`},{value:`lowest-usage`,label:`Lowest usage`,hint:`Pick whichever has the most left.`}];function Le({open:e,onOpenChange:t,strategy:n,onChange:r}){return(0,G.jsx)(x,{open:e,onOpenChange:t,children:(0,G.jsxs)(A,{className:`sm:max-w-md`,children:[(0,G.jsxs)(C,{children:[(0,G.jsx)(g,{className:`text-sm`,children:`Codex Rotation`}),(0,G.jsx)(O,{className:`text-xs leading-relaxed`,children:`How a new chat chooses among your Codex accounts.`})]}),(0,G.jsx)(`div`,{className:`space-y-2`,children:Ie.map(e=>(0,G.jsxs)(`button`,{type:`button`,onClick:()=>r(e.value),"aria-current":n===e.value?`true`:void 0,className:d(`w-full text-left rounded-md border px-3 py-2.5 min-h-11 cursor-pointer transition-colors`,n===e.value?`border-primary bg-primary/10`:`border-border hover:bg-accent/50 active:bg-accent`),children:[(0,G.jsx)(`p`,{className:`text-sm font-medium`,children:e.label}),(0,G.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:e.hint})]},e.value))}),(0,G.jsx)(j,{children:(0,G.jsx)(m,{size:`sm`,variant:`outline`,className:`text-xs cursor-pointer`,onClick:()=>t(!1),children:`Done`})})]})})}var Re=2e3,ze=21e4;function Be(e){let[t,n]=(0,W.useState)([]),[r,i]=(0,W.useState)(`round-robin`),[a,o]=(0,W.useState)({}),[s,c]=(0,W.useState)(!1),[d,f]=(0,W.useState)(``),[p,m]=(0,W.useState)(``),[h,g]=(0,W.useState)(!1),[_,v]=(0,W.useState)(null),[y,b]=(0,W.useState)(null),[x,S]=(0,W.useState)(!1),[C,w]=(0,W.useState)(``),[T,E]=(0,W.useState)(!1),[D,O]=(0,W.useState)(!1),[k,A]=(0,W.useState)(null),j=(0,W.useRef)(null),M=(0,W.useRef)(null),N=(0,W.useCallback)(async()=>{c(!0);try{let e=await l.get(`/api/codex-accounts`);n(e.accounts),i(e.strategy),l.get(`/api/codex-accounts/usage`).then(o).catch(()=>{})}finally{c(!1)}},[]);(0,W.useEffect)(()=>{N()},[N]);let P=async()=>{if(d.trim()){g(!0),v(null);try{await l.post(`/api/codex-accounts/api-key`,{apiKey:d.trim(),label:p.trim()||void 0}),f(``),m(``),e(),await N()}catch(e){v(e.message)}finally{g(!1)}}},F=(0,W.useCallback)(t=>{let n=Date.now()+ze,r=!1;j.current=()=>{r=!0};let i=e=>{r=!0,j.current=null,M.current=null,e&&v(e),b(null),S(!1)},a=async()=>{if(r)return;let o=null;try{o=await l.get(`/api/codex-accounts/device-login/${t}/status`)}catch{}if(!r){if(o?.state===`done`){i(),m(``),e(),await N();return}if(o?.state===`error`){i(o.error);return}if(Date.now()>n){i(`Device login timed out.`);return}setTimeout(a,Re)}};a()},[N]);return(0,W.useEffect)(()=>()=>{j.current?.();let e=M.current;e&&l.del(`/api/codex-accounts/device-login/${e}`).catch(()=>{})},[]),{accounts:t,strategy:r,usages:a,loading:s,apiKey:d,setApiKey:f,label:p,setLabel:m,adding:h,err:_,setErr:v,device:y,deviceWaiting:x,backupPassword:C,setBackupPassword:w,exporting:T,importing:D,msg:k,setMsg:A,load:N,addApiKey:P,startDevice:async()=>{v(null);try{let e=await l.post(`/api/codex-accounts/device-login`,{label:p.trim()||void 0});b(e),S(!0),M.current=e.id,F(e.id)}catch(e){v(e.message)}},remove:async e=>{await l.del(`/api/codex-accounts/${e}`),await N()},changeStrategy:async e=>{i(e);try{await l.put(`/api/codex-accounts/strategy`,{strategy:e})}catch{}},doExport:async()=>{if(!C.trim()){v(`Set a backup password first.`);return}E(!0),v(null),A(null);try{let t={"Content-Type":`application/json`},n=u();n&&(t.Authorization=`Bearer ${n}`);let r=await fetch(`/api/codex-accounts/export`,{method:`POST`,headers:t,body:JSON.stringify({password:C})});if(!r.ok){let e=await r.json().catch(()=>({}));throw Error(e.error??`Export failed: ${r.status}`)}let i=await r.text(),a=new Blob([i],{type:`application/json`}),o=document.createElement(`a`);o.href=URL.createObjectURL(a),o.download=`ppm-codex-accounts-backup.json`,o.click(),URL.revokeObjectURL(o.href),A(`Backup downloaded.`),e()}catch(e){v(e.message)}finally{E(!1)}},doImport:async t=>{if(!C.trim()){v(`Enter the backup password first.`);return}O(!0),v(null),A(null);try{let n=await t.text(),r=await l.post(`/api/codex-accounts/import`,{data:n,password:C});A(`Imported ${r.imported}${r.skipped?`, skipped ${r.skipped}`:``}.`),e(),await N()}catch(e){v(e.message)}finally{O(!1)}}}}function $(e){return e==null?null:Math.round(e*100)}function Ve(){let[e,t]=(0,W.useState)(null),n=Be(()=>t(null));return(0,G.jsxs)(`div`,{className:`space-y-4`,children:[(0,G.jsx)(H,{description:(0,G.jsxs)(G.Fragment,{children:[`Each Codex account keeps its own login (`,(0,G.jsx)(`code`,{children:`CODEX_HOME`}),`). One is picked per chat by the rotation setting.`]}),onRefresh:()=>void n.load(),refreshing:n.loading,actions:(0,G.jsxs)(G.Fragment,{children:[(0,G.jsxs)(m,{size:`sm`,className:`cursor-pointer gap-1.5`,onClick:()=>t(`add`),children:[(0,G.jsx)(b,{className:`size-4`}),` Add account`]}),(0,G.jsxs)(m,{size:`sm`,variant:`outline`,className:`cursor-pointer gap-1.5`,onClick:()=>t(`export`),children:[(0,G.jsx)(i,{className:`size-4`}),` Export`]}),(0,G.jsxs)(m,{size:`sm`,variant:`outline`,className:`cursor-pointer gap-1.5`,onClick:()=>t(`import`),children:[(0,G.jsx)(P,{className:`size-4`}),` Import`]}),(0,G.jsxs)(m,{size:`sm`,variant:`outline`,className:`cursor-pointer gap-1.5`,onClick:()=>t(`rotation`),children:[(0,G.jsx)(E,{className:`size-4`}),` Rotation`]})]})}),(0,G.jsx)(B,{message:n.msg??n.err,onDismiss:()=>{n.setMsg(null),n.setErr(null)}}),(0,G.jsxs)(`section`,{className:`space-y-2`,children:[(0,G.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,G.jsx)(`h3`,{className:`text-sm font-medium`,children:`Codex Accounts`}),(0,G.jsx)(`span`,{className:`text-[10px] text-muted-foreground capitalize`,children:n.strategy.replace(`-`,` `)})]}),n.loading&&n.accounts.length===0?(0,G.jsxs)(`div`,{className:`flex items-center gap-2 py-6 text-xs text-muted-foreground`,children:[(0,G.jsx)(s,{className:`size-4 animate-spin`}),` Loading accounts...`]}):n.accounts.length===0?(0,G.jsxs)(`div`,{className:`flex flex-col items-center gap-2 py-8 text-center`,children:[(0,G.jsx)(o,{className:`size-5 text-muted-foreground`}),(0,G.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`No Codex accounts yet.`}),(0,G.jsxs)(`p`,{className:`text-xs text-muted-foreground`,children:[`Chats use your default `,(0,G.jsx)(`code`,{children:`~/.codex`}),` login until you add one.`]})]}):(0,G.jsx)(`div`,{className:`space-y-2`,children:n.accounts.map(e=>{let t=n.usages[e.id]??{};return(0,G.jsxs)(pe,{"data-testid":`account-card`,"data-account-id":e.id,children:[(0,G.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,G.jsx)(`span`,{className:`text-sm font-medium truncate flex-1 min-w-0`,children:e.label}),(0,G.jsx)(`span`,{className:`text-[10px] uppercase tracking-wide text-text-subtle border border-border rounded px-1 shrink-0`,children:e.type}),e.planType&&(0,G.jsx)(`span`,{className:`text-[10px] text-text-subtle shrink-0`,children:e.planType}),(0,G.jsx)(`button`,{type:`button`,onClick:()=>void n.remove(e.id),title:`Remove account`,"aria-label":`Remove account`,className:`p-2 rounded cursor-pointer text-text-subtle hover:text-error hover:bg-surface-elevated transition-colors shrink-0`,children:(0,G.jsx)(S,{className:`size-4`})})]}),(0,G.jsxs)(`div`,{className:`space-y-2`,children:[(0,G.jsx)(U,{label:`5-Hour Session`,pct:$(t.fiveHour)}),(0,G.jsx)(U,{label:`Weekly`,pct:$(t.sevenDay)})]})]},e.id)})})]}),(0,G.jsx)(Fe,{open:e===`add`,onOpenChange:e=>t(e?`add`:null),label:n.label,onLabelChange:n.setLabel,apiKey:n.apiKey,onApiKeyChange:n.setApiKey,adding:n.adding,onAddApiKey:()=>void n.addApiKey(),deviceWaiting:n.deviceWaiting,onStartDevice:()=>void n.startDevice(),device:n.device,error:n.err}),(0,G.jsx)(Q,{mode:e===`export`?`export`:e===`import`?`import`:null,onOpenChange:e=>{e||t(null)},password:n.backupPassword,onPasswordChange:n.setBackupPassword,busy:n.exporting||n.importing,onExport:()=>void n.doExport(),onImport:e=>void n.doImport(e),error:n.err}),(0,G.jsx)(Le,{open:e===`rotation`,onOpenChange:e=>t(e?`rotation`:null),strategy:n.strategy,onChange:e=>void n.changeStrategy(e)})]})}var He=[{id:`claude`,label:`Claude`},{id:`codex`,label:`Codex`}];function Ue(){let[e,t]=(0,W.useState)(null),[n,r]=(0,W.useState)(`claude`);(0,W.useEffect)(()=>{let e=!1;return re().then(n=>{e||t(new Set(Object.keys(n.providers??{})))}).catch(()=>{e||t(new Set([`claude`]))}),()=>{e=!0}},[]);let i=He.filter(t=>t.id===`claude`||e?.has(t.id)),a=i.length>1;return(0,G.jsxs)(`div`,{className:`space-y-4`,"data-testid":`accounts-pane`,"data-provider":n,children:[a&&(0,G.jsx)(`div`,{className:`flex gap-1 border-b border-border/50`,children:i.map(e=>(0,G.jsxs)(`button`,{onClick:()=>r(e.id),"data-testid":`accounts-tab-${e.id}`,"aria-current":n===e.id?`page`:void 0,className:`flex items-center gap-1.5 px-3 py-2 text-xs rounded-t transition-colors cursor-pointer ${n===e.id?`text-primary border-b-2 border-primary font-medium`:`text-text-subtle hover:text-text-secondary`}`,children:[(0,G.jsx)(N,{providerId:e.id}),e.label]},e.id))}),e===null&&!a&&(0,G.jsxs)(`div`,{className:`flex items-center gap-2 text-xs text-muted-foreground`,children:[(0,G.jsx)(s,{className:`size-3 animate-spin`}),` Checking configured providers...`]}),n===`claude`?(0,G.jsx)(Pe,{}):(0,G.jsx)(Ve,{})]})}export{Ue as AccountsSettingsSection};