@cartanova/qgrid-cli 2.5.10 → 2.6.1

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 (54) hide show
  1. package/README.ko.md +0 -1
  2. package/README.md +0 -1
  3. package/bundle/dist/application/qgrid/oauth.js +3 -2
  4. package/bundle/dist/application/qgrid/qgrid-run-lifecycle.js +2 -1
  5. package/bundle/dist/application/qgrid/qgrid.frame.js +90 -24
  6. package/bundle/dist/application/qgrid/qgrid.types.js +5 -2
  7. package/bundle/dist/application/request-log/request-log.model.js +3 -2
  8. package/bundle/dist/application/request-log/request-log.types.js +4 -2
  9. package/bundle/dist/application/sonamu.generated.js +8 -1
  10. package/bundle/dist/application/sonamu.generated.sso.js +2 -1
  11. package/bundle/dist/i18n/sd.generated.js +2 -1
  12. package/bundle/dist/migrations/20260806141701_alter_request_logs_add1_alter1.js +15 -0
  13. package/bundle/src/application/qgrid/oauth.ts +3 -0
  14. package/bundle/src/application/qgrid/qgrid-oauth-redirect.test.ts +151 -0
  15. package/bundle/src/application/qgrid/qgrid-run-lifecycle.test.ts +14 -0
  16. package/bundle/src/application/qgrid/qgrid-run-lifecycle.ts +3 -0
  17. package/bundle/src/application/qgrid/qgrid.frame.ts +110 -38
  18. package/bundle/src/application/qgrid/qgrid.types.ts +3 -0
  19. package/bundle/src/application/request-log/request-log.entity.json +8 -0
  20. package/bundle/src/application/request-log/request-log.model.test.ts +3 -1
  21. package/bundle/src/application/request-log/request-log.model.ts +7 -1
  22. package/bundle/src/application/request-log/request-log.types.ts +5 -0
  23. package/bundle/src/application/sonamu.generated.http +10 -2
  24. package/bundle/src/application/sonamu.generated.sso.ts +1 -0
  25. package/bundle/src/application/sonamu.generated.ts +13 -0
  26. package/bundle/src/i18n/sd.generated.ts +1 -0
  27. package/bundle/src/migrations/20260806141701_alter_request_logs_add1_alter1.ts +15 -0
  28. package/bundle/web-dist/client/assets/{index-SdI2T3b8.js → index-BGGBFmzE.js} +3 -3
  29. package/bundle/web-dist/client/assets/index-GA9b-zy4.css +1 -0
  30. package/bundle/web-dist/client/assets/{logs-O-8hGStp.js → logs-Dh19u6Tk.js} +1 -1
  31. package/bundle/web-dist/client/assets/{monit-DA8MAY-d.js → monit-CyqMmy1G.js} +1 -1
  32. package/bundle/web-dist/client/assets/{routes-COPjkgNr.js → routes-Cy0gCq8c.js} +1 -1
  33. package/bundle/web-dist/client/assets/{sd.generated-DbBC73_z.js → sd.generated-CjW3rfJ8.js} +1 -1
  34. package/bundle/web-dist/client/assets/{services.generated-BWyAypSh.js → services.generated-BuT6Gp8V.js} +1 -1
  35. package/bundle/web-dist/client/assets/show-C5WxfoAt.js +30 -0
  36. package/bundle/web-dist/client/assets/tokens-Ciolqw4l.js +1 -0
  37. package/bundle/web-dist/client/assets/{use-update-token-mutation-DeDBiZiM.js → use-update-token-mutation-DBkxL29Y.js} +1 -1
  38. package/bundle/web-dist/client/index.html +3 -3
  39. package/bundle/web-dist/server/assets/{lazyRouteComponent-BCJr868T.js → lazyRouteComponent-PRxXWW3t.js} +1 -1
  40. package/bundle/web-dist/server/assets/{logs-33YNafdW.js → logs-D0z1Ghj1.js} +3 -3
  41. package/bundle/web-dist/server/assets/{monit--9mVsjNU.js → monit-dBXxnAyI.js} +2 -2
  42. package/bundle/web-dist/server/assets/{routes-BK3Xh1UB.js → routes-B3p7fuwk.js} +3 -3
  43. package/bundle/web-dist/server/assets/{sd.generated-C3gcvRcU.js → sd.generated-ek-tz-7H.js} +1 -0
  44. package/bundle/web-dist/server/assets/{services.generated-B2_Auwvk.js → services.generated-BNuxWs7K.js} +10 -1
  45. package/bundle/web-dist/server/assets/{show-Dxxxe608.js → show-0iNioJ2I.js} +39 -13
  46. package/bundle/web-dist/server/assets/{tokens-BWPVvdfi.js → tokens-CvytQ9Xy.js} +86 -18
  47. package/bundle/web-dist/server/assets/{use-update-token-mutation-CNBgTysy.js → use-update-token-mutation-BBCC2GKM.js} +1 -1
  48. package/bundle/web-dist/server/entry-server.generated.js +8 -8
  49. package/package.json +1 -1
  50. package/skills/qgrid/references/cli-env-and-server-boot.md +1 -2
  51. package/skills/qgrid/references/token-auth-quota-lifecycle.md +5 -1
  52. package/bundle/web-dist/client/assets/index-B-cZKEyf.css +0 -1
  53. package/bundle/web-dist/client/assets/show-ClG4e0BP.js +0 -30
  54. package/bundle/web-dist/client/assets/tokens-Cc5frn5f.js +0 -1
@@ -1,6 +1,6 @@
1
- import { _ as require_jsx_runtime, d as string, l as number, u as object, v as require_react } from "./sd.generated-C3gcvRcU.js";
2
- import { a as TokenService, n as QgridService, r as RequestLogService } from "./services.generated-B2_Auwvk.js";
3
- import { a as Link, o as useNavigate } from "./lazyRouteComponent-BCJr868T.js";
1
+ import { _ as require_jsx_runtime, d as string, l as number, u as object, v as require_react } from "./sd.generated-ek-tz-7H.js";
2
+ import { a as TokenService, n as QgridService, r as RequestLogService } from "./services.generated-BNuxWs7K.js";
3
+ import { a as Link, o as useNavigate } from "./lazyRouteComponent-PRxXWW3t.js";
4
4
  import { n as Route } from "../entry-server.generated.js";
5
5
  import { n as formatMicroUsd, r as formatUsd, t as cacheHitRate } from "./cost-CSH_t17I.js";
6
6
  require_react();
@@ -1,5 +1,5 @@
1
- import { Z as __toESM, _ as require_jsx_runtime, c as _enum, p as require_react_dom, u as object, v as require_react } from "./sd.generated-C3gcvRcU.js";
2
- import { s as useQuery, t as MonitService } from "./services.generated-B2_Auwvk.js";
1
+ import { Z as __toESM, _ as require_jsx_runtime, c as _enum, p as require_react_dom, u as object, v as require_react } from "./sd.generated-ek-tz-7H.js";
2
+ import { s as useQuery, t as MonitService } from "./services.generated-BNuxWs7K.js";
3
3
  import { a as clsx, r as Route } from "../entry-server.generated.js";
4
4
  //#region ../../node_modules/.pnpm/@tanstack+virtual-core@3.17.4/node_modules/@tanstack/virtual-core/dist/esm/lazy-measurements.js
5
5
  var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
@@ -1,8 +1,8 @@
1
- import { Z as __toESM, _ as require_jsx_runtime, g as useQueryClient, p as require_react_dom, v as require_react } from "./sd.generated-C3gcvRcU.js";
2
- import { a as TokenService, n as QgridService } from "./services.generated-B2_Auwvk.js";
1
+ import { Z as __toESM, _ as require_jsx_runtime, g as useQueryClient, p as require_react_dom, v as require_react } from "./sd.generated-ek-tz-7H.js";
2
+ import { a as TokenService, n as QgridService } from "./services.generated-BNuxWs7K.js";
3
3
  import { i as Q } from "../entry-server.generated.js";
4
4
  import { r as formatUsd } from "./cost-CSH_t17I.js";
5
- import { t as useUpdateTokenMutation } from "./use-update-token-mutation-CNBgTysy.js";
5
+ import { t as useUpdateTokenMutation } from "./use-update-token-mutation-BBCC2GKM.js";
6
6
  //#region ../../node_modules/.pnpm/@dnd-kit+utilities@3.2.2_react@19.2.4/node_modules/@dnd-kit/utilities/dist/utilities.esm.js
7
7
  var import_react_dom = /* @__PURE__ */ __toESM(require_react_dom());
8
8
  var import_react = /* @__PURE__ */ __toESM(require_react());
@@ -22558,6 +22558,7 @@ var entityLabels = {
22558
22558
  "entity.RequestLog.image_cost_method": "이미지 비용 추정 방식",
22559
22559
  "entity.RequestLog.effort": "effort level",
22560
22560
  "entity.RequestLog.history": "inject_items 히스토리",
22561
+ "entity.RequestLog.tools": "요청에 장착된 tool 정의 목록",
22561
22562
  "entity.RequestLog.status": "실행 상태",
22562
22563
  "entity.RequestLog.error_message": "에러 메시지",
22563
22564
  "entity.RequestLog.tool_call_count": "tool call 횟수",
@@ -1,4 +1,4 @@
1
- import { B as resolveStaleTime, C as infiniteQueryBehavior, D as notifyManager, G as timeoutManager, H as shouldThrowError, I as noop, K as focusManager, N as isValidTimeout, O as pendingThenable, R as replaceData, S as hasPreviousPage, T as fetchState, V as shallowEqualObjects, W as timeUntilStale, Z as __toESM, _ as require_jsx_runtime, a as fetch, b as getDefaultState, f as require_lib, g as useQueryClient, i as dedupeAndFlatten, j as hashKey, k as environmentManager, o as useRefreshable, q as Subscribable, s as useSSEStream, v as require_react, x as hasNextPage, z as resolveEnabled } from "./sd.generated-C3gcvRcU.js";
1
+ import { B as resolveStaleTime, C as infiniteQueryBehavior, D as notifyManager, G as timeoutManager, H as shouldThrowError, I as noop, K as focusManager, N as isValidTimeout, O as pendingThenable, R as replaceData, S as hasPreviousPage, T as fetchState, V as shallowEqualObjects, W as timeUntilStale, Z as __toESM, _ as require_jsx_runtime, a as fetch, b as getDefaultState, f as require_lib, g as useQueryClient, i as dedupeAndFlatten, j as hashKey, k as environmentManager, o as useRefreshable, q as Subscribable, s as useSSEStream, v as require_react, x as hasNextPage, z as resolveEnabled } from "./sd.generated-ek-tz-7H.js";
2
2
  //#region ../../node_modules/.pnpm/@tanstack+query-core@5.95.2/node_modules/@tanstack/query-core/build/modern/queryObserver.js
3
3
  var QueryObserver = class extends Subscribable {
4
4
  constructor(client, options) {
@@ -1076,6 +1076,15 @@ var QgridService;
1076
1076
  }
1077
1077
  _QgridService.oauthStart = oauthStart;
1078
1078
  _QgridService.useOauthStartMutation = () => useMutation({ mutationFn: (params) => oauthStart(params.name) });
1079
+ async function oauthComplete(pastedCode) {
1080
+ return fetch({
1081
+ method: "POST",
1082
+ url: `/api/qgrid/oauthComplete`,
1083
+ data: { pastedCode }
1084
+ });
1085
+ }
1086
+ _QgridService.oauthComplete = oauthComplete;
1087
+ _QgridService.useOauthCompleteMutation = () => useMutation({ mutationFn: (params) => oauthComplete(params.pastedCode) });
1079
1088
  async function oauthStartOpenAI(name) {
1080
1089
  return fetch({
1081
1090
  method: "POST",
@@ -1,6 +1,6 @@
1
- import { J as __commonJSMin, Y as __exportAll, Z as __toESM, _ as require_jsx_runtime, v as require_react } from "./sd.generated-C3gcvRcU.js";
2
- import { i as RequestLogStepService, r as RequestLogService } from "./services.generated-B2_Auwvk.js";
3
- import { a as Link } from "./lazyRouteComponent-BCJr868T.js";
1
+ import { J as __commonJSMin, Y as __exportAll, Z as __toESM, _ as require_jsx_runtime, v as require_react } from "./sd.generated-ek-tz-7H.js";
2
+ import { i as RequestLogStepService, r as RequestLogService } from "./services.generated-BNuxWs7K.js";
3
+ import { a as Link } from "./lazyRouteComponent-PRxXWW3t.js";
4
4
  import { t as Route } from "../entry-server.generated.js";
5
5
  import { n as formatMicroUsd, t as cacheHitRate } from "./cost-CSH_t17I.js";
6
6
  import minpath from "node:path";
@@ -25801,6 +25801,12 @@ function extractText(content) {
25801
25801
  }).join("\n");
25802
25802
  return JSON.stringify(content);
25803
25803
  }
25804
+ function MonoPre({ className, children }) {
25805
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
25806
+ className: `text-[12px] text-sand-700 whitespace-pre-wrap wrap-break-word font-mono leading-relaxed max-h-40 overflow-auto ${className ?? ""}`,
25807
+ children
25808
+ });
25809
+ }
25804
25810
  function HistorySection({ history }) {
25805
25811
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, {
25806
25812
  title: "History",
@@ -25814,20 +25820,37 @@ function HistorySection({ history }) {
25814
25820
  className: "text-[10px] uppercase tracking-wider text-sand-500 font-medium mb-1",
25815
25821
  children: historyItemLabel(item)
25816
25822
  }),
25817
- item.content !== null && item.content !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
25818
- className: "text-[12px] text-sand-700 whitespace-pre-wrap wrap-break-word font-mono leading-relaxed max-h-40 overflow-auto",
25819
- children: extractText(item.content)
25823
+ item.content !== null && item.content !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MonoPre, { children: extractText(item.content) }),
25824
+ item.arguments && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MonoPre, { children: item.arguments }),
25825
+ item.output && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MonoPre, { children: item.output })
25826
+ ]
25827
+ }, `hist-${i}`))
25828
+ })
25829
+ });
25830
+ }
25831
+ function ToolsSection({ tools }) {
25832
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, {
25833
+ title: `Tools (${tools.length})`,
25834
+ defaultOpen: false,
25835
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
25836
+ className: "space-y-1.5",
25837
+ children: tools.map((tool, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
25838
+ className: "rounded-md px-3 py-2 bg-caution-400/10",
25839
+ children: [
25840
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
25841
+ className: "text-[10px] uppercase tracking-wider text-sand-500 font-medium mb-1",
25842
+ children: ["fn: ", tool.name]
25820
25843
  }),
25821
- item.arguments && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
25822
- className: "text-[12px] text-sand-700 whitespace-pre-wrap wrap-break-word font-mono leading-relaxed max-h-40 overflow-auto",
25823
- children: item.arguments
25844
+ tool.description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
25845
+ className: "text-[12px] text-sand-700 leading-relaxed",
25846
+ children: tool.description
25824
25847
  }),
25825
- item.output && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
25826
- className: "text-[12px] text-sand-700 whitespace-pre-wrap wrap-break-word font-mono leading-relaxed max-h-40 overflow-auto",
25827
- children: item.output
25848
+ tool.inputSchema !== void 0 && tool.inputSchema !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MonoPre, {
25849
+ className: "mt-1",
25850
+ children: JSON.stringify(tool.inputSchema, null, 2)
25828
25851
  })
25829
25852
  ]
25830
- }, `hist-${i}`))
25853
+ }, `tool-${i}`))
25831
25854
  })
25832
25855
  });
25833
25856
  }
@@ -25865,6 +25888,8 @@ function RequestDetail({ id }) {
25865
25888
  const tokensPerSecEnabled = isTokensPerSecEnabled(data);
25866
25889
  const history = data.history ?? null;
25867
25890
  const hasHistory = history !== null && history.length > 0;
25891
+ const tools = data.tools ?? null;
25892
+ const hasTools = tools !== null && tools.length > 0;
25868
25893
  const promptSections = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
25869
25894
  className: "space-y-4",
25870
25895
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, {
@@ -25901,6 +25926,7 @@ function RequestDetail({ id }) {
25901
25926
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HeaderBar, { data }),
25902
25927
  data.error_message && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorPanel, { message: data.error_message }),
25903
25928
  hasHistory && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HistorySection, { history }),
25929
+ hasTools && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolsSection, { tools }),
25904
25930
  hasSteps ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
25905
25931
  className: "flex gap-4 items-start",
25906
25932
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -1,7 +1,7 @@
1
- import { Z as __toESM, _ as require_jsx_runtime, g as useQueryClient, v as require_react } from "./sd.generated-C3gcvRcU.js";
2
- import { a as TokenService, n as QgridService } from "./services.generated-B2_Auwvk.js";
1
+ import { Z as __toESM, _ as require_jsx_runtime, g as useQueryClient, v as require_react } from "./sd.generated-ek-tz-7H.js";
2
+ import { a as TokenService, n as QgridService } from "./services.generated-BNuxWs7K.js";
3
3
  import { i as Q, o as lucideKeyRound } from "../entry-server.generated.js";
4
- import { t as useUpdateTokenMutation } from "./use-update-token-mutation-CNBgTysy.js";
4
+ import { t as useUpdateTokenMutation } from "./use-update-token-mutation-BBCC2GKM.js";
5
5
  //#region ~icons/lucide/plus.jsx
6
6
  var import_react = /* @__PURE__ */ __toESM(require_react());
7
7
  var import_jsx_runtime = require_jsx_runtime();
@@ -23,16 +23,19 @@ var lucidePlus = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
23
23
  //#region src/components/qgrid/AddTokenModal.tsx
24
24
  function AddTokenModal() {
25
25
  const [open, setOpen] = (0, import_react.useState)(false);
26
- const [oauthLoading, setOauthLoading] = (0, import_react.useState)(false);
26
+ const [oauthLoading, setOauthLoading] = (0, import_react.useState)(null);
27
27
  const [name, setName] = (0, import_react.useState)("");
28
+ const [codeEntry, setCodeEntry] = (0, import_react.useState)(false);
29
+ const [pastedCode, setPastedCode] = (0, import_react.useState)("");
28
30
  const queryClient = useQueryClient();
29
31
  const oauthStartMutation = QgridService.useOauthStartMutation();
30
32
  const oauthStartOpenAIMutation = QgridService.useOauthStartOpenAIMutation();
33
+ const oauthCompleteMutation = QgridService.useOauthCompleteMutation();
31
34
  const handleOAuthLogin = async (provider) => {
32
35
  if (!name.trim()) return;
33
36
  if (provider === "openai") {
34
37
  const popup = window.open("about:blank", "_blank");
35
- setOauthLoading(true);
38
+ setOauthLoading("openai");
36
39
  try {
37
40
  const { authUrl } = await oauthStartOpenAIMutation.mutateAsync({ name: name.trim() });
38
41
  if (popup) popup.location.href = authUrl;
@@ -43,27 +46,51 @@ function AddTokenModal() {
43
46
  }, 3e3);
44
47
  setTimeout(() => {
45
48
  clearInterval(poll);
46
- setOauthLoading(false);
49
+ setOauthLoading(null);
47
50
  }, 3e5);
48
51
  } catch (e) {
49
52
  console.error("OAuth start failed:", e);
50
- setOauthLoading(false);
53
+ setOauthLoading(null);
51
54
  }
52
55
  } else {
53
- setOauthLoading(true);
56
+ const popup = window.open("about:blank", "_blank");
57
+ setOauthLoading("anthropic");
54
58
  try {
55
- const { authUrl } = await oauthStartMutation.mutateAsync({ name: name.trim() });
56
- window.location.href = authUrl;
59
+ const { authUrl, mode } = await oauthStartMutation.mutateAsync({ name: name.trim() });
60
+ if (mode === "code") {
61
+ if (popup) popup.location.href = authUrl;
62
+ else window.open(authUrl, "_blank");
63
+ setCodeEntry(true);
64
+ setOauthLoading(null);
65
+ } else {
66
+ popup?.close();
67
+ window.location.href = authUrl;
68
+ }
57
69
  } catch (e) {
70
+ popup?.close();
58
71
  console.error("OAuth start failed:", e);
59
- setOauthLoading(false);
72
+ setOauthLoading(null);
60
73
  }
61
74
  }
62
75
  };
76
+ const submitPastedCode = async () => {
77
+ if (!pastedCode.trim()) return;
78
+ try {
79
+ await oauthCompleteMutation.mutateAsync({ pastedCode: pastedCode.trim() });
80
+ } catch (e) {
81
+ console.error("OAuth complete failed:", e);
82
+ return;
83
+ }
84
+ await Promise.all([queryClient.invalidateQueries({ queryKey: ["Token"] }), queryClient.invalidateQueries({ queryKey: ["Qgrid"] })]);
85
+ close();
86
+ };
63
87
  const close = () => {
64
88
  setOpen(false);
65
89
  setName("");
66
- setOauthLoading(false);
90
+ setOauthLoading(null);
91
+ setCodeEntry(false);
92
+ setPastedCode("");
93
+ oauthCompleteMutation.reset();
67
94
  };
68
95
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
69
96
  type: "button",
@@ -97,15 +124,53 @@ function AddTokenModal() {
97
124
  onChange: (e) => setName(e.target.value),
98
125
  placeholder: "e.g. your-token-name",
99
126
  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"
100
- })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
127
+ })] }), codeEntry ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
101
128
  className: "space-y-2",
102
129
  children: [
130
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
131
+ className: "text-[12px] text-sand-600 leading-relaxed",
132
+ children: "새 탭에서 Claude 로그인을 완료하면 코드가 표시됩니다. 코드 전체를 아래에 붙여넣으세요."
133
+ }),
134
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Q, {
135
+ value: pastedCode,
136
+ onChange: (e) => setPastedCode(e.target.value),
137
+ placeholder: "code#state",
138
+ 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"
139
+ }),
140
+ oauthCompleteMutation.isError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
141
+ className: "text-[11px] text-red-500",
142
+ children: "코드 확인에 실패했습니다 — 표시된 코드 전체를 다시 붙여넣거나 로그인을 다시 시작하세요."
143
+ }),
103
144
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
104
145
  type: "button",
105
146
  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",
106
- disabled: !name.trim() || oauthLoading,
147
+ disabled: !pastedCode.trim() || oauthCompleteMutation.isPending,
148
+ onClick: submitPastedCode,
149
+ children: oauthCompleteMutation.isPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
150
+ className: "flex items-center gap-1.5",
151
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" }), "Verifying..."]
152
+ }) : "Complete login"
153
+ }),
154
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
155
+ type: "button",
156
+ className: "w-full py-1.5 text-[12px] text-sand-500 hover:text-sand-700 transition-colors duration-150",
157
+ onClick: () => {
158
+ setCodeEntry(false);
159
+ setPastedCode("");
160
+ oauthCompleteMutation.reset();
161
+ },
162
+ children: "처음부터 다시 시작"
163
+ })
164
+ ]
165
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
166
+ className: "space-y-2",
167
+ children: [
168
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
169
+ type: "button",
170
+ 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",
171
+ disabled: !name.trim() || oauthLoading !== null,
107
172
  onClick: () => handleOAuthLogin("openai"),
108
- children: oauthLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
173
+ children: oauthLoading === "openai" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
109
174
  className: "flex items-center gap-1.5",
110
175
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" }), "Waiting for login..."]
111
176
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideKeyRound, { className: "size-4" }), "Login with OpenAI"] })
@@ -121,12 +186,15 @@ function AddTokenModal() {
121
186
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex-1 h-px bg-sand-200" })
122
187
  ]
123
188
  }),
124
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
189
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
125
190
  type: "button",
126
191
  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",
127
- disabled: !name.trim() || oauthLoading,
192
+ disabled: !name.trim() || oauthLoading !== null,
128
193
  onClick: () => handleOAuthLogin("anthropic"),
129
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideKeyRound, { className: "size-4" }), "Login with Claude"]
194
+ children: oauthLoading === "anthropic" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
195
+ className: "flex items-center gap-1.5",
196
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "w-3.5 h-3.5 border-2 border-sand-400 border-t-transparent rounded-full animate-spin" }), "Waiting for login..."]
197
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideKeyRound, { className: "size-4" }), "Login with Claude"] })
130
198
  })
131
199
  ]
132
200
  })]
@@ -1,4 +1,4 @@
1
- import { n as QgridService, o as useMutation } from "./services.generated-B2_Auwvk.js";
1
+ import { n as QgridService, o as useMutation } from "./services.generated-BNuxWs7K.js";
2
2
  //#region src/services/token/use-update-token-mutation.ts
3
3
  function useUpdateTokenMutation() {
4
4
  return useMutation({ mutationFn: ({ id, name, quotaThreshold, weight }) => QgridService.updateToken(id, name, quotaThreshold, weight) });
@@ -1,5 +1,5 @@
1
- import { A as functionalUpdate, B as resolveStaleTime, C as infiniteQueryBehavior, D as notifyManager, E as onlineManager, F as matchQuery, I as noop$1, J as __commonJSMin, K as focusManager, L as partialMatchKey, M as hashQueryKeyByOptions, P as matchMutation, U as skipToken, X as __require, Z as __toESM, _ as require_jsx_runtime, c as _enum, d as string, h as QueryClientProvider, j as hashKey, l as number, m as require_shim, n as SUPPORTED_LOCALES, p as require_react_dom, q as Subscribable, r as setLocale, t as SD, u as object, v as require_react, w as Query, y as Mutation } from "./assets/sd.generated-C3gcvRcU.js";
2
- import { A as decodePath, C as findRouteMatch, D as createLRUCache, E as processRouteTree, F as last, I as replaceEqualDeep, M as findLast, N as functionalUpdate$1, O as invariant, P as isPromise, S as findFlatMatch, T as processRouteMasks, _ as interpolatePath, a as Link$1, b as trimPath, c as usePrevious$1, d as useRouter, f as getRouterContext, g as cleanPath, h as isNotFound, i as createRootRouteWithContext, j as deepEqual$2, k as createControlledPromise, l as matchContext, m as rootRouteId, n as createFileRoute, p as warning, r as createLazyFileRoute, s as useLayoutEffect$3, t as lazyRouteComponent, u as useRouterState, v as joinPaths, w as findSingleMatch, x as trimPathRight, y as resolvePath } from "./assets/lazyRouteComponent-BCJr868T.js";
1
+ import { A as functionalUpdate, B as resolveStaleTime, C as infiniteQueryBehavior, D as notifyManager, E as onlineManager, F as matchQuery, I as noop$1, J as __commonJSMin, K as focusManager, L as partialMatchKey, M as hashQueryKeyByOptions, P as matchMutation, U as skipToken, X as __require, Z as __toESM, _ as require_jsx_runtime, c as _enum, d as string, h as QueryClientProvider, j as hashKey, l as number, m as require_shim, n as SUPPORTED_LOCALES, p as require_react_dom, q as Subscribable, r as setLocale, t as SD, u as object, v as require_react, w as Query, y as Mutation } from "./assets/sd.generated-ek-tz-7H.js";
2
+ import { A as decodePath, C as findRouteMatch, D as createLRUCache, E as processRouteTree, F as last, I as replaceEqualDeep, M as findLast, N as functionalUpdate$1, O as invariant, P as isPromise, S as findFlatMatch, T as processRouteMasks, _ as interpolatePath, a as Link$1, b as trimPath, c as usePrevious$1, d as useRouter, f as getRouterContext, g as cleanPath, h as isNotFound, i as createRootRouteWithContext, j as deepEqual$2, k as createControlledPromise, l as matchContext, m as rootRouteId, n as createFileRoute, p as warning, r as createLazyFileRoute, s as useLayoutEffect$3, t as lazyRouteComponent, u as useRouterState, v as joinPaths, w as findSingleMatch, x as trimPathRight, y as resolvePath } from "./assets/lazyRouteComponent-PRxXWW3t.js";
3
3
  //#region ../../node_modules/.pnpm/@tanstack+query-core@5.95.2/node_modules/@tanstack/query-core/build/modern/hydration.js
4
4
  function defaultTransformerFn(data) {
5
5
  return data;
@@ -22703,7 +22703,7 @@ var sections = [{
22703
22703
  }
22704
22704
  ]
22705
22705
  }];
22706
- var qgridVersion = "2.5.10";
22706
+ var qgridVersion = "2.6.1";
22707
22707
  function Sidebar({ className }) {
22708
22708
  const pathname = useRouterState({ select: (s) => s.location.pathname });
22709
22709
  const isActive = (path) => {
@@ -44584,11 +44584,11 @@ function RootComponent() {
44584
44584
  }
44585
44585
  //#endregion
44586
44586
  //#region src/routes/tokens.tsx
44587
- var $$splitComponentImporter$4 = () => import("./assets/tokens-BWPVvdfi.js");
44587
+ var $$splitComponentImporter$4 = () => import("./assets/tokens-CvytQ9Xy.js");
44588
44588
  var Route$4 = createFileRoute("/tokens")({ component: lazyRouteComponent($$splitComponentImporter$4, "component") });
44589
44589
  //#endregion
44590
44590
  //#region src/routes/monit.tsx
44591
- var $$splitComponentImporter$3 = () => import("./assets/monit--9mVsjNU.js");
44591
+ var $$splitComponentImporter$3 = () => import("./assets/monit-dBXxnAyI.js");
44592
44592
  var monitSearchSchema = object({ level: _enum([
44593
44593
  "all",
44594
44594
  "scaling",
@@ -44601,7 +44601,7 @@ var Route$3 = createFileRoute("/monit")({
44601
44601
  });
44602
44602
  //#endregion
44603
44603
  //#region src/routes/logs.tsx
44604
- var $$splitComponentImporter$2 = () => import("./assets/logs-33YNafdW.js");
44604
+ var $$splitComponentImporter$2 = () => import("./assets/logs-D0z1Ghj1.js");
44605
44605
  var logsSearchSchema = object({
44606
44606
  token: string().optional(),
44607
44607
  project: string().optional(),
@@ -44613,11 +44613,11 @@ var Route$2 = createFileRoute("/logs")({
44613
44613
  });
44614
44614
  //#endregion
44615
44615
  //#region src/routes/index.tsx
44616
- var $$splitComponentImporter$1 = () => import("./assets/routes-BK3Xh1UB.js");
44616
+ var $$splitComponentImporter$1 = () => import("./assets/routes-B3p7fuwk.js");
44617
44617
  var Route$1 = createFileRoute("/")({ component: lazyRouteComponent($$splitComponentImporter$1, "component") });
44618
44618
  //#endregion
44619
44619
  //#region src/routes/requests/show.tsx
44620
- var $$splitComponentImporter = () => import("./assets/show-Dxxxe608.js");
44620
+ var $$splitComponentImporter = () => import("./assets/show-0iNioJ2I.js");
44621
44621
  var showSearchSchema = object({ id: number() });
44622
44622
  var Route = createFileRoute("/requests/show")({
44623
44623
  validateSearch: showSearchSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cartanova/qgrid-cli",
3
- "version": "2.5.10",
3
+ "version": "2.6.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cartanova-ai/qgrid"
@@ -43,7 +43,6 @@ Sonamu's native `SONAMU_DB_HOST`, `SONAMU_DB_PORT`, `SONAMU_DB_USER`,
43
43
  | `NODE_ENV` | direct API: `development`; packaged CLI: `production` | Sonamu runtime profile. Use `staging` for remote non-production API deployments such as dev0. The profile does not create or rename the explicitly configured DB. |
44
44
  | `HOST` | `localhost` | Sonamu server listen host when running API directly. |
45
45
  | `PORT` | `44900` | Sonamu server listen port. In CLI mode this is derived from `--port`, not read as user input. |
46
- | `QGRID_PUBLIC_BASE_URL` | empty | Public base URL used to construct Anthropic OAuth callback URL. If unset, callback defaults to `http://localhost:${PORT}/callback`. |
47
46
  | `PROJECT_NAME` | `Qgrid` | Sonamu project name. Not the same as request log `projectName`. |
48
47
 
49
48
  Do not treat CLI bundle bootstrapping internals as user-facing qgrid configuration.
@@ -91,5 +90,5 @@ On shutdown, it stops provider dispatchers and the token subscriber.
91
90
 
92
91
  ## OAuth callbacks
93
92
 
94
- - Anthropic OAuth uses `/callback` on the qgrid server. The callback URL is based on `QGRID_PUBLIC_BASE_URL` when set, otherwise localhost and `PORT`.
93
+ - 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`.
95
94
  - OpenAI OAuth is handled by Codex app-server's own callback flow and completed through qgrid's OpenAI OAuth APIs.
@@ -94,7 +94,11 @@ Anthropic OAuth is implemented directly in qgrid.
94
94
  6. Existing Anthropic tokens with the same `accountUuid` are deleted.
95
95
  7. qgrid saves a new `provider: "anthropic"` token.
96
96
 
97
- `QGRID_PUBLIC_BASE_URL` controls the public callback base. If unset, callback defaults to `http://localhost:${PORT}/callback`.
97
+ The Anthropic OAuth client enforces a redirect-URI allowlist: loopback (`http://localhost:*/callback`) and the console callback (`https://console.anthropic.com/oauth/code/callback`) only — arbitrary public URLs cannot be registered. qgrid therefore picks the flow per request from the `Origin` header (falling back to `X-Forwarded-Host`/`Host`):
98
+
99
+ - Loopback origin: redirect flow — the callback returns to the request-derived base URL (`mode: "redirect"`).
100
+ - Remote origin: code flow — `redirect_uri` is the console callback; after login the console page shows `code#state`, which the user pastes into the dashboard and `oauthComplete` exchanges (`mode: "code"`, same flow as Claude Code CLI).
101
+ - No HTTP context (direct calls/tests): falls back to `http://localhost:${PORT}/callback`.
98
102
 
99
103
  Refresh:
100
104