@omg-dev/admin 0.4.24

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.
@@ -0,0 +1,50 @@
1
+ //#region src/server.ts
2
+ const ALLOWED = [
3
+ "/api/flags/",
4
+ "/api/adminBilling/",
5
+ "/api/bugReports/",
6
+ "/api/users/"
7
+ ];
8
+ function json(body, status) {
9
+ return new Response(JSON.stringify(body), {
10
+ status,
11
+ headers: {
12
+ "content-type": "application/json",
13
+ "cache-control": "no-store"
14
+ }
15
+ });
16
+ }
17
+ function createOmgAdminProxy(opts) {
18
+ const target = (opts.target ?? "https://backend.omg.dev").replace(/\/$/, "");
19
+ const prefix = opts.prefix ?? "/_omg";
20
+ return async function handle(req) {
21
+ const url = new URL(req.url);
22
+ if (url.pathname !== prefix && !url.pathname.startsWith(prefix + "/")) return null;
23
+ if (!opts.token) return json({ error: "omg admin token not configured" }, 503);
24
+ const upstreamPath = url.pathname.slice(prefix.length);
25
+ if (!ALLOWED.some((p) => upstreamPath.startsWith(p))) return json({ error: "forbidden path" }, 403);
26
+ const init = {
27
+ method: req.method,
28
+ headers: {
29
+ "content-type": req.headers.get("content-type") ?? "application/json",
30
+ authorization: `Bearer ${opts.token}`
31
+ },
32
+ signal: AbortSignal.timeout(3e4)
33
+ };
34
+ if (req.method !== "GET" && req.method !== "HEAD") init.body = await req.text();
35
+ try {
36
+ const r = await fetch(`${target}${upstreamPath}${url.search}`, init);
37
+ return new Response(r.body, {
38
+ status: r.status,
39
+ headers: {
40
+ "content-type": r.headers.get("content-type") ?? "application/json",
41
+ "cache-control": "no-store"
42
+ }
43
+ });
44
+ } catch {
45
+ return json({ error: "omg control-plane unreachable" }, 502);
46
+ }
47
+ };
48
+ }
49
+ //#endregion
50
+ export { createOmgAdminProxy };
@@ -0,0 +1,11 @@
1
+ import { o as AdminClient, t as AdminConsole } from "./react-COCR3YSL.mjs";
2
+ import { createElement } from "react";
3
+ import { createRoot } from "react-dom/client";
4
+ //#region src/standalone.tsx
5
+ function mountAdminConsole(el, config) {
6
+ const root = createRoot(el);
7
+ root.render(createElement(AdminConsole, { client: new AdminClient(config) }));
8
+ return root;
9
+ }
10
+ //#endregion
11
+ export { AdminClient, AdminConsole, mountAdminConsole };
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@omg-dev/admin",
3
+ "version": "0.4.24",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./src/index.ts",
8
+ "default": "./dist/index.mjs"
9
+ },
10
+ "./standalone": {
11
+ "types": "./src/standalone.tsx",
12
+ "default": "./dist/standalone.mjs"
13
+ },
14
+ "./server": {
15
+ "types": "./src/server.ts",
16
+ "default": "./dist/server.mjs"
17
+ }
18
+ },
19
+ "peerDependencies": {
20
+ "react": "^18 || ^19",
21
+ "react-dom": "^18 || ^19"
22
+ },
23
+ "peerDependenciesMeta": {
24
+ "react-dom": {
25
+ "optional": true
26
+ }
27
+ },
28
+ "devDependencies": {
29
+ "@types/react": "^19.2.14",
30
+ "@types/react-dom": "^19.2.3",
31
+ "typescript": "^5.4.5"
32
+ },
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/BennyKok/vibes.git"
37
+ },
38
+ "homepage": "https://docs.omg.dev",
39
+ "files": [
40
+ "dist",
41
+ "src"
42
+ ],
43
+ "publishConfig": {
44
+ "access": "public",
45
+ "registry": "https://registry.npmjs.org/"
46
+ }
47
+ }
package/src/client.ts ADDED
@@ -0,0 +1,278 @@
1
+ // @omg-dev/admin — host-agnostic control-plane RPC client.
2
+ //
3
+ // The admin console is embedded in MORE than one host (the omg dashboard, the
4
+ // LFG app, later Inspect), so it never assumes a base URL or an auth provider.
5
+ // The host supplies an `AdminClientConfig`: where the control-plane lives
6
+ // (`apiBase`) and how to get a bearer token (`getToken`). Every call is
7
+ // POST {apiBase}/api/<module>/<name> with the args object as the JSON body and
8
+ // `Authorization: Bearer <token>` — the same wire the dashboard's own client
9
+ // uses, minus the Convex-shape bridging the console doesn't need.
10
+
11
+ export interface AdminClientConfig {
12
+ /** Control-plane origin, e.g. "https://backend.omg.dev" (no trailing slash). */
13
+ apiBase: string
14
+ /** Returns a fresh bearer JWT (or null when signed out). Awaited per call. */
15
+ getToken: () => string | null | Promise<string | null>
16
+ }
17
+
18
+ export class AdminError extends Error {
19
+ status: number
20
+ data: unknown
21
+ constructor(message: string, status: number, data: unknown) {
22
+ super(message)
23
+ this.name = "AdminError"
24
+ this.status = status
25
+ this.data = data
26
+ }
27
+ }
28
+
29
+ export class AdminClient {
30
+ private apiBase: string
31
+ private getToken: AdminClientConfig["getToken"]
32
+
33
+ constructor(config: AdminClientConfig) {
34
+ this.apiBase = config.apiBase.replace(/\/$/, "")
35
+ this.getToken = config.getToken
36
+ }
37
+
38
+ private async rpc<T>(module: string, name: string, args?: Record<string, unknown>): Promise<T> {
39
+ const token = await this.getToken()
40
+ const headers: Record<string, string> = { "content-type": "application/json" }
41
+ if (token) headers.authorization = `Bearer ${token}`
42
+ const res = await fetch(`${this.apiBase}/api/${module}/${name}`, {
43
+ method: "POST",
44
+ headers,
45
+ body: JSON.stringify(args ?? {}),
46
+ })
47
+ if (!res.ok) {
48
+ let message = `${module}.${name} failed (${res.status})`
49
+ let payload: unknown = null
50
+ try {
51
+ payload = await res.json()
52
+ if (payload && typeof payload === "object" && "error" in payload) {
53
+ message = String((payload as { error: unknown }).error)
54
+ }
55
+ } catch {
56
+ /* non-JSON error body */
57
+ }
58
+ throw new AdminError(message, res.status, payload)
59
+ }
60
+ if (res.status === 204) return undefined as T
61
+ const text = await res.text()
62
+ return (text ? JSON.parse(text) : undefined) as T
63
+ }
64
+
65
+ // ── flags ────────────────────────────────────────────────────────────────
66
+ listFlags() {
67
+ return this.rpc<FlagDef[]>("flags", "listFlags")
68
+ }
69
+ upsertFlag(flag: FlagUpsert) {
70
+ return this.rpc<{ ok: true; key: string }>("flags", "upsert", flag as unknown as Record<string, unknown>)
71
+ }
72
+ setFlagArchived(key: string, archived: boolean) {
73
+ return this.rpc<{ ok: true }>("flags", "setArchived", { key, archived })
74
+ }
75
+ removeFlag(key: string) {
76
+ return this.rpc<{ ok: true }>("flags", "removeFlag", { key })
77
+ }
78
+ listOverrides(flagKey: string) {
79
+ return this.rpc<FlagOverride[]>("flags", "listOverrides", { flagKey })
80
+ }
81
+ setOverride(o: { flagKey: string; userId: string; enabled: boolean; value?: unknown; note?: string }) {
82
+ return this.rpc<{ ok: true }>("flags", "setOverride", o as Record<string, unknown>)
83
+ }
84
+ removeOverride(flagKey: string, userId: string) {
85
+ return this.rpc<{ ok: true }>("flags", "removeOverride", { flagKey, userId })
86
+ }
87
+ findUsers(query: string) {
88
+ return this.rpc<UserHit[]>("users", "findUsers", { query })
89
+ }
90
+ listUsers(args?: { query?: string; limit?: number; offset?: number }) {
91
+ return this.rpc<AdminUserList>("users", "listUsers", args ?? {})
92
+ }
93
+
94
+ // ── billing (read-only) ────────────────────────────────────────────────────
95
+ planVersions() {
96
+ return this.rpc<PlanVersion[]>("adminBilling", "planVersions")
97
+ }
98
+ models() {
99
+ return this.rpc<ModelInfo[]>("adminBilling", "models")
100
+ }
101
+ userBalance(userId: string) {
102
+ return this.rpc<UserBalance>("adminBilling", "userBalance", { userId })
103
+ }
104
+
105
+ // ── bug reports (triage) ───────────────────────────────────────────────────
106
+ listReports(args?: { status?: BugStatus; limit?: number; clusterKey?: string }) {
107
+ return this.rpc<BugReportList>("bugReports", "listReports", args ?? {})
108
+ }
109
+ getReport(id: string) {
110
+ return this.rpc<BugReportDetail | null>("bugReports", "getReport", { id })
111
+ }
112
+ setReportStatus(id: string, status: BugStatus, assignee?: string) {
113
+ return this.rpc<{ ok: true }>("bugReports", "setStatus", { id, status, assignee })
114
+ }
115
+ addReportNote(id: string, note: string) {
116
+ return this.rpc<{ ok: true }>("bugReports", "addNote", { id, note })
117
+ }
118
+ // Dedupe surface: group a report under a root cause (clusterKey=null clears),
119
+ // and roll up clusters so triage can skip a duplicate fixer.
120
+ setReportCluster(id: string, clusterKey: string | null) {
121
+ return this.rpc<{ ok: true; clusterKey: string | null }>("bugReports", "setCluster", { id, clusterKey })
122
+ }
123
+ listClusters() {
124
+ return this.rpc<BugClusterList>("bugReports", "listClusters", {})
125
+ }
126
+ removeReport(id: string) {
127
+ return this.rpc<{ ok: boolean }>("bugReports", "removeReport", { id })
128
+ }
129
+ }
130
+
131
+ // ── shared types (mirror control-plane function returns) ──────────────────────
132
+
133
+ export type ValueType = "bool" | "json"
134
+
135
+ export interface FlagDef {
136
+ key: string
137
+ description: string
138
+ valueType: ValueType
139
+ enabled: boolean
140
+ defaultValue: unknown
141
+ rolloutPercent: number | null
142
+ planRules: Record<string, unknown> | null
143
+ archivedAt: number | null
144
+ overrideCount: number
145
+ createdAt: number
146
+ updatedAt: number
147
+ }
148
+
149
+ export interface FlagUpsert {
150
+ key: string
151
+ description?: string
152
+ valueType?: ValueType
153
+ enabled?: boolean
154
+ defaultValue?: unknown
155
+ rolloutPercent?: number | null
156
+ planRules?: Record<string, unknown> | null
157
+ }
158
+
159
+ export interface FlagOverride {
160
+ flagKey: string
161
+ userId: string
162
+ userEmail: string | null
163
+ userName: string | null
164
+ enabled: boolean
165
+ value: unknown
166
+ note: string
167
+ updatedAt: number
168
+ }
169
+
170
+ export interface UserHit {
171
+ id: string
172
+ email: string
173
+ name: string
174
+ image: string | null
175
+ }
176
+
177
+ export interface AdminUser {
178
+ id: string
179
+ email: string
180
+ name: string
181
+ image: string | null
182
+ emailVerified: boolean
183
+ createdAt: number
184
+ updatedAt: number
185
+ activeSessionCount: number
186
+ projectCount: number
187
+ ownedProjectCount: number
188
+ flagOverrideCount: number
189
+ }
190
+
191
+ export interface AdminUserList {
192
+ users: AdminUser[]
193
+ total: number
194
+ limit: number
195
+ offset: number
196
+ query: string
197
+ }
198
+
199
+ export interface PlanVersion {
200
+ planKey: string
201
+ version: number
202
+ status: string
203
+ priceUsd: number
204
+ interval: string
205
+ externalProductId: string
206
+ externalPriceId: string
207
+ createdAt: number
208
+ }
209
+
210
+ export interface ModelInfo {
211
+ id: string
212
+ name?: string
213
+ provider?: string
214
+ [k: string]: unknown
215
+ }
216
+
217
+ export interface UserBalance {
218
+ exists: boolean
219
+ plan: string
220
+ balanceUsd: number
221
+ appCreditsUsd: number
222
+ }
223
+
224
+ // ── bug reports ───────────────────────────────────────────────────────────────
225
+
226
+ export type BugStatus = "open" | "triaged" | "in_progress" | "resolved" | "wont_fix"
227
+ export type BugSeverity = "low" | "normal" | "high" | "critical"
228
+
229
+ export interface BugReportSummary {
230
+ id: string
231
+ title: string
232
+ severity: BugSeverity
233
+ status: BugStatus
234
+ assignee: string | null
235
+ reporterEmail: string | null
236
+ // null = anonymous app-submitted report → reporterEmail is self-claimed and
237
+ // unverified (the UI marks it). Non-null = dashboard report, email from JWT.
238
+ reporterUserId: string | null
239
+ slug: string | null
240
+ // Root-cause cluster key (null = unclustered). Reports sharing a key are
241
+ // duplicates of one root cause; triage uses it to dedupe.
242
+ clusterKey: string | null
243
+ createdAt: number
244
+ updatedAt: number
245
+ }
246
+
247
+ export interface BugReportList {
248
+ reports: BugReportSummary[]
249
+ counts: Record<string, number>
250
+ }
251
+
252
+ export interface BugCluster {
253
+ clusterKey: string
254
+ total: number
255
+ open: number
256
+ // Reports not yet resolved/wont_fix — non-zero means the root cause is still
257
+ // live, so a new duplicate should attach here instead of opening a new fixer.
258
+ unresolved: number
259
+ latestAt: number
260
+ latestTitle: string | null
261
+ }
262
+
263
+ export interface BugClusterList {
264
+ clusters: BugCluster[]
265
+ }
266
+
267
+ export interface BugReportDetail extends BugReportSummary {
268
+ body: string
269
+ projectId: string | null
270
+ runId: string | null
271
+ snapshotId: string | null
272
+ version: number | null
273
+ pageUrl: string | null
274
+ userAgent: string | null
275
+ adminNotes: string | null
276
+ resolvedAt: number | null
277
+ context: unknown
278
+ }
package/src/index.ts ADDED
@@ -0,0 +1,43 @@
1
+ // @omg-dev/admin — embeddable internal admin console.
2
+ //
3
+ // A panel-registry React console (Feature Flags, Users, Reports + read-only
4
+ // Pricing/Billing) that drops into any host as a single <AdminConsole/> — the
5
+ // omg dashboard, the LFG app, or Inspect. Host-agnostic: you pass an AdminClient
6
+ // (control-plane base URL + a bearer-token getter); the console does the rest.
7
+ // Self-styled (no Tailwind dependency), and every endpoint it calls is
8
+ // admin-gated server-side.
9
+ //
10
+ // Standalone usage (its own app) lives in "@omg-dev/admin/standalone".
11
+
12
+ export {
13
+ AdminConsole,
14
+ FlagsPanel,
15
+ PricingPanel,
16
+ ReportsPanel,
17
+ UsersPanel,
18
+ AdminClient,
19
+ type AdminConsoleProps,
20
+ type AdminClientConfig,
21
+ type PanelDef,
22
+ } from "./react"
23
+
24
+ export {
25
+ AdminError,
26
+ type FlagDef,
27
+ type FlagUpsert,
28
+ type FlagOverride,
29
+ type PlanVersion,
30
+ type ModelInfo,
31
+ type UserBalance,
32
+ type UserHit,
33
+ type AdminUser,
34
+ type AdminUserList,
35
+ type ValueType,
36
+ type BugStatus,
37
+ type BugSeverity,
38
+ type BugReportSummary,
39
+ type BugReportList,
40
+ type BugReportDetail,
41
+ } from "./client"
42
+
43
+ export { ensureStyles, STYLE_ID } from "./styles"