@cartanova/qgrid-cli 2.7.7 → 2.8.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/bundle/dist/application/qgrid/oauth.js +5 -2
- package/bundle/dist/application/qgrid/qgrid.frame.js +63 -28
- package/bundle/dist/application/qgrid/qgrid.types.js +6 -3
- package/bundle/dist/application/request-log/request-log.model.js +2 -2
- package/bundle/dist/sonamu.config.js +3 -1
- package/bundle/dist/utils/providers/anthropic/anthropic-dispatcher.js +12 -7
- package/bundle/dist/utils/providers/anthropic/anthropic-quota.js +7 -5
- package/bundle/dist/utils/providers/openai/openai-callback-relay.js +86 -0
- package/bundle/src/application/qgrid/oauth.test.ts +28 -0
- package/bundle/src/application/qgrid/oauth.ts +5 -0
- package/bundle/src/application/qgrid/qgrid-oauth-redirect.test.ts +71 -8
- package/bundle/src/application/qgrid/qgrid.frame.test.ts +43 -0
- package/bundle/src/application/qgrid/qgrid.frame.ts +72 -26
- package/bundle/src/application/qgrid/qgrid.types.ts +5 -2
- package/bundle/src/application/request-log/request-log.model.test.ts +25 -0
- package/bundle/src/application/request-log/request-log.model.ts +3 -1
- package/bundle/src/application/sonamu.generated.http +7 -3
- package/bundle/src/sonamu.config.ts +2 -0
- package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.test.ts +36 -0
- package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.ts +20 -7
- package/bundle/src/utils/providers/anthropic/anthropic-quota.test.ts +22 -0
- package/bundle/src/utils/providers/anthropic/anthropic-quota.ts +16 -2
- package/bundle/src/utils/providers/openai/openai-callback-relay.test.ts +109 -0
- package/bundle/src/utils/providers/openai/openai-callback-relay.ts +105 -0
- package/bundle/web-dist/client/assets/{index-CokxkK2G.js → index-D208L33c.js} +3 -3
- package/bundle/web-dist/client/assets/{logs-Ckqp98Y3.js → logs-noN5Aka6.js} +1 -1
- package/bundle/web-dist/client/assets/{monit-u0pMHbyf.js → monit-Ber13xcA.js} +1 -1
- package/bundle/web-dist/client/assets/{routes-DobxbCAV.js → routes-3_xetpSK.js} +2 -2
- package/bundle/web-dist/client/assets/{settings-C8rfyi9z.js → settings-C5sHCg8X.js} +1 -1
- package/bundle/web-dist/client/assets/{show-FbUQYs4L.js → show-BhTgGnkc.js} +1 -1
- package/bundle/web-dist/client/assets/tokens-R7obwdAV.js +1 -0
- package/bundle/web-dist/client/assets/use-oauth-login-flow-CRAqRZXX.js +1 -0
- package/bundle/web-dist/client/index.html +1 -1
- package/bundle/web-dist/server/assets/{routes-B9X0lvjQ.js → routes-Ct3I9263.js} +3 -2
- package/bundle/web-dist/server/assets/{tokens-CyItE-L-.js → tokens-PR42jF1J.js} +3 -2
- package/bundle/web-dist/server/assets/{use-oauth-login-flow-CItVKngs.js → use-oauth-login-flow-C-33Z5HA.js} +16 -10
- package/bundle/web-dist/server/entry-server.generated.js +3 -3
- package/dist/cli.js +10 -11
- package/package.json +1 -1
- package/skills/qgrid/SKILL.md +7 -2
- package/skills/qgrid/references/ai-sdk-provider-contract.md +7 -0
- package/skills/qgrid/references/anthropic-claude-code-runtime.md +5 -1
- package/skills/qgrid/references/cli-env-and-server-boot.md +1 -1
- package/skills/qgrid/references/decision-rationale.md +3 -1
- package/skills/qgrid/references/docs-workflow.md +3 -3
- package/skills/qgrid/references/openai-codex-runtime.md +12 -5
- package/skills/qgrid/references/request-log-run-lifecycle.md +2 -0
- package/skills/qgrid/references/sonamu-api-web-flow.md +4 -2
- package/skills/qgrid/references/token-auth-quota-lifecycle.md +7 -2
- package/skills/qgrid/references/verification-and-debugging.md +19 -16
- package/bundle/web-dist/client/assets/tokens-DFwEavFD.js +0 -1
- package/bundle/web-dist/client/assets/use-oauth-login-flow-D-9h3WUt.js +0 -1
|
@@ -139,4 +139,4 @@ HTTP listening opens before the dispatchers finish starting, so requests can arr
|
|
|
139
139
|
## OAuth callbacks
|
|
140
140
|
|
|
141
141
|
- Anthropic OAuth: loopback dashboards use `/callback` on the qgrid server (base derived per request from `Origin`, falling back to `X-Forwarded-Host`/`Host`). Remote dashboards cannot use a public callback (client redirect-URI allowlist), so `oauthStart` returns `mode: "code"` with the console callback and the user pastes the shown `code#state` into the dashboard (`oauthComplete`). No env configuration either way; direct non-HTTP calls fall back to localhost and `PORT`.
|
|
142
|
-
- OpenAI OAuth uses
|
|
142
|
+
- OpenAI OAuth uses direct PKCE and the Codex CLI's registered loopback callback. Local dashboards complete through a temporary 1455/1457 relay; remote dashboards return `mode: "code"` and ask the user to paste the full callback URL so qgrid can exchange it directly.
|
|
@@ -140,7 +140,9 @@ Key decisions:
|
|
|
140
140
|
- The token subscriber disables statement/idle timeouts for `LISTEN` and uses reconnect backoff.
|
|
141
141
|
- `quota_threshold` is a routing guardrail, not a load balancer. It prevents an individual token from crossing a configured utilization percentage; it does not equalize traffic.
|
|
142
142
|
- Threshold values are nullable integers from 1 to 100. `0` is not valid; `100` means exclude only at full utilization.
|
|
143
|
-
- Provider criteria differ by primary window: Anthropic uses `five_hour.utilization
|
|
143
|
+
- Provider criteria differ by primary window: Anthropic uses `five_hour.utilization` plus the
|
|
144
|
+
Fable-only `seven_day_overage_included.utilization` when applicable; OpenAI uses
|
|
145
|
+
`primary.usedPercent`.
|
|
144
146
|
- Threshold gates are by token id, not token name. Names are display/logging labels and can change.
|
|
145
147
|
- Lookup failure is fail-open with logs/metrics. Hard failure happens only when usage was read successfully and the token is over threshold.
|
|
146
148
|
- OpenAI applies the gate during token selection. An affinity-preferred token over threshold must fall back to another eligible token when possible.
|
|
@@ -35,15 +35,15 @@ When a repo doc establishes or changes durable rationale for a qgrid feature, re
|
|
|
35
35
|
|
|
36
36
|
`packages/cli/skills/qgrid` is the canonical qgrid skill. The CLI package ships this directory to consuming projects. The repository-local `.agents/skills/qgrid` directory is a checked mirror for agents working in this repository; do not edit the mirror as a separate source.
|
|
37
37
|
|
|
38
|
-
-
|
|
39
|
-
-
|
|
38
|
+
- In the qgrid source repository, run `mise run qgrid-docs:sync` after changing the canonical skill.
|
|
39
|
+
- In the qgrid source repository, run `mise run qgrid-docs:check` before finishing documentation or runtime-contract work.
|
|
40
40
|
- `docs/qgrid-doc-map.json` maps each durable topic to code, canonical skill references, and the matching Notion page.
|
|
41
41
|
- Code and tests remain the source of truth. Update the skill and Notion together when a code change alters a documented contract.
|
|
42
42
|
- Keep dated operational snapshots, internal server addresses, and other environment-specific facts in Notion. Keep reusable runtime contracts in the skill.
|
|
43
43
|
|
|
44
44
|
## Retract pre-release caveats after shipping
|
|
45
45
|
|
|
46
|
-
Documenting a contract before it ships is fine, but the caveat must be retracted once it does. Wording like "not deployed yet", "based on the working tree", "migration is still untracked", or "package version is still X" becomes a false statement the moment the release lands, and `
|
|
46
|
+
Documenting a contract before it ships is fine, but the caveat must be retracted once it does. Wording like "not deployed yet", "based on the working tree", "migration is still untracked", or "package version is still X" becomes a false statement the moment the release lands, and `mise run qgrid-docs:check` will not catch it — that task validates the skill mirror and `docs/qgrid-doc-map.json` paths only, never Notion body text.
|
|
47
47
|
|
|
48
48
|
- When you add a pre-release caveat, record the retraction in the release page at the same time. Do not rely on memory.
|
|
49
49
|
- After a release, search the wiki for `미배포`, `배포되지 않았`, `working tree`, `untracked`, `아직`, and the previous version number.
|
|
@@ -8,7 +8,7 @@ Use this reference before changing OpenAI transport, concurrency, routing, promp
|
|
|
8
8
|
- Direct client and transport interface: `openai-direct-client.ts`.
|
|
9
9
|
- Request, identity headers, and normalized events: `openai-backend-protocol.ts`.
|
|
10
10
|
- SSE decoding: `openai-sse.ts`.
|
|
11
|
-
- Direct PKCE OAuth and refresh: `openai-oauth.ts`, `openai-refresh.ts`.
|
|
11
|
+
- Direct PKCE OAuth and refresh: `openai-oauth.ts`, `openai-refresh.ts`, `openai-callback-relay.ts`.
|
|
12
12
|
- Direct quota lookup: `openai-quota.ts`.
|
|
13
13
|
- Provider integration and history: `packages/api/src/application/qgrid/qgrid.dispatcher.ts`, `conv-routing.ts`.
|
|
14
14
|
|
|
@@ -51,10 +51,17 @@ Stable affinity can improve provider prompt-cache reuse only when the serialized
|
|
|
51
51
|
OpenAI browser login is implemented directly with authorization-code PKCE:
|
|
52
52
|
|
|
53
53
|
1. Generate verifier, SHA-256 challenge, and state.
|
|
54
|
-
2.
|
|
55
|
-
3.
|
|
56
|
-
4.
|
|
57
|
-
5.
|
|
54
|
+
2. Open a loopback callback relay on a port OpenAI registered for the Codex CLI client.
|
|
55
|
+
3. Build the OpenAI authorize URL with Codex CLI-compatible client, scope, originator, and simplified-flow fields.
|
|
56
|
+
4. Validate pending state on callback.
|
|
57
|
+
5. Exchange the code directly at `https://auth.openai.com/oauth/token`.
|
|
58
|
+
6. Parse account id and plan claims, then store access, refresh, and id tokens.
|
|
59
|
+
|
|
60
|
+
The redirect URI is not qgrid's own server address. OpenAI matches the Codex CLI client's redirect URIs by exact string and registers only `http://localhost:1455/auth/callback` and `http://localhost:1457/auth/callback`. Sending qgrid's configurable port instead makes `/oauth/authorize` fail with `invalid_authorize_request`, which the browser renders as a generic "Authentication Error / error_code: unknown_error" page before any login screen.
|
|
61
|
+
|
|
62
|
+
Loopback dashboards use automatic completion: `openai-callback-relay.ts` binds `127.0.0.1:1455` (falling back to `1457`) for the login window and 302-forwards only `code` and `state` to qgrid's own `/auth/callback` route. Both ports being busy — typically a running `codex login` — fails the local login start with a message naming them.
|
|
63
|
+
|
|
64
|
+
Remote dashboards use manual completion because the browser's `localhost` is not the qgrid server. Qgrid signs the authorize request with the registered `http://localhost:1455/auth/callback` URI without opening a server relay and returns `mode: "code"`. After login the browser may show a connection failure at that loopback URL; the user copies the full address-bar URL into the dashboard. `oauthComplete` validates the cached state, extracts only `code` and `state`, and performs the same token exchange and account replacement as the automatic callback.
|
|
58
65
|
|
|
59
66
|
Refresh posts the stored refresh token directly to the token endpoint, deduplicates concurrent refreshes, and persists rotated credentials. Generation sends Codex CLI identity headers built by `buildCodexIdentityHeaders`.
|
|
60
67
|
|
|
@@ -116,3 +116,5 @@ If missing, qgrid maps TTFT to `0` in `QueryOutput` and nullable/optional places
|
|
|
116
116
|
## Logger integration
|
|
117
117
|
|
|
118
118
|
`packages/ai-sdk/src/logger.ts` lets non-qgrid providers write logs into qgrid through the public lifecycle endpoints. It skips model provider `qgrid` to avoid double logging and skips any generation with `providerOptions.qgrid.logger: false`. Therefore one disabled generation produces neither native qgrid logs nor `createQgridLogger` telemetry logs. Preserve its stale-run fallback because AI SDK telemetry may emit start without a final event on provider failures.
|
|
119
|
+
|
|
120
|
+
External structured output is detected from AI SDK telemetry's `output.responseFormat`, before provider-specific request projection. The logger sends optional `isStructured` and serialized `jsonSchema` fields on `createRun`; the server stores them in the existing `is_structured` and `json_schema` columns. On a succeeded structured run, `finishRun` may also carry `responseJsonOk`, which records JSON parseability only. Omitted fields retain the legacy unstructured defaults, and the native qgrid provider continues to own its existing server-side lifecycle without telemetry duplication.
|
|
@@ -26,10 +26,12 @@ qgrid dashboard work is usually a Sonamu API/model/generated-client/web change,
|
|
|
26
26
|
## Migration workflow
|
|
27
27
|
|
|
28
28
|
Sonamu entity definitions are the source of truth for managed database schema.
|
|
29
|
+
These commands apply only inside the qgrid source repository. Downstream projects must use
|
|
30
|
+
their own configured task and tool runner.
|
|
29
31
|
|
|
30
32
|
1. Change the relevant `*.entity.json` files.
|
|
31
|
-
2. Run `pnpm --dir packages/api sonamu migrate status` and inspect `preparedCodes`.
|
|
32
|
-
3. Run `pnpm --dir packages/api sonamu migrate generate`.
|
|
33
|
+
2. Run `mise exec -- pnpm --dir packages/api sonamu migrate status` and inspect `preparedCodes`.
|
|
34
|
+
3. Run `mise exec -- pnpm --dir packages/api sonamu migrate generate`.
|
|
33
35
|
4. Inspect the generated, table-scoped migration files without rewriting them by hand.
|
|
34
36
|
5. Apply them through Sonamu's migration target flow or qgrid's startup migration runner.
|
|
35
37
|
6. Re-run migration status and require `pending: []` and `preparedCodes: []`.
|
|
@@ -113,7 +113,8 @@ Usage/quota:
|
|
|
113
113
|
|
|
114
114
|
- Usage API is `https://api.anthropic.com/api/oauth/usage`.
|
|
115
115
|
- qgrid caches Anthropic usage by access-token suffix for 60 seconds.
|
|
116
|
-
- Quota threshold uses `five_hour.utilization
|
|
116
|
+
- Quota threshold uses `five_hour.utilization`; Fable 5 additionally uses the model-specific
|
|
117
|
+
`seven_day_overage_included.utilization`, and either window can exclude the token.
|
|
117
118
|
- Usage lookup failure is fail-open for routing.
|
|
118
119
|
|
|
119
120
|
## OpenAI OAuth And Refresh Flow
|
|
@@ -129,6 +130,8 @@ Browser registration:
|
|
|
129
130
|
5. Existing OpenAI tokens with the same `accountId` are deleted.
|
|
130
131
|
6. qgrid saves the access, refresh, and id tokens as a new `provider: "openai"` token.
|
|
131
132
|
|
|
133
|
+
Completion follows the request origin. Loopback dashboards use a temporary relay on the Codex client's registered 1455/1457 callback ports. Remote dashboards cannot reach that server-side loopback, so qgrid returns `mode: "code"`; after authentication the user pastes the full `localhost:1455/auth/callback?code=...&state=...` URL into the dashboard, and `oauthComplete` validates the pending state and performs the same exchange.
|
|
134
|
+
|
|
132
135
|
Refresh:
|
|
133
136
|
|
|
134
137
|
- qgrid posts the stored refresh token directly to the OpenAI token endpoint through `handleChatgptAuthTokensRefresh(tokenId)`.
|
|
@@ -232,7 +235,9 @@ When a refresh fails in a way that only re-login can fix, qgrid removes the toke
|
|
|
232
235
|
- A targeted request never falls back. Missing, inactive, or over-threshold targets fail rather than silently serving from another account — the whole point of addressing a token is that the answer comes from that account.
|
|
233
236
|
- Targeted selection does not consume weighted round-robin state. Without that guarantee, background targeting such as keepalive would skew the token distribution of ordinary traffic.
|
|
234
237
|
- Targeting is distinct from cache affinity. Affinity sets `preferredTokenId` as a preference and still falls back when the token is ineligible; explicit targeting adds `requirePreferredToken` so the same field becomes strict. The Anthropic path only ever receives explicit targeting — affinity coordinates are not forwarded there, so making it strict cannot break affinity behavior.
|
|
235
|
-
- Exposure boundary:
|
|
238
|
+
- Exposure boundary: raw qgrid callers and AI SDK consumers can pass `tokenName`; the AI SDK sends
|
|
239
|
+
it from `providerOptions.qgrid.tokenName` on both generate and stream. Callers that omit it keep
|
|
240
|
+
weighted round-robin routing, while an explicitly empty SDK value fails before transport.
|
|
236
241
|
|
|
237
242
|
## Token Window Keepalive
|
|
238
243
|
|
|
@@ -12,36 +12,39 @@ Use this reference to choose tests, smoke scripts, and first debugging targets f
|
|
|
12
12
|
|
|
13
13
|
## Package Scripts
|
|
14
14
|
|
|
15
|
+
The commands in this section apply only inside the qgrid source repository. In a downstream
|
|
16
|
+
project, use that project's configured task and tool runner.
|
|
17
|
+
|
|
15
18
|
Root:
|
|
16
19
|
|
|
17
|
-
- `
|
|
18
|
-
- `
|
|
19
|
-
- `
|
|
20
|
+
- `mise run check`: qgrid docs consistency, oxlint, and oxfmt check for the repo.
|
|
21
|
+
- `mise run build`: recursive package build.
|
|
22
|
+
- `mise run dev`: parallel package dev servers.
|
|
20
23
|
|
|
21
24
|
API package `qgrid-api`:
|
|
22
25
|
|
|
23
|
-
- `pnpm --filter qgrid-api test`: Vitest run.
|
|
24
|
-
- `pnpm --filter qgrid-api test:watch`: standalone watch mode.
|
|
25
|
-
- `pnpm --filter qgrid-api build`: Sonamu build.
|
|
26
|
-
- `pnpm --filter qgrid-api sonamu`: run Sonamu CLI.
|
|
26
|
+
- `mise exec -- pnpm --filter qgrid-api test`: Vitest run.
|
|
27
|
+
- `mise exec -- pnpm --filter qgrid-api test:watch`: standalone watch mode.
|
|
28
|
+
- `mise exec -- pnpm --filter qgrid-api build`: Sonamu build.
|
|
29
|
+
- `mise exec -- pnpm --filter qgrid-api sonamu`: run Sonamu CLI.
|
|
27
30
|
|
|
28
31
|
AI SDK package `@cartanova/qgrid-ai-sdk`:
|
|
29
32
|
|
|
30
|
-
- `pnpm --filter @cartanova/qgrid-ai-sdk test`.
|
|
31
|
-
- `pnpm --filter @cartanova/qgrid-ai-sdk build`.
|
|
32
|
-
- `pnpm --filter @cartanova/qgrid-ai-sdk e2e`.
|
|
33
|
-
- `pnpm --filter @cartanova/qgrid-ai-sdk e2e:tools-output`.
|
|
34
|
-
- `pnpm --filter @cartanova/qgrid-ai-sdk e2e:logger`.
|
|
33
|
+
- `mise exec -- pnpm --filter @cartanova/qgrid-ai-sdk test`.
|
|
34
|
+
- `mise exec -- pnpm --filter @cartanova/qgrid-ai-sdk build`.
|
|
35
|
+
- `mise exec -- pnpm --filter @cartanova/qgrid-ai-sdk e2e`.
|
|
36
|
+
- `mise exec -- pnpm --filter @cartanova/qgrid-ai-sdk e2e:tools-output`.
|
|
37
|
+
- `mise exec -- pnpm --filter @cartanova/qgrid-ai-sdk e2e:logger`.
|
|
35
38
|
|
|
36
39
|
CLI package `@cartanova/qgrid-cli`:
|
|
37
40
|
|
|
38
|
-
- `pnpm --filter @cartanova/qgrid-cli build`.
|
|
39
|
-
- `pnpm --filter @cartanova/qgrid-cli bundle` builds the API first and copies the server bundle.
|
|
41
|
+
- `mise exec -- pnpm --filter @cartanova/qgrid-cli build`.
|
|
42
|
+
- `mise exec -- pnpm --filter @cartanova/qgrid-cli bundle` builds the API first and copies the server bundle.
|
|
40
43
|
|
|
41
44
|
Web package `qgrid-web`:
|
|
42
45
|
|
|
43
|
-
- `pnpm --filter qgrid-web dev`.
|
|
44
|
-
- `pnpm --filter qgrid-web preview`.
|
|
46
|
+
- `mise exec -- pnpm --filter qgrid-web dev`.
|
|
47
|
+
- `mise exec -- pnpm --filter qgrid-web preview`.
|
|
45
48
|
|
|
46
49
|
The old v1 SDK package (`packages/sdk`) has been removed from the repository; all public-SDK verification goes through `packages/ai-sdk`.
|
|
47
50
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as e}from"./rolldown-runtime-Dw2cE7zH.js";import{y as t}from"./vendor-tanstack-DJRQAbrY.js";import{i as n,r}from"./vendor-react-DH4kUfK2.js";import{n as i,o as a}from"./services.generated-Da1Eq5_R.js";import{i as o,u as s}from"./index-CokxkK2G.js";import{t as c}from"./use-update-token-mutation-CZ57EKA1.js";import{n as l,t as u}from"./use-oauth-login-flow-D-9h3WUt.js";import{t as d}from"./plus-p4vtg6LN.js";var f=e(n(),1),p=r();function m(){let[e,t]=(0,f.useState)(!1),[n,r]=(0,f.useState)(``),i=u(),a=()=>{t(!1),r(``),i.reset()},c=async e=>{await i.submitCode(e)&&a()};return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)(`button`,{type:`button`,className:`px-3.5 py-1.5 text-[12px] font-medium rounded-lg bg-sienna-500 text-white hover:bg-sienna-600 disabled:opacity-50 transition-all duration-150 active:scale-[0.97] flex items-center gap-1.5 shadow-sm`,onClick:()=>t(!0),children:[(0,p.jsx)(d,{className:`size-3.5`}),`Add Token`]}),e&&(0,p.jsxs)(`div`,{className:`fixed inset-0 z-50 flex items-start justify-center pt-[15vh]`,children:[(0,p.jsx)(`div`,{className:`absolute inset-0 bg-sand-900/8 backdrop-blur-sm`,onClick:a,onKeyDown:()=>{}}),(0,p.jsxs)(`div`,{className:`relative panel shadow-xl w-full max-w-md mx-4`,children:[(0,p.jsx)(`div`,{className:`px-5 py-4 border-b border-sand-100/50`,children:(0,p.jsx)(`h2`,{className:`text-base font-medium text-sand-900`,children:`Add Token`})}),(0,p.jsxs)(`div`,{className:`px-5 py-4 space-y-4`,children:[(0,p.jsxs)(`div`,{children:[(0,p.jsx)(`label`,{htmlFor:`token-name`,className:`text-[10px] uppercase tracking-wider text-sand-500 font-medium`,children:`Name *`}),(0,p.jsx)(o,{value:n,onChange:e=>r(e.target.value),placeholder:`e.g. your-token-name`,className:`mt-1 w-full border border-sand-200 rounded-md px-3 py-2 text-sm text-sand-900 bg-white placeholder:text-sand-300 focus:outline-none focus:border-sienna-300`})]}),i.codeEntry?(0,p.jsx)(l,{isPending:i.completeMutation.isPending,isError:i.completeMutation.isError,onSubmit:e=>void c(e),onRestart:i.reset}):(0,p.jsxs)(`div`,{className:`space-y-2`,children:[(0,p.jsx)(`button`,{type:`button`,className:`w-full py-2.5 text-sm font-medium rounded-md bg-sand-900 text-white hover:bg-sand-800 disabled:opacity-50 transition-colors duration-150 flex items-center justify-center gap-2`,disabled:!n.trim()||i.loadingProvider!==null,onClick:()=>void i.start(`openai`,n),children:i.loadingProvider===`openai`?(0,p.jsxs)(`span`,{className:`flex items-center gap-1.5`,children:[(0,p.jsx)(`span`,{className:`w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin`}),`Waiting for login...`]}):(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)(s,{className:`size-4`}),`Login with OpenAI`]})}),(0,p.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,p.jsx)(`div`,{className:`flex-1 h-px bg-sand-200`}),(0,p.jsx)(`span`,{className:`text-[10px] text-sand-400 uppercase`,children:`or`}),(0,p.jsx)(`div`,{className:`flex-1 h-px bg-sand-200`})]}),(0,p.jsx)(`button`,{type:`button`,className:`w-full py-2.5 text-sm font-medium rounded-md border border-sand-300 text-sand-700 hover:bg-sand-100 disabled:opacity-50 transition-colors duration-150 flex items-center justify-center gap-2`,disabled:!n.trim()||i.loadingProvider!==null,onClick:()=>void i.start(`anthropic`,n),children:i.loadingProvider===`anthropic`?(0,p.jsxs)(`span`,{className:`flex items-center gap-1.5`,children:[(0,p.jsx)(`span`,{className:`w-3.5 h-3.5 border-2 border-sand-400 border-t-transparent rounded-full animate-spin`}),`Waiting for login...`]}):(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)(s,{className:`size-4`}),`Login with Claude`]})})]})]}),(0,p.jsx)(`div`,{className:`px-5 py-3 border-t border-sand-100/50 flex items-center justify-end gap-2`,children:(0,p.jsx)(`button`,{type:`button`,className:`px-3 py-1 text-xs font-medium rounded-md border border-sand-200 text-sand-600 hover:bg-sand-100 transition-colors duration-150`,onClick:a,children:`Cancel`})})]})]})]})}var h=e=>(0,p.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`1.2em`,height:`1.2em`,...e,children:(0,p.jsx)(`path`,{fill:`none`,stroke:`currentColor`,strokeLinecap:`round`,strokeLinejoin:`round`,strokeWidth:2,d:`M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497zM15 5l4 4`})}),g=e=>(0,p.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`1.2em`,height:`1.2em`,...e,children:(0,p.jsx)(`path`,{fill:`none`,stroke:`currentColor`,strokeLinecap:`round`,strokeLinejoin:`round`,strokeWidth:2,d:`M10 11v6m4-6v6m5-11v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2`})});function _(e){let t=e.credentials;return`expiresAt`in t&&typeof t.expiresAt==`number`?t.expiresAt:`accessTokenExpiresAt`in t&&typeof t.accessTokenExpiresAt==`number`?t.accessTokenExpiresAt:null}function v(e){if(!e)return{text:``,color:``};let t=e-Date.now();if(t<=0)return{text:`expired`,color:`text-red-500`};let n=Math.floor(t/36e5),r=Math.floor(t%36e5/6e4),i=n>0?`${n}h ${r}m`:`${r}m`;return t<36e5?{text:i,color:`text-amber-500`}:{text:i,color:`text-sand-400`}}function y(e){let t=e.credentials.accessToken;return t.length<=12?t:`${t.slice(0,8)}...${t.slice(-4)}`}function b({data:e,isLoading:n}){let[r,a]=(0,f.useState)(null),[s,l]=(0,f.useState)(null),[u,d]=(0,f.useState)(``),m=t(),b=i.useRemoveTokenMutation(),x=c(),S=i.useToggleTokenMutation(),C=()=>Promise.all([m.invalidateQueries({queryKey:[`Token`]}),m.invalidateQueries({queryKey:[`Qgrid`]})]),w=async e=>{await S.mutateAsync({id:e.id}),await C()},T=async()=>{r&&(await b.mutateAsync({id:r.id}),await C(),a(null))},E=e=>{l(e),d(e.name??``)},D=async()=>{s&&(await x.mutateAsync({id:s.id,name:u.trim()}),await C(),l(null))};if(n)return(0,p.jsx)(`div`,{className:`space-y-2`,children:Array.from({length:3}).map((e,t)=>(0,p.jsx)(`div`,{className:`h-12 bg-sand-100 rounded-md animate-pulse`},`skeleton-${t}`))});let O=e??[];return O.length===0?(0,p.jsxs)(`div`,{className:`text-sand-400 text-center py-16 text-sm`,children:[`No tokens registered. Click `,(0,p.jsx)(`strong`,{className:`text-sand-600`,children:`Add Token`}),` to get started.`]}):(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)(`div`,{className:`panel overflow-hidden`,children:(0,p.jsx)(`div`,{className:`overflow-x-auto`,children:(0,p.jsxs)(`table`,{className:`w-full min-w-[640px] text-[13px]`,children:[(0,p.jsx)(`thead`,{children:(0,p.jsxs)(`tr`,{className:`panel-header`,children:[(0,p.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Provider`}),(0,p.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Name`}),(0,p.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Token`}),(0,p.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Status`}),(0,p.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Expires`}),(0,p.jsx)(`th`,{className:`w-20 px-3 py-2.5`})]})}),(0,p.jsx)(`tbody`,{className:`divide-y divide-sand-100/80`,children:O.map(e=>{let t=v(_(e));return(0,p.jsxs)(`tr`,{className:`transition-colors duration-150 hover:bg-sand-100/60 ${e.active?``:`opacity-50`}`,children:[(0,p.jsx)(`td`,{className:`px-5 py-3`,children:(0,p.jsx)(`span`,{className:`text-xs font-medium text-sand-500 uppercase`,children:e.provider})}),(0,p.jsx)(`td`,{className:`px-5 py-3`,children:e.name?(0,p.jsx)(`span`,{className:`text-sm text-sand-700`,children:e.name}):(0,p.jsx)(`span`,{className:`text-[11px] text-sand-400`,children:`—`})}),(0,p.jsx)(`td`,{className:`px-5 py-3`,children:(0,p.jsx)(`code`,{className:`text-[13px] font-mono text-sand-800`,children:y(e)})}),(0,p.jsx)(`td`,{className:`px-5 py-3`,children:(0,p.jsx)(`button`,{type:`button`,className:`relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-200 ${e.active?`bg-sage-400`:`bg-sand-300`}`,onClick:()=>w(e),disabled:S.isPending,children:(0,p.jsx)(`span`,{className:`inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform duration-200 ${e.active?`translate-x-4.5`:`translate-x-0.5`}`})})}),(0,p.jsx)(`td`,{className:`px-5 py-3`,children:t.text&&(0,p.jsx)(`span`,{className:`text-xs ${t.color}`,children:t.text})}),(0,p.jsx)(`td`,{className:`px-3 py-3`,children:(0,p.jsxs)(`div`,{className:`flex items-center gap-1`,children:[(0,p.jsx)(`button`,{type:`button`,className:`p-1 rounded text-sand-400 hover:text-sienna-500 transition-colors duration-150`,onClick:()=>E(e),children:(0,p.jsx)(h,{className:`size-4`})}),(0,p.jsx)(`button`,{type:`button`,className:`p-1 rounded text-sand-400 hover:text-red-500 transition-colors duration-150`,onClick:()=>a(e),children:(0,p.jsx)(g,{className:`size-4`})})]})})]},e.id)})})]})})}),s&&(0,p.jsxs)(`div`,{className:`fixed inset-0 z-50 flex items-start justify-center pt-[15vh]`,children:[(0,p.jsx)(`div`,{className:`absolute inset-0 bg-sand-900/8 backdrop-blur-sm`,onClick:()=>l(null),onKeyDown:()=>{}}),(0,p.jsxs)(`div`,{className:`relative panel shadow-xl w-full max-w-sm mx-4`,children:[(0,p.jsx)(`div`,{className:`px-5 py-4 border-b border-sand-100/60`,children:(0,p.jsx)(`h3`,{className:`text-base font-medium text-sand-900`,children:`Edit Token`})}),(0,p.jsx)(`div`,{className:`px-5 py-4 space-y-3`,children:(0,p.jsxs)(`div`,{children:[(0,p.jsx)(`label`,{htmlFor:`token-name`,className:`text-[10px] uppercase tracking-wider text-sand-500 font-medium`,children:`Name`}),(0,p.jsx)(o,{value:u,onChange:e=>d(e.target.value),placeholder:`e.g. prod, dev, team-a`,className:`mt-1 w-full border border-sand-200 rounded-md px-3 py-2 text-sm text-sand-900 bg-white placeholder:text-sand-300 focus:outline-none focus:border-sienna-300`})]})}),(0,p.jsxs)(`div`,{className:`px-5 py-3 border-t border-sand-100/60 flex items-center justify-end gap-2`,children:[(0,p.jsx)(`button`,{type:`button`,className:`px-3 py-1 text-xs font-medium rounded-md border border-sand-200 text-sand-600 hover:bg-sand-100 transition-colors duration-150`,onClick:()=>l(null),children:`Cancel`}),(0,p.jsx)(`button`,{type:`button`,className:`px-3 py-1 text-xs font-medium rounded-md bg-sienna-400 text-white hover:bg-sienna-500 disabled:opacity-50 transition-colors duration-150`,disabled:x.isPending,onClick:D,children:x.isPending?`Saving...`:`Save`})]})]})]}),r&&(0,p.jsxs)(`div`,{className:`fixed inset-0 z-50 flex items-start justify-center pt-[15vh]`,children:[(0,p.jsx)(`div`,{className:`absolute inset-0 bg-sand-900/8 backdrop-blur-sm`,onClick:()=>a(null),onKeyDown:()=>{}}),(0,p.jsxs)(`div`,{className:`relative panel shadow-xl w-full max-w-sm mx-4`,children:[(0,p.jsxs)(`div`,{className:`px-5 py-4`,children:[(0,p.jsx)(`h3`,{className:`text-base font-medium text-sand-900`,children:`Remove Token`}),(0,p.jsxs)(`p`,{className:`text-sm text-sand-700 mt-2`,children:[`Are you sure you want to remove`,` `,(0,p.jsx)(`code`,{className:`text-[13px] font-mono text-sand-800`,children:y(r)}),r.name&&(0,p.jsxs)(`span`,{className:`text-sand-500`,children:[` (`,r.name,`)`]}),`?`]})]}),(0,p.jsxs)(`div`,{className:`px-5 py-3 border-t border-sand-100/60 flex items-center justify-end gap-2`,children:[(0,p.jsx)(`button`,{type:`button`,className:`px-3 py-1 text-xs font-medium rounded-md border border-sand-200 text-sand-600 hover:bg-sand-100 transition-colors duration-150`,onClick:()=>a(null),children:`Cancel`}),(0,p.jsx)(`button`,{type:`button`,className:`px-3 py-1 text-xs font-medium rounded-md bg-red-500 text-white hover:bg-red-600 disabled:opacity-50 transition-colors duration-150`,disabled:b.isPending,onClick:T,children:b.isPending?`Removing...`:`Remove`})]})]})]})]})}function x(){let{data:e,isLoading:t}=a.useTokens(`A`);return(0,p.jsxs)(`div`,{className:`max-w-6xl mx-auto space-y-4`,children:[(0,p.jsx)(`div`,{className:`flex items-center justify-end`,children:(0,p.jsx)(m,{})}),(0,p.jsx)(b,{data:e?.rows,isLoading:t})]})}export{x as component};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as e}from"./rolldown-runtime-Dw2cE7zH.js";import{y as t}from"./vendor-tanstack-DJRQAbrY.js";import{i as n,r}from"./vendor-react-DH4kUfK2.js";import{n as i}from"./services.generated-Da1Eq5_R.js";import{i as a}from"./index-CokxkK2G.js";var o=e(n(),1),s=r();function c({isPending:e,isError:t,onSubmit:n,onRestart:r}){let[i,c]=(0,o.useState)(``);return(0,s.jsxs)(`div`,{className:`space-y-2`,children:[(0,s.jsx)(`p`,{className:`text-[12px] text-sand-600 leading-relaxed`,children:`새 탭에서 Claude 로그인을 완료하면 코드가 표시됩니다. 코드 전체를 아래에 붙여넣으세요.`}),(0,s.jsx)(a,{value:i,onChange:e=>c(e.target.value),placeholder:`code#state`,className:`w-full border border-sand-200 rounded-md px-3 py-2 text-sm font-mono text-sand-900 bg-white placeholder:text-sand-300 focus:outline-none focus:border-sienna-300`}),t&&(0,s.jsx)(`p`,{className:`text-[11px] text-red-500`,children:`코드 확인에 실패했습니다 — 표시된 코드 전체를 다시 붙여넣거나 로그인을 다시 시작하세요.`}),(0,s.jsx)(`button`,{type:`button`,className:`w-full py-2.5 text-sm font-medium rounded-md bg-sand-900 text-white hover:bg-sand-800 disabled:opacity-50 transition-colors duration-150 flex items-center justify-center gap-2`,disabled:!i.trim()||e,onClick:()=>n(i),children:e?(0,s.jsxs)(`span`,{className:`flex items-center gap-1.5`,children:[(0,s.jsx)(`span`,{className:`w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin`}),`Verifying...`]}):`Complete login`}),(0,s.jsx)(`button`,{type:`button`,className:`w-full py-1.5 text-[12px] text-sand-500 hover:text-sand-700 transition-colors duration-150`,onClick:()=>{c(``),r()},children:`처음부터 다시 시작`})]})}var l=3e3,u=3e5;function d(e){let t=0;for(let n of e.getQueryCache().findAll({queryKey:[`Token`]})){let e=n.state.data?.rows;Array.isArray(e)&&(t=Math.max(t,e.length))}return t}function f(){let[e,n]=(0,o.useState)(null),[r,a]=(0,o.useState)(!1),s=t(),c=i.useOauthStartMutation(),f=i.useOauthStartOpenAIMutation(),p=i.useOauthCompleteMutation(),m=(0,o.useRef)({}),h=()=>{m.current.interval!==void 0&&clearInterval(m.current.interval),m.current.timeout!==void 0&&clearTimeout(m.current.timeout),m.current={}};(0,o.useEffect)(()=>h,[]);let g=()=>Promise.all([s.invalidateQueries({queryKey:[`Token`]}),s.invalidateQueries({queryKey:[`Qgrid`]})]);return{loadingProvider:e,codeEntry:r,start:async(e,t)=>{let r=t.trim();if(!r)return;let i=window.open(`about:blank`,`_blank`);n(e);try{if(e===`openai`){let{authUrl:e}=await f.mutateAsync({name:r});i?i.location.href=e:window.open(e,`_blank`);let t=d(s);h(),m.current.interval=window.setInterval(()=>{g().then(()=>{d(s)>t&&(h(),n(null))})},l),m.current.timeout=window.setTimeout(()=>{h(),n(null)},u);return}let{authUrl:t,mode:o}=await c.mutateAsync({name:r});o===`code`?(i?i.location.href=t:window.open(t,`_blank`),a(!0),n(null)):(i?.close(),window.location.href=t)}catch(e){i?.close(),console.error(`OAuth start failed:`,e),n(null)}},submitCode:async e=>{let t=e.trim();if(!t)return!1;try{await p.mutateAsync({pastedCode:t})}catch(e){return console.error(`OAuth complete failed:`,e),!1}return await g(),!0},reset:()=>{h(),n(null),a(!1),p.reset()},completeMutation:p}}export{c as n,f as t};
|