@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.
Files changed (52) hide show
  1. package/bundle/dist/application/qgrid/oauth.js +5 -2
  2. package/bundle/dist/application/qgrid/qgrid.frame.js +63 -28
  3. package/bundle/dist/application/qgrid/qgrid.types.js +6 -3
  4. package/bundle/dist/application/request-log/request-log.model.js +2 -2
  5. package/bundle/dist/sonamu.config.js +3 -1
  6. package/bundle/dist/utils/providers/anthropic/anthropic-dispatcher.js +12 -7
  7. package/bundle/dist/utils/providers/anthropic/anthropic-quota.js +7 -5
  8. package/bundle/dist/utils/providers/openai/openai-callback-relay.js +86 -0
  9. package/bundle/src/application/qgrid/oauth.test.ts +28 -0
  10. package/bundle/src/application/qgrid/oauth.ts +5 -0
  11. package/bundle/src/application/qgrid/qgrid-oauth-redirect.test.ts +71 -8
  12. package/bundle/src/application/qgrid/qgrid.frame.test.ts +43 -0
  13. package/bundle/src/application/qgrid/qgrid.frame.ts +72 -26
  14. package/bundle/src/application/qgrid/qgrid.types.ts +5 -2
  15. package/bundle/src/application/request-log/request-log.model.test.ts +25 -0
  16. package/bundle/src/application/request-log/request-log.model.ts +3 -1
  17. package/bundle/src/application/sonamu.generated.http +7 -3
  18. package/bundle/src/sonamu.config.ts +2 -0
  19. package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.test.ts +36 -0
  20. package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.ts +20 -7
  21. package/bundle/src/utils/providers/anthropic/anthropic-quota.test.ts +22 -0
  22. package/bundle/src/utils/providers/anthropic/anthropic-quota.ts +16 -2
  23. package/bundle/src/utils/providers/openai/openai-callback-relay.test.ts +109 -0
  24. package/bundle/src/utils/providers/openai/openai-callback-relay.ts +105 -0
  25. package/bundle/web-dist/client/assets/{index-CokxkK2G.js → index-D208L33c.js} +3 -3
  26. package/bundle/web-dist/client/assets/{logs-Ckqp98Y3.js → logs-noN5Aka6.js} +1 -1
  27. package/bundle/web-dist/client/assets/{monit-u0pMHbyf.js → monit-Ber13xcA.js} +1 -1
  28. package/bundle/web-dist/client/assets/{routes-DobxbCAV.js → routes-3_xetpSK.js} +2 -2
  29. package/bundle/web-dist/client/assets/{settings-C8rfyi9z.js → settings-C5sHCg8X.js} +1 -1
  30. package/bundle/web-dist/client/assets/{show-FbUQYs4L.js → show-BhTgGnkc.js} +1 -1
  31. package/bundle/web-dist/client/assets/tokens-R7obwdAV.js +1 -0
  32. package/bundle/web-dist/client/assets/use-oauth-login-flow-CRAqRZXX.js +1 -0
  33. package/bundle/web-dist/client/index.html +1 -1
  34. package/bundle/web-dist/server/assets/{routes-B9X0lvjQ.js → routes-Ct3I9263.js} +3 -2
  35. package/bundle/web-dist/server/assets/{tokens-CyItE-L-.js → tokens-PR42jF1J.js} +3 -2
  36. package/bundle/web-dist/server/assets/{use-oauth-login-flow-CItVKngs.js → use-oauth-login-flow-C-33Z5HA.js} +16 -10
  37. package/bundle/web-dist/server/entry-server.generated.js +3 -3
  38. package/dist/cli.js +10 -11
  39. package/package.json +1 -1
  40. package/skills/qgrid/SKILL.md +7 -2
  41. package/skills/qgrid/references/ai-sdk-provider-contract.md +7 -0
  42. package/skills/qgrid/references/anthropic-claude-code-runtime.md +5 -1
  43. package/skills/qgrid/references/cli-env-and-server-boot.md +1 -1
  44. package/skills/qgrid/references/decision-rationale.md +3 -1
  45. package/skills/qgrid/references/docs-workflow.md +3 -3
  46. package/skills/qgrid/references/openai-codex-runtime.md +12 -5
  47. package/skills/qgrid/references/request-log-run-lifecycle.md +2 -0
  48. package/skills/qgrid/references/sonamu-api-web-flow.md +4 -2
  49. package/skills/qgrid/references/token-auth-quota-lifecycle.md +7 -2
  50. package/skills/qgrid/references/verification-and-debugging.md +19 -16
  51. package/bundle/web-dist/client/assets/tokens-DFwEavFD.js +0 -1
  52. package/bundle/web-dist/client/assets/use-oauth-login-flow-D-9h3WUt.js +0 -1
@@ -0,0 +1,105 @@
1
+ import http from "node:http";
2
+
3
+ import { getLogger } from "@logtape/logtape";
4
+
5
+ const logger = getLogger(["qgrid", "oauth"]);
6
+
7
+ /**
8
+ * OpenAI matches the Codex CLI client's redirect URIs by exact string, and only these two loopback
9
+ * callbacks are registered. Any other host, port, or path makes /oauth/authorize fail with
10
+ * `invalid_authorize_request` before the login screen renders, so qgrid cannot point the callback
11
+ * at its own configurable server port. Keep in sync with the Codex CLI allow-list (default 1455,
12
+ * fallback 1457).
13
+ */
14
+ export const OPENAI_CALLBACK_PORTS = [1455, 1457] as const;
15
+ export const OPENAI_CALLBACK_PATH = "/auth/callback";
16
+
17
+ export function openAICallbackRedirectUri(port: number): string {
18
+ return `http://localhost:${port}${OPENAI_CALLBACK_PATH}`;
19
+ }
20
+
21
+ type Relay = {
22
+ server: http.Server;
23
+ redirectUri: string;
24
+ dashboardBase: string;
25
+ expiryTimer: NodeJS.Timeout;
26
+ };
27
+
28
+ let relay: Relay | null = null;
29
+
30
+ function forward(request: http.IncomingMessage, response: http.ServerResponse): void {
31
+ const current = relay;
32
+ const incoming = new URL(request.url ?? "/", "http://localhost");
33
+ if (!current || incoming.pathname !== OPENAI_CALLBACK_PATH) {
34
+ response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
35
+ response.end("not found");
36
+ return;
37
+ }
38
+
39
+ // Only the two OAuth result fields cross over, onto a fixed loopback base. The request never
40
+ // contributes scheme, authority, or path bytes to the redirect target.
41
+ const target = new URL(OPENAI_CALLBACK_PATH, current.dashboardBase);
42
+ for (const field of ["code", "state"] as const) {
43
+ const value = incoming.searchParams.get(field);
44
+ if (value) target.searchParams.set(field, value);
45
+ }
46
+ response.writeHead(302, { Location: target.toString(), "Cache-Control": "no-store" });
47
+ response.end();
48
+ }
49
+
50
+ function listenOn(port: number): Promise<http.Server | null> {
51
+ return new Promise((resolve, reject) => {
52
+ const server = http.createServer(forward);
53
+ server.once("error", (error: NodeJS.ErrnoException) => {
54
+ server.close();
55
+ if (error.code === "EADDRINUSE" || error.code === "EACCES") resolve(null);
56
+ else reject(error);
57
+ });
58
+ // Bind loopback only, like the Codex CLI: the relay must not be reachable off-host.
59
+ server.listen(port, "127.0.0.1", () => resolve(server));
60
+ });
61
+ }
62
+
63
+ /**
64
+ * Opens a short-lived loopback listener on a registered Codex callback port and forwards the
65
+ * authorization code to qgrid own callback route. Returns the redirect URI to put in the authorize
66
+ * request. Repeated logins reuse the running listener and refresh its lifetime.
67
+ */
68
+ export async function startOpenAICallbackRelay(
69
+ dashboardBase: string,
70
+ ttlMs: number,
71
+ ports: readonly number[] = OPENAI_CALLBACK_PORTS,
72
+ ): Promise<string> {
73
+ if (relay) {
74
+ relay.dashboardBase = dashboardBase;
75
+ relay.expiryTimer.refresh();
76
+ return relay.redirectUri;
77
+ }
78
+
79
+ for (const port of ports) {
80
+ const server = await listenOn(port);
81
+ if (!server) continue;
82
+
83
+ const expiryTimer = setTimeout(() => stopOpenAICallbackRelay(), ttlMs);
84
+ expiryTimer.unref();
85
+ const address = server.address();
86
+ const boundPort = typeof address === "object" && address ? address.port : port;
87
+ relay = {
88
+ server,
89
+ redirectUri: openAICallbackRedirectUri(boundPort),
90
+ dashboardBase,
91
+ expiryTimer,
92
+ };
93
+ logger.info(`openai oauth callback relay listening on 127.0.0.1:${boundPort}`);
94
+ return relay.redirectUri;
95
+ }
96
+
97
+ throw new Error(`OPENAI_CALLBACK_PORTS_UNAVAILABLE_${ports.join("_")}`);
98
+ }
99
+
100
+ export function stopOpenAICallbackRelay(): void {
101
+ if (!relay) return;
102
+ clearTimeout(relay.expiryTimer);
103
+ relay.server.close();
104
+ relay = null;
105
+ }