@hachej/boring-core 0.1.43 → 0.1.44

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.
@@ -118,12 +118,16 @@ interface CreditLedgerEntry {
118
118
  createdAt: string;
119
119
  description: string;
120
120
  }
121
- /** Format SIGNED credit micros as a euro string with an explicit +/− sign. */
122
- declare function formatSignedCreditMicros(micros: number, locale?: string): string;
121
+ /** Format SIGNED credit micros as a currency string with an explicit +/− sign.
122
+ * `currency` is the configured display currency (1 credit-unit = 1 major unit); defaults
123
+ * to EUR for callers without a configured purchase currency. */
124
+ declare function formatSignedCreditMicros(micros: number, currency?: string, locale?: string): string;
123
125
  /** Format a minor-unit price (e.g. cents) in its currency for pack labels/buttons. */
124
126
  declare function formatMinorPrice(priceMinor: number, currency: string, locale?: string): string;
125
- /** Format credit micros as a euro string. 1 credit = €0.000001 µ/1e6 euros. */
126
- declare function formatCreditMicros(micros: number, locale?: string): string;
127
+ /** Format credit micros as a currency string. 1 credit-unit = 1 major unit of the
128
+ * configured display `currency` (µ/1e6). Defaults to EUR for callers without a
129
+ * configured purchase currency (e.g. a consumption-only deployment). */
130
+ declare function formatCreditMicros(micros: number, currency?: string, locale?: string): string;
127
131
  /** True when the remaining balance is at or below the low-balance threshold. */
128
132
  declare function isLowBalance(micros: number, thresholdMicros?: number): boolean;
129
133
  /** Stable server error code for an out-of-credits rejection (mirrors the agent's
@@ -2,6 +2,7 @@ import {
2
2
  CoreFront,
3
3
  UserMenu,
4
4
  WorkspaceSwitcher,
5
+ routes,
5
6
  useCurrentWorkspace,
6
7
  useSession,
7
8
  useSignIn,
@@ -144,6 +145,7 @@ function AuthCard({
144
145
  mode === "signup" ? /* @__PURE__ */ jsx2("input", { className: "w-full rounded-xl border border-border bg-background px-3 py-2.5 text-sm outline-none focus:border-ring", placeholder: "Name", value: name, onChange: (event) => setName(event.currentTarget.value) }) : null,
145
146
  /* @__PURE__ */ jsx2("input", { className: "w-full rounded-xl border border-border bg-background px-3 py-2.5 text-sm outline-none focus:border-ring", type: "email", autoComplete: "email", placeholder: "Email", value: email, onChange: (event) => setEmail(event.currentTarget.value), required: true }),
146
147
  /* @__PURE__ */ jsx2("input", { className: "w-full rounded-xl border border-border bg-background px-3 py-2.5 text-sm outline-none focus:border-ring", type: "password", autoComplete: mode === "signin" ? "current-password" : "new-password", placeholder: "Password", value: password, onChange: (event) => setPassword(event.currentTarget.value), required: true }),
148
+ mode === "signin" ? /* @__PURE__ */ jsx2("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx2("a", { href: routes.forgotPassword, className: "text-xs text-muted-foreground hover:underline", children: "Forgot password?" }) }) : null,
147
149
  /* @__PURE__ */ jsx2("button", { type: "submit", className: "w-full rounded-xl bg-primary px-3 py-2.5 text-sm font-medium text-primary-foreground disabled:opacity-50", disabled: submitting, children: submitting ? "Please wait\u2026" : mode === "signin" ? "Continue with email" : "Create account" })
148
150
  ] })
149
151
  ] });
@@ -617,6 +619,7 @@ function WorkspaceRoute({
617
619
  {
618
620
  ...workspaceProps,
619
621
  workspaceId,
622
+ workspaceLabel: workspaceProps.workspaceLabel ?? currentWorkspace.name,
620
623
  requestHeaders,
621
624
  authHeaders,
622
625
  chatParams,
@@ -735,19 +738,19 @@ function creditNetMicros(balance) {
735
738
  const debt = Number.isFinite(balance.debtMicros) ? balance.debtMicros : 0;
736
739
  return remaining - debt;
737
740
  }
738
- function formatSignedCreditMicros(micros, locale) {
739
- const euros = (Number.isFinite(micros) ? micros : 0) / 1e6;
740
- const sign = euros > 0 ? "+" : euros < 0 ? "\u2212" : "";
741
- const abs = new Intl.NumberFormat(locale, { style: "currency", currency: "EUR" }).format(Math.abs(euros));
741
+ function formatSignedCreditMicros(micros, currency = "EUR", locale) {
742
+ const major = (Number.isFinite(micros) ? micros : 0) / 1e6;
743
+ const sign = major > 0 ? "+" : major < 0 ? "\u2212" : "";
744
+ const abs = new Intl.NumberFormat(locale, { style: "currency", currency }).format(Math.abs(major));
742
745
  return `${sign}${abs}`;
743
746
  }
744
747
  function formatMinorPrice(priceMinor, currency, locale) {
745
748
  const major = (Number.isFinite(priceMinor) ? priceMinor : 0) / 100;
746
749
  return new Intl.NumberFormat(locale, { style: "currency", currency, maximumFractionDigits: major % 1 === 0 ? 0 : 2 }).format(major);
747
750
  }
748
- function formatCreditMicros(micros, locale) {
749
- const euros = (Number.isFinite(micros) ? Math.max(0, micros) : 0) / 1e6;
750
- return new Intl.NumberFormat(locale, { style: "currency", currency: "EUR" }).format(euros);
751
+ function formatCreditMicros(micros, currency = "EUR", locale) {
752
+ const major = (Number.isFinite(micros) ? Math.max(0, micros) : 0) / 1e6;
753
+ return new Intl.NumberFormat(locale, { style: "currency", currency }).format(major);
751
754
  }
752
755
  function isLowBalance(micros, thresholdMicros = 5e5) {
753
756
  return Number.isFinite(micros) && micros <= thresholdMicros;
@@ -924,6 +927,7 @@ function CreditBalanceBadge({
924
927
  const low = inDebt || isLowBalance(balance.remainingMicros);
925
928
  const showBuy = balance.checkoutEnabled ?? buyEnabled;
926
929
  const packs = (balance.packs ?? []).filter((p) => !p.custom);
930
+ const currency = balance.packs?.[0]?.currency ?? "EUR";
927
931
  const pick = async (packId) => {
928
932
  setOpen(false);
929
933
  await buy(packId);
@@ -934,7 +938,7 @@ function CreditBalanceBadge({
934
938
  {
935
939
  title: inDebt ? "Amount owed \u2014 top up to resume" : "Remaining credits",
936
940
  className: `text-[11px] tabular-nums ${low ? "text-destructive" : "text-muted-foreground"}`,
937
- children: inDebt ? `\u2212${formatCreditMicros(balance.debtMicros, locale)}` : formatCreditMicros(balance.remainingMicros, locale)
941
+ children: inDebt ? `\u2212${formatCreditMicros(balance.debtMicros, currency, locale)}` : formatCreditMicros(balance.remainingMicros, currency, locale)
938
942
  }
939
943
  ),
940
944
  showBuy ? /* @__PURE__ */ jsxs5(Popover, { open, onOpenChange: setOpen, children: [
@@ -961,7 +965,7 @@ function CreditBalanceBadge({
961
965
  className: "flex items-center justify-between gap-3 rounded-md px-2 py-1.5 text-[13px] hover:bg-muted disabled:opacity-50",
962
966
  children: [
963
967
  /* @__PURE__ */ jsx6("span", { className: "tabular-nums font-medium", children: formatMinorPrice(p.priceMinor, p.currency, locale) }),
964
- /* @__PURE__ */ jsx6("span", { className: "text-[11px] text-muted-foreground", children: formatCreditMicros(p.creditMicros, locale) })
968
+ /* @__PURE__ */ jsx6("span", { className: "text-[11px] text-muted-foreground", children: formatCreditMicros(p.creditMicros, p.currency, locale) })
965
969
  ]
966
970
  },
967
971
  p.id
@@ -1046,6 +1050,7 @@ function CreditsSettingsPanel({ apiBaseUrl = "", locale }) {
1046
1050
  const low = inDebt || isLowBalance(balance.remainingMicros);
1047
1051
  const showBuy = balance.checkoutEnabled ?? false;
1048
1052
  const packs = balance.packs ?? [];
1053
+ const currency = packs[0]?.currency ?? "EUR";
1049
1054
  const activePack = selectedPack ?? packs.find((p) => p.isDefault)?.id ?? packs[0]?.id ?? null;
1050
1055
  const activePackObj = packs.find((p) => p.id === activePack) ?? null;
1051
1056
  const doBuy = async (pack) => {
@@ -1067,10 +1072,10 @@ function CreditsSettingsPanel({ apiBaseUrl = "", locale }) {
1067
1072
  !inDebt && low && /* @__PURE__ */ jsx7(Notice, { role: "status", tone: "warning", description: "You're low on credits. Top up to avoid interruptions." }),
1068
1073
  /* @__PURE__ */ jsxs6(DetailList, { children: [
1069
1074
  /* @__PURE__ */ jsx7(DetailLine, { label: "Remaining balance", children: /* @__PURE__ */ jsxs6("p", { style: { fontVariantNumeric: "tabular-nums", fontWeight: 600 }, children: [
1070
- inDebt ? `\u2212${formatCreditMicros(balance.debtMicros, locale)}` : formatCreditMicros(balance.remainingMicros, locale),
1075
+ inDebt ? `\u2212${formatCreditMicros(balance.debtMicros, currency, locale)}` : formatCreditMicros(balance.remainingMicros, currency, locale),
1071
1076
  updating && /* @__PURE__ */ jsx7("span", { className: "ml-2 text-[11px] font-normal text-muted-foreground", "aria-live": "polite", children: "Updating\u2026" })
1072
1077
  ] }) }),
1073
- /* @__PURE__ */ jsx7(DetailLine, { label: "Used so far", children: /* @__PURE__ */ jsx7("p", { style: { fontVariantNumeric: "tabular-nums" }, children: formatCreditMicros(balance.usedMicros, locale) }) })
1078
+ /* @__PURE__ */ jsx7(DetailLine, { label: "Used so far", children: /* @__PURE__ */ jsx7("p", { style: { fontVariantNumeric: "tabular-nums" }, children: formatCreditMicros(balance.usedMicros, currency, locale) }) })
1074
1079
  ] }),
1075
1080
  showBuy && packs.length > 0 && /* @__PURE__ */ jsxs6("fieldset", { className: "space-y-2", children: [
1076
1081
  /* @__PURE__ */ jsx7("legend", { className: "text-[12px] font-medium text-foreground", children: "Buy credits" }),
@@ -1101,7 +1106,7 @@ function CreditsSettingsPanel({ apiBaseUrl = "", locale }) {
1101
1106
  /* @__PURE__ */ jsx7("span", { style: { fontVariantNumeric: "tabular-nums" }, children: formatMinorPrice(p.priceMinor, p.currency, locale) }),
1102
1107
  /* @__PURE__ */ jsxs6("span", { className: "text-muted-foreground", children: [
1103
1108
  "\xB7 ",
1104
- formatCreditMicros(p.creditMicros, locale)
1109
+ formatCreditMicros(p.creditMicros, p.currency, locale)
1105
1110
  ] })
1106
1111
  ] })
1107
1112
  ]
@@ -1139,7 +1144,7 @@ function CreditsSettingsPanel({ apiBaseUrl = "", locale }) {
1139
1144
  {
1140
1145
  style: { fontVariantNumeric: "tabular-nums" },
1141
1146
  className: e.amountMicros >= 0 ? "text-foreground" : "text-muted-foreground",
1142
- children: formatSignedCreditMicros(e.amountMicros, locale)
1147
+ children: formatSignedCreditMicros(e.amountMicros, currency, locale)
1143
1148
  }
1144
1149
  )
1145
1150
  ] }, e.id)) })
@@ -1315,7 +1320,17 @@ function BuyCreditsNoticeAction({ apiBaseUrl = "", label = "Buy credits" }) {
1315
1320
  if (message) setError(message);
1316
1321
  };
1317
1322
  return /* @__PURE__ */ jsxs8("div", { className: "flex shrink-0 flex-col items-end gap-1", children: [
1318
- /* @__PURE__ */ jsx9(Button3, { type: "button", size: "sm", onClick: () => void onBuy(), disabled: buying, children: buying ? "Opening\u2026" : label }),
1323
+ /* @__PURE__ */ jsx9(
1324
+ Button3,
1325
+ {
1326
+ type: "button",
1327
+ size: "sm",
1328
+ onClick: () => void onBuy(),
1329
+ disabled: buying,
1330
+ className: "bg-foreground text-background hover:bg-foreground/90",
1331
+ children: buying ? "Opening\u2026" : label
1332
+ }
1333
+ ),
1319
1334
  error ? /* @__PURE__ */ jsx9("span", { role: "alert", className: "text-xs text-destructive", children: error }) : null
1320
1335
  ] });
1321
1336
  }
@@ -621,6 +621,25 @@ async function createCoreWorkspaceAgentServer(options = {}) {
621
621
  const callerOptions = options.getPi ? await options.getPi(ctx) : void 0;
622
622
  return mergePiOptions(pluginOptions, callerOptions);
623
623
  };
624
+ app.get("/api/v1/workspace/meta", async (request, reply) => {
625
+ try {
626
+ const workspaceId = await resolveWorkspaceId(request);
627
+ const [workspace, workspaceRootForRequest] = await Promise.all([
628
+ workspaceStore.get(workspaceId),
629
+ resolveRoot(workspaceId, request)
630
+ ]);
631
+ return {
632
+ workspaceId,
633
+ workspaceRoot: workspaceRootForRequest,
634
+ projectName: workspace?.name ?? "Workspace"
635
+ };
636
+ } catch (error) {
637
+ const statusCode = typeof error?.statusCode === "number" ? error.statusCode : 500;
638
+ const message = error instanceof Error ? error.message : "workspace meta failed";
639
+ return reply.code(statusCode).send({ error: message });
640
+ }
641
+ });
642
+ const resolveSessionNamespace = async (ctx) => options.getSessionNamespace ? await options.getSessionNamespace(ctx) : options.sessionNamespace ?? ctx.workspaceId;
624
643
  await app.register(registerAgentRoutes, {
625
644
  workspaceRoot,
626
645
  sessionId: options.sessionId,
@@ -637,8 +656,7 @@ async function createCoreWorkspaceAgentServer(options = {}) {
637
656
  systemPromptAppend: pluginCollection.agentOptions.systemPromptAppend,
638
657
  pi: pluginCollection.agentOptions.pi,
639
658
  getPi: resolvePiOptions,
640
- sessionNamespace: options.sessionNamespace,
641
- getSessionNamespace: options.getSessionNamespace,
659
+ getSessionNamespace: resolveSessionNamespace,
642
660
  getExtraTools: async (ctx) => {
643
661
  const callerTools = options.getExtraTools ? await options.getExtraTools(ctx) : [];
644
662
  return [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-core",
3
- "version": "0.1.43",
3
+ "version": "0.1.44",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Foundation package for boring-ui-v2 apps: DB, auth, config, HTTP app factory, and frontend app shell.",
@@ -79,9 +79,9 @@
79
79
  "react-router-dom": "^7.14.2",
80
80
  "smol-toml": "^1.6.1",
81
81
  "zod": "^3.25.76",
82
- "@hachej/boring-agent": "0.1.43",
83
- "@hachej/boring-workspace": "0.1.43",
84
- "@hachej/boring-ui-kit": "0.1.43"
82
+ "@hachej/boring-ui-kit": "0.1.44",
83
+ "@hachej/boring-workspace": "0.1.44",
84
+ "@hachej/boring-agent": "0.1.44"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@testing-library/jest-dom": "^6.9.1",