@userz-ai/api 0.1.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/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # @userz-ai/api
2
+
3
+ Typed REST client for the [Userz](https://userz.ai) API. Server-side use with an `sk_` key.
4
+
5
+ This is the typed counterpart to hand-rolling `fetch` calls against `https://api.userz.ai/v1/*`. Types are stable per major version; the underlying spec is published at `https://api.userz.ai/openapi.json`.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @userz-ai/api
11
+ ```
12
+
13
+ Zero runtime deps. Pure ESM. Works on Node 18+, Bun, Deno, and Cloudflare Workers — anywhere `fetch` is available.
14
+
15
+ ## Quick start
16
+
17
+ ```ts
18
+ import { createUserzApi } from '@userz-ai/api';
19
+
20
+ const userz = createUserzApi({
21
+ apiKey: process.env.USERZ_API_KEY!, // sk_...
22
+ });
23
+
24
+ // List feedback for the authenticated Org
25
+ const { data, nextCursor } = await userz.feedback.list({ limit: 50 });
26
+
27
+ // Drill into one
28
+ const fb = await userz.feedback.get(data[0].id);
29
+
30
+ // Move a sanitized item into the agent queue (manual mode)
31
+ await userz.feedback.update(fb.id, { status: 'queued' });
32
+ ```
33
+
34
+ ## Reference
35
+
36
+ | Namespace | Method | Endpoint |
37
+ |---|---|---|
38
+ | `apps` | `list()` | `GET /v1/apps` |
39
+ | `feedback` | `list(query?)` | `GET /v1/feedback` |
40
+ | `feedback` | `get(id)` | `GET /v1/feedback/:id` |
41
+ | `feedback` | `update(id, body)` | `PATCH /v1/feedback/:id` |
42
+ | `agentRuns` | `get(id)` | `GET /v1/agent-runs/:id` |
43
+ | `tokens` | `mint({ appId, body })` | `POST /v1/tokens/mint?appId=…` |
44
+ | `raw` | `raw({ method, path, query?, body? })` | escape hatch |
45
+
46
+ Inputs and outputs are typed end-to-end. See `src/types.ts` for the wire shapes (`FeedbackFull`, `AgentRun`, `App`, `MintTokenBody`, etc.).
47
+
48
+ ## Errors
49
+
50
+ Non-2xx responses throw `UserzApiError`. The HTTP status, the server's stable error code, and the optional details bag are all on the thrown error:
51
+
52
+ ```ts
53
+ import { UserzApiError } from '@userz-ai/api';
54
+
55
+ try {
56
+ await userz.feedback.get(id);
57
+ } catch (err) {
58
+ if (err instanceof UserzApiError) {
59
+ if (err.code === 'rate_limited') {
60
+ await sleep(err.body.retryAfterMs ?? 1000);
61
+ return retry();
62
+ }
63
+ if (err.code === 'not_found') {
64
+ return null;
65
+ }
66
+ console.error(`Userz API ${err.status} ${err.code} (request id ${err.requestId})`);
67
+ }
68
+ throw err;
69
+ }
70
+ ```
71
+
72
+ The full set of stable error codes is exported as the `ApiErrorCode` union.
73
+
74
+ ## Options
75
+
76
+ ```ts
77
+ createUserzApi({
78
+ apiKey: '…',
79
+ baseUrl: 'https://api.userz.ai', // override for self-hosted / staging
80
+ fetch: customFetch, // inject for tests, edge runtimes
81
+ timeoutMs: 30_000, // 0 disables
82
+ userAgent: 'my-app/1.0.0',
83
+ });
84
+ ```
85
+
86
+ ## Minting widget tokens (private mode)
87
+
88
+ For private-mode submissions, mint a per-user JWT on your server and pass it to the widget. You can do that two ways:
89
+
90
+ 1. **In-process** with `@userz-ai/node` — no network call, signs locally with the App's signing secret.
91
+ 2. **Via the REST endpoint** — call `userz.tokens.mint(...)` here. The server signs with the App's secret (so your sk_ key never sees the secret).
92
+
93
+ The REST path is the right choice if your backend runs in many languages or you don't want each service to hold the App signing secret:
94
+
95
+ ```ts
96
+ const { token, expiresInSeconds } = await userz.tokens.mint({
97
+ appId: process.env.USERZ_APP_ID!,
98
+ body: { sub: user.id, ctx: { email: user.email } },
99
+ });
100
+ return res.json({ user, userzToken: token, userzExpiresIn: expiresInSeconds });
101
+ ```
102
+
103
+ ## Type definitions
104
+
105
+ Everything in [`src/types.ts`](./src/types.ts) is exported from the package root. You can use the types independently of the client:
106
+
107
+ ```ts
108
+ import type { FeedbackStatus, AgentRun } from '@userz-ai/api';
109
+ ```
110
+
111
+ ## Versioning
112
+
113
+ Semver. Breaking changes ship in major versions; additive (new endpoints, new optional fields) ships in minors. The runtime client and the published types track the underlying API on the same major-version line — `@userz-ai/api@1.x` follows `/v1/*`, `@userz-ai/api@2.x` will follow `/v2/*` when that lands.
114
+
115
+ ## License
116
+
117
+ MIT
@@ -0,0 +1,227 @@
1
+ //#region src/types.d.ts
2
+ /**
3
+ * Wire types — the shapes the external-api accepts and returns over /v1/*.
4
+ *
5
+ * These mirror the Zod schemas in @userz/schemas and the Mongoose models in
6
+ * @userz/db (via the customer routes). We intentionally don't depend on
7
+ * those packages — keeping this client zero-dependency means it can drop
8
+ * into any TypeScript backend without dragging the monorepo's internals.
9
+ *
10
+ * If the underlying API drifts, regenerate by running the codegen step
11
+ * documented in README.md (`pnpm openapi:gen`). The hand-written shapes
12
+ * here are the source of truth until that lands in CI.
13
+ */
14
+ type Severity = 'low' | 'medium' | 'high';
15
+ type FeedbackStatus = 'received' | 'sanitizing' | 'sanitized' | 'flagged_malicious' | 'flagged_abuse' | 'queued' | 'awaiting_manual' | 'batch_pending' | 'processing' | 'pr_opened' | 'pr_failed' | 'timed_out' | 'agent_error' | 'archived';
16
+ type ProcessingMode = 'auto_pr' | 'manual' | 'no_pr';
17
+ type AgentRunStatus = 'pending' | 'preparing' | 'running' | 'succeeded' | 'failed' | 'timed_out' | 'guardrailed' | 'cancelled';
18
+ interface AttachmentRef {
19
+ key: string;
20
+ kind: 'screenshot' | 'annotated' | 'attachment';
21
+ contentType: string;
22
+ byteSize: number;
23
+ width?: number;
24
+ height?: number;
25
+ }
26
+ interface ConsoleLogEntry {
27
+ level: 'log' | 'info' | 'warn' | 'error' | 'debug';
28
+ message: string;
29
+ ts: number;
30
+ }
31
+ interface Viewport {
32
+ w: number;
33
+ h: number;
34
+ dpr: number;
35
+ }
36
+ interface ComponentTarget {
37
+ name: string;
38
+ meta?: Record<string, unknown>;
39
+ bbox?: {
40
+ x: number;
41
+ y: number;
42
+ w: number;
43
+ h: number;
44
+ };
45
+ screenshotKey?: string;
46
+ }
47
+ interface FeedbackSubmitter {
48
+ mode: 'public' | 'private';
49
+ externalUserId?: string;
50
+ email?: string;
51
+ name?: string;
52
+ claims?: Record<string, unknown>;
53
+ }
54
+ interface App {
55
+ id: string;
56
+ slug: string;
57
+ name: string;
58
+ description?: string;
59
+ publicKey: string;
60
+ publicSubmissionEnabled: boolean;
61
+ processingMode: ProcessingMode;
62
+ batchEnabled: boolean;
63
+ batchWindowHours: number;
64
+ allowedOrigins: string[];
65
+ createdAt: string;
66
+ }
67
+ interface ListAppsResponse {
68
+ data: App[];
69
+ }
70
+ interface FeedbackSummary {
71
+ id: string;
72
+ appId: string;
73
+ status: FeedbackStatus;
74
+ submittedBy: FeedbackSubmitter;
75
+ severity: Severity | null;
76
+ text: string;
77
+ url?: string;
78
+ createdAt: string;
79
+ processedAt?: string;
80
+ mergeRequestUrl?: string;
81
+ flaggedReason?: string;
82
+ }
83
+ interface FeedbackFull extends FeedbackSummary {
84
+ attachments: AttachmentRef[];
85
+ consoleLogs: ConsoleLogEntry[];
86
+ userAgent: string;
87
+ viewport?: Viewport;
88
+ componentTarget?: ComponentTarget;
89
+ submissionMeta: Record<string, unknown>;
90
+ batchedIntoId: string | null;
91
+ }
92
+ interface ListFeedbackResponse {
93
+ data: FeedbackSummary[];
94
+ nextCursor: string | null;
95
+ }
96
+ interface ListFeedbackQuery {
97
+ appId?: string;
98
+ status?: FeedbackStatus | string;
99
+ cursor?: string;
100
+ limit?: number;
101
+ }
102
+ interface PatchFeedbackBody {
103
+ status: 'queued' | 'archived';
104
+ }
105
+ interface PatchFeedbackResponse {
106
+ id: string;
107
+ status: FeedbackStatus;
108
+ }
109
+ interface AgentRun {
110
+ id: string;
111
+ appId: string;
112
+ feedbackId: string | null;
113
+ batchId: string | null;
114
+ provider: 'anthropic' | 'openai' | 'moonshot' | 'zai';
115
+ model: string;
116
+ status: AgentRunStatus;
117
+ tokensIn: number;
118
+ tokensOut: number;
119
+ costUsd: number;
120
+ prUrl?: string;
121
+ branch?: string;
122
+ guardrailReason?: string;
123
+ error?: string;
124
+ timings?: {
125
+ prepareMs?: number;
126
+ cloneMs?: number;
127
+ agentMs?: number;
128
+ pushMs?: number;
129
+ prMs?: number;
130
+ };
131
+ startedAt?: string;
132
+ endedAt?: string;
133
+ createdAt: string;
134
+ }
135
+ interface MintTokenBody {
136
+ sub: string;
137
+ ctx?: Record<string, unknown>;
138
+ expiresInSeconds?: number;
139
+ }
140
+ interface MintTokenResponse {
141
+ token: string;
142
+ expiresInSeconds: number;
143
+ }
144
+ type ApiErrorCode = 'invalid_body' | 'invalid_query' | 'invalid_id' | 'invalid_app' | 'no_op' | 'missing_authorization' | 'invalid_authorization' | 'invalid_api_key' | 'unknown_app' | 'invalid_user_token' | 'origin_not_allowed' | 'public_disabled' | 'app_not_found' | 'org_dek_missing' | 'not_found' | 'invalid_transition' | 'concurrent_update' | 'rate_limited' | 'internal_error';
145
+ interface ApiErrorBody {
146
+ error: ApiErrorCode | string;
147
+ message?: string;
148
+ /** Present on rate_limited responses. */
149
+ retryAfterMs?: number;
150
+ /** Present on invalid_body / invalid_query responses. */
151
+ issues?: unknown[];
152
+ /** Present on invalid_transition responses. */
153
+ from?: FeedbackStatus;
154
+ to?: FeedbackStatus;
155
+ }
156
+ //#endregion
157
+ //#region src/client.d.ts
158
+ interface UserzApiOptions {
159
+ /** Server-side API key from the Userz dashboard. Starts with `sk_`. */
160
+ apiKey: string;
161
+ /** Base URL of the Userz API. Default `https://api.userz.ai`. */
162
+ baseUrl?: string;
163
+ /** Custom fetch (defaults to global fetch). Useful for tests, edge runtimes. */
164
+ fetch?: typeof fetch;
165
+ /** Per-request timeout in ms. Default 30_000. Pass 0 to disable. */
166
+ timeoutMs?: number;
167
+ /** Default `User-Agent` to send. Default `@userz-ai/api/0.1.0`. */
168
+ userAgent?: string;
169
+ }
170
+ interface UserzApi {
171
+ apps: {
172
+ list(): Promise<ListAppsResponse>;
173
+ };
174
+ feedback: {
175
+ list(query?: ListFeedbackQuery): Promise<ListFeedbackResponse>;
176
+ get(id: string): Promise<FeedbackFull>;
177
+ update(id: string, body: PatchFeedbackBody): Promise<PatchFeedbackResponse>;
178
+ };
179
+ agentRuns: {
180
+ get(id: string): Promise<AgentRun>;
181
+ };
182
+ tokens: {
183
+ mint(input: {
184
+ appId: string;
185
+ body: MintTokenBody;
186
+ }): Promise<MintTokenResponse>;
187
+ };
188
+ /** Escape hatch — issue a raw call against any endpoint. */
189
+ raw<T>(input: {
190
+ method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT';
191
+ path: string;
192
+ query?: Record<string, string | number | undefined>;
193
+ body?: unknown;
194
+ }): Promise<T>;
195
+ }
196
+ declare function createUserzApi(opts: UserzApiOptions): UserzApi;
197
+ //#endregion
198
+ //#region src/error.d.ts
199
+ /**
200
+ * Thrown by the client on any non-2xx response. The HTTP status, the
201
+ * server's stable error code, and the optional details bag are all on the
202
+ * thrown error so callers can pattern-match without re-parsing the response.
203
+ *
204
+ * try {
205
+ * await api.feedback.get(id);
206
+ * } catch (err) {
207
+ * if (err instanceof UserzApiError && err.code === 'rate_limited') {
208
+ * await sleep(err.body.retryAfterMs ?? 1000);
209
+ * // retry
210
+ * }
211
+ * throw err;
212
+ * }
213
+ */
214
+ declare class UserzApiError extends Error {
215
+ readonly status: number;
216
+ readonly code: ApiErrorCode | string;
217
+ readonly body: ApiErrorBody;
218
+ readonly requestId: string | undefined;
219
+ constructor(input: {
220
+ status: number;
221
+ body: ApiErrorBody;
222
+ requestId?: string | undefined;
223
+ });
224
+ }
225
+ //#endregion
226
+ export { type AgentRun, type AgentRunStatus, type ApiErrorBody, type ApiErrorCode, type App, type AttachmentRef, type ComponentTarget, type ConsoleLogEntry, type FeedbackFull, type FeedbackStatus, type FeedbackSubmitter, type FeedbackSummary, type ListAppsResponse, type ListFeedbackQuery, type ListFeedbackResponse, type MintTokenBody, type MintTokenResponse, type PatchFeedbackBody, type PatchFeedbackResponse, type ProcessingMode, type Severity, type UserzApi, UserzApiError, type UserzApiOptions, type Viewport, createUserzApi };
227
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/client.ts","../src/error.ts"],"mappings":"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"}
package/dist/index.mjs ADDED
@@ -0,0 +1,127 @@
1
+ //#region src/error.ts
2
+ /**
3
+ * Thrown by the client on any non-2xx response. The HTTP status, the
4
+ * server's stable error code, and the optional details bag are all on the
5
+ * thrown error so callers can pattern-match without re-parsing the response.
6
+ *
7
+ * try {
8
+ * await api.feedback.get(id);
9
+ * } catch (err) {
10
+ * if (err instanceof UserzApiError && err.code === 'rate_limited') {
11
+ * await sleep(err.body.retryAfterMs ?? 1000);
12
+ * // retry
13
+ * }
14
+ * throw err;
15
+ * }
16
+ */
17
+ var UserzApiError = class extends Error {
18
+ status;
19
+ code;
20
+ body;
21
+ requestId;
22
+ constructor(input) {
23
+ const code = input.body.error ?? "internal_error";
24
+ const message = input.body.message ?? code;
25
+ super(`Userz API ${input.status} ${code}: ${message}`);
26
+ this.name = "UserzApiError";
27
+ this.status = input.status;
28
+ this.code = code;
29
+ this.body = input.body;
30
+ this.requestId = input.requestId;
31
+ }
32
+ };
33
+
34
+ //#endregion
35
+ //#region src/client.ts
36
+ function createUserzApi(opts) {
37
+ const baseUrl = (opts.baseUrl ?? "https://api.userz.ai").replace(/\/+$/, "");
38
+ const doFetch = opts.fetch ?? fetch;
39
+ const timeoutMs = opts.timeoutMs ?? 3e4;
40
+ const userAgent = opts.userAgent ?? "@userz-ai/api/0.1.0";
41
+ async function request(input) {
42
+ const url = new URL(baseUrl + input.path);
43
+ if (input.query) for (const [k, v] of Object.entries(input.query)) {
44
+ if (v === void 0) continue;
45
+ url.searchParams.set(k, String(v));
46
+ }
47
+ const headers = {
48
+ authorization: `Bearer ${opts.apiKey}`,
49
+ "user-agent": userAgent
50
+ };
51
+ if (input.body !== void 0) headers["content-type"] = "application/json";
52
+ const ctrl = timeoutMs > 0 ? new AbortController() : void 0;
53
+ const timer = ctrl ? setTimeout(() => ctrl.abort(), timeoutMs) : void 0;
54
+ let res;
55
+ try {
56
+ res = await doFetch(url.toString(), {
57
+ method: input.method,
58
+ headers,
59
+ body: input.body === void 0 ? void 0 : JSON.stringify(input.body),
60
+ signal: ctrl?.signal
61
+ });
62
+ } finally {
63
+ if (timer) clearTimeout(timer);
64
+ }
65
+ const requestId = res.headers.get("x-request-id") ?? void 0;
66
+ if (!res.ok) {
67
+ let errBody;
68
+ try {
69
+ errBody = await res.json();
70
+ } catch {
71
+ errBody = {
72
+ error: "internal_error",
73
+ message: res.statusText
74
+ };
75
+ }
76
+ throw new UserzApiError({
77
+ status: res.status,
78
+ body: errBody,
79
+ requestId
80
+ });
81
+ }
82
+ if (res.status === 204) return void 0;
83
+ return await res.json();
84
+ }
85
+ return {
86
+ apps: { list: () => request({
87
+ method: "GET",
88
+ path: "/v1/apps"
89
+ }) },
90
+ feedback: {
91
+ list: (query) => request({
92
+ method: "GET",
93
+ path: "/v1/feedback",
94
+ query: query ? {
95
+ appId: query.appId,
96
+ status: query.status,
97
+ cursor: query.cursor,
98
+ limit: query.limit
99
+ } : void 0
100
+ }),
101
+ get: (id) => request({
102
+ method: "GET",
103
+ path: `/v1/feedback/${encodeURIComponent(id)}`
104
+ }),
105
+ update: (id, body) => request({
106
+ method: "PATCH",
107
+ path: `/v1/feedback/${encodeURIComponent(id)}`,
108
+ body
109
+ })
110
+ },
111
+ agentRuns: { get: (id) => request({
112
+ method: "GET",
113
+ path: `/v1/agent-runs/${encodeURIComponent(id)}`
114
+ }) },
115
+ tokens: { mint: ({ appId, body }) => request({
116
+ method: "POST",
117
+ path: "/v1/tokens/mint",
118
+ query: { appId },
119
+ body
120
+ }) },
121
+ raw: (input) => request(input)
122
+ };
123
+ }
124
+
125
+ //#endregion
126
+ export { UserzApiError, createUserzApi };
127
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/error.ts","../src/client.ts"],"sourcesContent":["import type { ApiErrorBody, ApiErrorCode } from './types.js';\n\n/**\n * Thrown by the client on any non-2xx response. The HTTP status, the\n * server's stable error code, and the optional details bag are all on the\n * thrown error so callers can pattern-match without re-parsing the response.\n *\n * try {\n * await api.feedback.get(id);\n * } catch (err) {\n * if (err instanceof UserzApiError && err.code === 'rate_limited') {\n * await sleep(err.body.retryAfterMs ?? 1000);\n * // retry\n * }\n * throw err;\n * }\n */\nexport class UserzApiError extends Error {\n readonly status: number;\n readonly code: ApiErrorCode | string;\n readonly body: ApiErrorBody;\n readonly requestId: string | undefined;\n\n constructor(input: {\n status: number;\n body: ApiErrorBody;\n requestId?: string | undefined;\n }) {\n const code = input.body.error ?? 'internal_error';\n const message = input.body.message ?? code;\n super(`Userz API ${input.status} ${code}: ${message}`);\n this.name = 'UserzApiError';\n this.status = input.status;\n this.code = code;\n this.body = input.body;\n this.requestId = input.requestId;\n }\n}\n","import { UserzApiError } from './error.js';\nimport type {\n AgentRun,\n ApiErrorBody,\n FeedbackFull,\n ListAppsResponse,\n ListFeedbackQuery,\n ListFeedbackResponse,\n MintTokenBody,\n MintTokenResponse,\n PatchFeedbackBody,\n PatchFeedbackResponse,\n} from './types.js';\n\nexport interface UserzApiOptions {\n /** Server-side API key from the Userz dashboard. Starts with `sk_`. */\n apiKey: string;\n /** Base URL of the Userz API. Default `https://api.userz.ai`. */\n baseUrl?: string;\n /** Custom fetch (defaults to global fetch). Useful for tests, edge runtimes. */\n fetch?: typeof fetch;\n /** Per-request timeout in ms. Default 30_000. Pass 0 to disable. */\n timeoutMs?: number;\n /** Default `User-Agent` to send. Default `@userz-ai/api/0.1.0`. */\n userAgent?: string;\n}\n\nexport interface UserzApi {\n apps: {\n list(): Promise<ListAppsResponse>;\n };\n feedback: {\n list(query?: ListFeedbackQuery): Promise<ListFeedbackResponse>;\n get(id: string): Promise<FeedbackFull>;\n update(id: string, body: PatchFeedbackBody): Promise<PatchFeedbackResponse>;\n };\n agentRuns: {\n get(id: string): Promise<AgentRun>;\n };\n tokens: {\n mint(input: { appId: string; body: MintTokenBody }): Promise<MintTokenResponse>;\n };\n /** Escape hatch — issue a raw call against any endpoint. */\n raw<T>(input: {\n method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT';\n path: string;\n query?: Record<string, string | number | undefined>;\n body?: unknown;\n }): Promise<T>;\n}\n\nexport function createUserzApi(opts: UserzApiOptions): UserzApi {\n const baseUrl = (opts.baseUrl ?? 'https://api.userz.ai').replace(/\\/+$/, '');\n const doFetch = opts.fetch ?? fetch;\n const timeoutMs = opts.timeoutMs ?? 30_000;\n const userAgent = opts.userAgent ?? '@userz-ai/api/0.1.0';\n\n async function request<T>(input: {\n method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT';\n path: string;\n query?: Record<string, string | number | undefined>;\n body?: unknown;\n }): Promise<T> {\n const url = new URL(baseUrl + input.path);\n if (input.query) {\n for (const [k, v] of Object.entries(input.query)) {\n if (v === undefined) continue;\n url.searchParams.set(k, String(v));\n }\n }\n const headers: Record<string, string> = {\n authorization: `Bearer ${opts.apiKey}`,\n 'user-agent': userAgent,\n };\n if (input.body !== undefined) {\n headers['content-type'] = 'application/json';\n }\n\n const ctrl = timeoutMs > 0 ? new AbortController() : undefined;\n const timer = ctrl ? setTimeout(() => ctrl.abort(), timeoutMs) : undefined;\n let res: Response;\n try {\n res = await doFetch(url.toString(), {\n method: input.method,\n headers,\n body: input.body === undefined ? undefined : JSON.stringify(input.body),\n signal: ctrl?.signal,\n });\n } finally {\n if (timer) clearTimeout(timer);\n }\n\n const requestId = res.headers.get('x-request-id') ?? undefined;\n if (!res.ok) {\n let errBody: ApiErrorBody;\n try {\n errBody = (await res.json()) as ApiErrorBody;\n } catch {\n errBody = { error: 'internal_error', message: res.statusText };\n }\n throw new UserzApiError({ status: res.status, body: errBody, requestId });\n }\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n }\n\n return {\n apps: {\n list: () => request({ method: 'GET', path: '/v1/apps' }),\n },\n feedback: {\n list: (query) =>\n request({\n method: 'GET',\n path: '/v1/feedback',\n query: query\n ? {\n appId: query.appId,\n status: query.status,\n cursor: query.cursor,\n limit: query.limit,\n }\n : undefined,\n }),\n get: (id) => request({ method: 'GET', path: `/v1/feedback/${encodeURIComponent(id)}` }),\n update: (id, body) =>\n request({\n method: 'PATCH',\n path: `/v1/feedback/${encodeURIComponent(id)}`,\n body,\n }),\n },\n agentRuns: {\n get: (id) => request({ method: 'GET', path: `/v1/agent-runs/${encodeURIComponent(id)}` }),\n },\n tokens: {\n mint: ({ appId, body }) =>\n request({\n method: 'POST',\n path: '/v1/tokens/mint',\n query: { appId },\n body,\n }),\n },\n raw: (input) => request(input),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,IAAa,gBAAb,cAAmC,MAAM;CACvC,AAAS;CACT,AAAS;CACT,AAAS;CACT,AAAS;CAET,YAAY,OAIT;EACD,MAAM,OAAO,MAAM,KAAK,SAAS;EACjC,MAAM,UAAU,MAAM,KAAK,WAAW;AACtC,QAAM,aAAa,MAAM,OAAO,GAAG,KAAK,IAAI,UAAU;AACtD,OAAK,OAAO;AACZ,OAAK,SAAS,MAAM;AACpB,OAAK,OAAO;AACZ,OAAK,OAAO,MAAM;AAClB,OAAK,YAAY,MAAM;;;;;;ACgB3B,SAAgB,eAAe,MAAiC;CAC9D,MAAM,WAAW,KAAK,WAAW,wBAAwB,QAAQ,QAAQ,GAAG;CAC5E,MAAM,UAAU,KAAK,SAAS;CAC9B,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,YAAY,KAAK,aAAa;CAEpC,eAAe,QAAW,OAKX;EACb,MAAM,MAAM,IAAI,IAAI,UAAU,MAAM,KAAK;AACzC,MAAI,MAAM,MACR,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,MAAM,EAAE;AAChD,OAAI,MAAM,OAAW;AACrB,OAAI,aAAa,IAAI,GAAG,OAAO,EAAE,CAAC;;EAGtC,MAAM,UAAkC;GACtC,eAAe,UAAU,KAAK;GAC9B,cAAc;GACf;AACD,MAAI,MAAM,SAAS,OACjB,SAAQ,kBAAkB;EAG5B,MAAM,OAAO,YAAY,IAAI,IAAI,iBAAiB,GAAG;EACrD,MAAM,QAAQ,OAAO,iBAAiB,KAAK,OAAO,EAAE,UAAU,GAAG;EACjE,IAAI;AACJ,MAAI;AACF,SAAM,MAAM,QAAQ,IAAI,UAAU,EAAE;IAClC,QAAQ,MAAM;IACd;IACA,MAAM,MAAM,SAAS,SAAY,SAAY,KAAK,UAAU,MAAM,KAAK;IACvE,QAAQ,MAAM;IACf,CAAC;YACM;AACR,OAAI,MAAO,cAAa,MAAM;;EAGhC,MAAM,YAAY,IAAI,QAAQ,IAAI,eAAe,IAAI;AACrD,MAAI,CAAC,IAAI,IAAI;GACX,IAAI;AACJ,OAAI;AACF,cAAW,MAAM,IAAI,MAAM;WACrB;AACN,cAAU;KAAE,OAAO;KAAkB,SAAS,IAAI;KAAY;;AAEhE,SAAM,IAAI,cAAc;IAAE,QAAQ,IAAI;IAAQ,MAAM;IAAS;IAAW,CAAC;;AAE3E,MAAI,IAAI,WAAW,IAAK,QAAO;AAC/B,SAAQ,MAAM,IAAI,MAAM;;AAG1B,QAAO;EACL,MAAM,EACJ,YAAY,QAAQ;GAAE,QAAQ;GAAO,MAAM;GAAY,CAAC,EACzD;EACD,UAAU;GACR,OAAO,UACL,QAAQ;IACN,QAAQ;IACR,MAAM;IACN,OAAO,QACH;KACE,OAAO,MAAM;KACb,QAAQ,MAAM;KACd,QAAQ,MAAM;KACd,OAAO,MAAM;KACd,GACD;IACL,CAAC;GACJ,MAAM,OAAO,QAAQ;IAAE,QAAQ;IAAO,MAAM,gBAAgB,mBAAmB,GAAG;IAAI,CAAC;GACvF,SAAS,IAAI,SACX,QAAQ;IACN,QAAQ;IACR,MAAM,gBAAgB,mBAAmB,GAAG;IAC5C;IACD,CAAC;GACL;EACD,WAAW,EACT,MAAM,OAAO,QAAQ;GAAE,QAAQ;GAAO,MAAM,kBAAkB,mBAAmB,GAAG;GAAI,CAAC,EAC1F;EACD,QAAQ,EACN,OAAO,EAAE,OAAO,WACd,QAAQ;GACN,QAAQ;GACR,MAAM;GACN,OAAO,EAAE,OAAO;GAChB;GACD,CAAC,EACL;EACD,MAAM,UAAU,QAAQ,MAAM;EAC/B"}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@userz-ai/api",
3
+ "version": "0.1.0",
4
+ "description": "Typed REST client for the Userz API. Server-side use with an sk_ key.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.mjs",
8
+ "types": "./dist/index.d.mts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.mts",
12
+ "import": "./dist/index.mjs"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "sideEffects": false,
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "engines": {
24
+ "node": ">=18.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^25.6.0",
28
+ "tsdown": "^0.21.9",
29
+ "typescript": "^6.0.3"
30
+ },
31
+ "scripts": {
32
+ "build": "tsdown",
33
+ "dev": "tsdown --watch",
34
+ "typecheck": "tsc --noEmit",
35
+ "clean": "rm -rf dist *.tsbuildinfo"
36
+ }
37
+ }