@cstar.help/js 0.12.0 → 0.13.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/dist/index.cjs CHANGED
@@ -24,6 +24,7 @@ __export(src_exports, {
24
24
  CStarClient: () => CStarClient,
25
25
  CStarConflictError: () => CStarConflictError,
26
26
  CStarError: () => CStarError,
27
+ CStarFeatureDisabledError: () => CStarFeatureDisabledError,
27
28
  CStarNotFoundError: () => CStarNotFoundError,
28
29
  CStarPermissionError: () => CStarPermissionError,
29
30
  CStarRateLimitError: () => CStarRateLimitError,
@@ -44,102 +45,199 @@ var ErrorType = {
44
45
  NOT_FOUND_ERROR: "not_found_error",
45
46
  CONFLICT_ERROR: "conflict_error",
46
47
  RATE_LIMIT_ERROR: "rate_limit_error",
48
+ FEATURE_NOT_CONFIGURED: "feature_not_configured",
47
49
  API_ERROR: "api_error",
48
50
  SERVER_ERROR: "server_error"
49
51
  };
52
+ var DEFAULT_DOC_URL = "https://cstar.help/developers/api/errors";
53
+ function normalizeArgs(messageOrStatus, maybeBody, defaults) {
54
+ if (typeof messageOrStatus === "string") {
55
+ return {
56
+ statusCode: defaults?.code === "rate_limit_error" ? 429 : 0,
57
+ body: {
58
+ type: defaults?.type ?? ErrorType.API_ERROR,
59
+ code: defaults?.code ?? "sdk_error",
60
+ message: messageOrStatus,
61
+ doc_url: defaults?.doc_url ?? DEFAULT_DOC_URL,
62
+ ...defaults?.feature ? { feature: defaults.feature } : {},
63
+ ...defaults?.enable_url ? { enable_url: defaults.enable_url } : {}
64
+ }
65
+ };
66
+ }
67
+ return {
68
+ statusCode: messageOrStatus,
69
+ body: maybeBody ?? {
70
+ type: defaults?.type ?? ErrorType.API_ERROR,
71
+ code: defaults?.code ?? "unknown_error",
72
+ message: defaults?.message ?? `Request failed with status ${messageOrStatus}`,
73
+ doc_url: defaults?.doc_url ?? DEFAULT_DOC_URL
74
+ }
75
+ };
76
+ }
50
77
  var CStarError = class extends Error {
51
- constructor(statusCode, body) {
52
- super(body.message);
78
+ constructor(messageOrStatus, body) {
79
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body);
80
+ super(resolved.message);
53
81
  this.name = "CStarError";
54
- this.type = body.type;
55
- this.code = body.code;
56
- this.param = body.param;
57
- this.docUrl = body.doc_url;
58
- this.requestId = body.request_id;
82
+ this.type = resolved.type;
83
+ this.code = resolved.code;
84
+ this.param = resolved.param;
85
+ this.docUrl = resolved.doc_url;
86
+ this.requestId = resolved.request_id;
59
87
  this.statusCode = statusCode;
60
88
  }
61
89
  };
62
90
  var CStarAuthenticationError = class extends CStarError {
63
- constructor(statusCode, body) {
64
- super(statusCode, body);
91
+ constructor(messageOrStatus, body) {
92
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
93
+ type: ErrorType.AUTHENTICATION_ERROR,
94
+ code: "authentication_failed",
95
+ doc_url: `${DEFAULT_DOC_URL}#authentication-error`
96
+ });
97
+ super(statusCode, resolved);
65
98
  this.name = "CStarAuthenticationError";
66
99
  }
67
100
  };
68
101
  var CStarRateLimitError = class extends CStarError {
69
- constructor(statusCode, body, retryAfter) {
70
- super(statusCode, body);
102
+ constructor(messageOrStatus, body, retryAfter) {
103
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
104
+ type: ErrorType.RATE_LIMIT_ERROR,
105
+ code: "rate_limit_exceeded",
106
+ doc_url: `${DEFAULT_DOC_URL}#rate-limit-error`
107
+ });
108
+ super(statusCode === 0 ? 429 : statusCode, resolved);
71
109
  this.name = "CStarRateLimitError";
72
- this.retryAfter = retryAfter;
110
+ this.retryAfter = retryAfter ?? 60;
73
111
  }
74
112
  };
75
113
  var CStarNotFoundError = class extends CStarError {
76
- constructor(statusCode, body) {
77
- super(statusCode, body);
114
+ constructor(messageOrStatus, body) {
115
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
116
+ type: ErrorType.NOT_FOUND_ERROR,
117
+ code: "resource_not_found",
118
+ doc_url: `${DEFAULT_DOC_URL}#not-found-error`
119
+ });
120
+ super(statusCode, resolved);
78
121
  this.name = "CStarNotFoundError";
79
122
  }
80
123
  };
81
124
  var CStarValidationError = class extends CStarError {
82
- constructor(statusCode, body) {
83
- super(statusCode, body);
125
+ constructor(messageOrStatus, body) {
126
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
127
+ type: ErrorType.INVALID_REQUEST_ERROR,
128
+ code: "validation_error",
129
+ doc_url: `${DEFAULT_DOC_URL}#validation-error`
130
+ });
131
+ super(statusCode, resolved);
84
132
  this.name = "CStarValidationError";
85
133
  }
86
134
  };
87
135
  var CStarPermissionError = class extends CStarError {
88
- constructor(statusCode, body) {
89
- super(statusCode, body);
136
+ constructor(messageOrStatus, body) {
137
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
138
+ type: ErrorType.PERMISSION_ERROR,
139
+ code: "permission_denied",
140
+ doc_url: `${DEFAULT_DOC_URL}#permission-error`
141
+ });
142
+ super(statusCode, resolved);
90
143
  this.name = "CStarPermissionError";
91
144
  }
92
145
  };
93
146
  var CStarConflictError = class extends CStarError {
94
- constructor(statusCode, body) {
95
- super(statusCode, body);
147
+ constructor(messageOrStatus, body) {
148
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
149
+ type: ErrorType.CONFLICT_ERROR,
150
+ code: "conflict",
151
+ doc_url: `${DEFAULT_DOC_URL}#conflict-error`
152
+ });
153
+ super(statusCode, resolved);
96
154
  this.name = "CStarConflictError";
97
155
  }
98
156
  };
99
157
  var CStarServerError = class extends CStarError {
100
- constructor(statusCode, body) {
101
- super(statusCode, body);
158
+ constructor(messageOrStatus, body) {
159
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
160
+ type: ErrorType.SERVER_ERROR,
161
+ code: "server_error",
162
+ doc_url: `${DEFAULT_DOC_URL}#server-error`
163
+ });
164
+ super(statusCode, resolved);
102
165
  this.name = "CStarServerError";
103
166
  }
104
167
  };
168
+ var CStarFeatureDisabledError = class extends CStarError {
169
+ constructor(messageOrStatus, body) {
170
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
171
+ type: ErrorType.FEATURE_NOT_CONFIGURED,
172
+ code: "feature_not_configured",
173
+ doc_url: `${DEFAULT_DOC_URL}#feature-not-configured`
174
+ });
175
+ super(statusCode, resolved);
176
+ this.name = "CStarFeatureDisabledError";
177
+ this.feature = resolved.feature ?? "unknown";
178
+ this.enableUrl = resolved.enable_url ?? "";
179
+ }
180
+ };
181
+ var STATUS_TO_CLASS = {
182
+ 400: CStarValidationError,
183
+ 401: CStarAuthenticationError,
184
+ 403: CStarPermissionError,
185
+ 404: CStarNotFoundError,
186
+ 409: CStarConflictError,
187
+ 422: CStarValidationError,
188
+ 429: CStarRateLimitError
189
+ };
190
+ var TYPE_TO_CLASS = {
191
+ [ErrorType.AUTHENTICATION_ERROR]: CStarAuthenticationError,
192
+ [ErrorType.PERMISSION_ERROR]: CStarPermissionError,
193
+ [ErrorType.RATE_LIMIT_ERROR]: CStarRateLimitError,
194
+ [ErrorType.NOT_FOUND_ERROR]: CStarNotFoundError,
195
+ [ErrorType.INVALID_REQUEST_ERROR]: CStarValidationError,
196
+ [ErrorType.CONFLICT_ERROR]: CStarConflictError,
197
+ [ErrorType.SERVER_ERROR]: CStarServerError,
198
+ [ErrorType.FEATURE_NOT_CONFIGURED]: CStarFeatureDisabledError
199
+ };
105
200
  function parseError(statusCode, body, retryAfter) {
106
201
  const errorBody = body?.error ?? {
107
202
  type: ErrorType.API_ERROR,
108
203
  code: "unknown_error",
109
204
  message: `Request failed with status ${statusCode}`,
110
- doc_url: "https://cstar.help/developers/api/errors"
205
+ doc_url: DEFAULT_DOC_URL
111
206
  };
112
- switch (errorBody.type) {
113
- case ErrorType.AUTHENTICATION_ERROR:
114
- return new CStarAuthenticationError(statusCode, errorBody);
115
- case ErrorType.PERMISSION_ERROR:
116
- return new CStarPermissionError(statusCode, errorBody);
117
- case ErrorType.RATE_LIMIT_ERROR:
118
- return new CStarRateLimitError(statusCode, errorBody, retryAfter ?? 60);
119
- case ErrorType.NOT_FOUND_ERROR:
120
- return new CStarNotFoundError(statusCode, errorBody);
121
- case ErrorType.INVALID_REQUEST_ERROR:
122
- return new CStarValidationError(statusCode, errorBody);
123
- case ErrorType.CONFLICT_ERROR:
124
- return new CStarConflictError(statusCode, errorBody);
125
- case ErrorType.SERVER_ERROR:
126
- return new CStarServerError(statusCode, errorBody);
127
- default:
128
- if (statusCode === 403) return new CStarPermissionError(statusCode, errorBody);
129
- if (statusCode === 409) return new CStarConflictError(statusCode, errorBody);
130
- if (statusCode >= 500) return new CStarServerError(statusCode, errorBody);
131
- return new CStarError(statusCode, errorBody);
207
+ if (statusCode === 429) {
208
+ return new CStarRateLimitError(statusCode, errorBody, retryAfter ?? 60);
209
+ }
210
+ const StatusCls = STATUS_TO_CLASS[statusCode];
211
+ if (StatusCls) {
212
+ return new StatusCls(statusCode, errorBody);
213
+ }
214
+ const TypeCls = TYPE_TO_CLASS[errorBody.type];
215
+ if (TypeCls) {
216
+ return new TypeCls(statusCode, errorBody);
132
217
  }
218
+ if (statusCode >= 500) return new CStarServerError(statusCode, errorBody);
219
+ return new CStarError(statusCode, errorBody);
133
220
  }
134
221
 
135
222
  // src/client.ts
136
- var SDK_VERSION = "0.5.2";
223
+ var SDK_VERSION = "0.13.0";
137
224
  var DEFAULT_BASE_URL = "https://www.cstar.help";
138
225
  var DEFAULT_API_VERSION = "2026-03-01";
139
226
  var DEFAULT_MAX_RETRIES = 3;
140
227
  var DEFAULT_TIMEOUT = 3e4;
141
228
  var HttpClient = class {
142
229
  constructor(config) {
230
+ /**
231
+ * Side-channel metadata from the most recent response. Holds the server
232
+ * `requestId`, `timestamp`, and (for list endpoints) `pagination`. Mutable
233
+ * per-call — read it immediately after awaiting the call.
234
+ *
235
+ * ```ts
236
+ * const ticket = await cstar.tickets.create({...});
237
+ * console.error('Created ticket', ticket.id, 'request:', cstar.lastMeta.requestId);
238
+ * ```
239
+ */
240
+ this.lastMeta = { requestId: null, timestamp: null, pagination: null };
143
241
  if (!config || typeof config.apiKey !== "string" || config.apiKey.length === 0) {
144
242
  throw new Error(
145
243
  "[cStar] CStarClient: apiKey is required. Pass a secret key (sk_live_... / sk_test_...) or a publishable key (pk_live_... / pk_test_...). See https://cstar.help/developers/quickstart"
@@ -170,7 +268,32 @@ var HttpClient = class {
170
268
  * Automatically retries on 5xx errors and rate limits.
171
269
  */
172
270
  async request(method, path, options) {
173
- const url = this.buildUrl(path, options?.query);
271
+ return this._sendRequest(method, this.buildUrl(path, options?.query), options);
272
+ }
273
+ /**
274
+ * Like `request()` but skips the `/api/v1/teams/{teamId}` prefix. Used for
275
+ * the small set of non-team-scoped endpoints (`/auth/whoami`, etc).
276
+ *
277
+ * @internal — exposed to CStarClient for `whoami()` and similar; not part
278
+ * of the documented public surface.
279
+ */
280
+ async requestUnscoped(method, path, options) {
281
+ const baseUrl = this.config.baseUrl + "/api/v1" + path;
282
+ const finalUrl = this.appendQuery(baseUrl, options?.query);
283
+ return this._sendRequest(method, finalUrl, options);
284
+ }
285
+ appendQuery(url, query) {
286
+ if (!query) return url;
287
+ const params = new URLSearchParams();
288
+ for (const [key, value] of Object.entries(query)) {
289
+ if (value === void 0 || value === null) continue;
290
+ if (Array.isArray(value)) for (const v of value) params.append(`${key}[]`, v);
291
+ else params.set(key, String(value));
292
+ }
293
+ const qs = params.toString();
294
+ return qs ? `${url}?${qs}` : url;
295
+ }
296
+ async _sendRequest(method, url, options) {
174
297
  const headers = this.buildHeaders(options?.idempotencyKey);
175
298
  const body = options?.body ? JSON.stringify(options.body) : void 0;
176
299
  let lastError = null;
@@ -201,13 +324,9 @@ var HttpClient = class {
201
324
  if (response.ok) {
202
325
  if (response.status === 204) return void 0;
203
326
  const json = await response.json();
204
- const requestId2 = response.headers.get("x-request-id") ?? response.headers.get("x-cstar-request-id");
205
- if (requestId2 && json && typeof json === "object" && "meta" in json) {
206
- const existing = json.meta;
207
- const metaBase = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
208
- json.meta = { ...metaBase, requestId: requestId2 };
209
- }
210
- return json;
327
+ const headerRequestId = response.headers.get("x-request-id") ?? response.headers.get("x-cstar-request-id");
328
+ this.captureMeta(json, headerRequestId);
329
+ return this.unwrapEnvelope(json);
211
330
  }
212
331
  const errorBody = await response.json().catch(() => null);
213
332
  const retryAfter = parseInt(response.headers.get("Retry-After") ?? "", 10) || void 0;
@@ -229,6 +348,51 @@ var HttpClient = class {
229
348
  }
230
349
  throw lastError ?? new Error("Request failed after retries");
231
350
  }
351
+ /**
352
+ * Capture `meta`, `requestId`, and `pagination` from the raw response
353
+ * into `this.lastMeta` before unwrapping the envelope.
354
+ */
355
+ captureMeta(body, headerRequestId) {
356
+ if (body && typeof body === "object" && !Array.isArray(body)) {
357
+ const obj = body;
358
+ const meta = obj.meta && typeof obj.meta === "object" ? obj.meta : null;
359
+ const pagination = obj.pagination && typeof obj.pagination === "object" ? obj.pagination : null;
360
+ this.lastMeta = {
361
+ requestId: headerRequestId ?? (typeof meta?.requestId === "string" ? meta.requestId : null),
362
+ timestamp: typeof meta?.timestamp === "string" ? meta.timestamp : null,
363
+ pagination
364
+ };
365
+ return;
366
+ }
367
+ this.lastMeta = { requestId: headerRequestId, timestamp: null, pagination: null };
368
+ }
369
+ /**
370
+ * Unwrap the legacy `{success, data, meta, pagination?}` envelope.
371
+ * - `{success:true, data: array, pagination}` → `{data, pagination, hasMore}`
372
+ * - `{success:true, data: object}` → `data` (bare resource)
373
+ * - other shapes pass through unchanged so subpath SDKs (chat, library) keep
374
+ * their bespoke shapes for now.
375
+ */
376
+ unwrapEnvelope(body) {
377
+ if (!body || typeof body !== "object" || Array.isArray(body)) return body;
378
+ const obj = body;
379
+ if (obj.success !== true || !("data" in obj)) return body;
380
+ const data = obj.data;
381
+ if (Array.isArray(data)) {
382
+ const pagination = obj.pagination ?? null;
383
+ return {
384
+ data,
385
+ pagination: pagination ?? {
386
+ total: data.length,
387
+ page: 1,
388
+ pageSize: data.length,
389
+ hasMore: false
390
+ },
391
+ hasMore: pagination?.hasMore ?? false
392
+ };
393
+ }
394
+ return data;
395
+ }
232
396
  buildUrl(path, query) {
233
397
  const base = `${this.config.baseUrl}/api/v1/teams/${this.config.teamId}${path}`;
234
398
  if (!query) return base;
@@ -1910,7 +2074,12 @@ var GameResource = class {
1910
2074
  async getPlayer(memberId) {
1911
2075
  return this.client.request("GET", `/game/player/${memberId}`);
1912
2076
  }
1913
- /** Get the team leaderboard. */
2077
+ /**
2078
+ * Get the team leaderboard. Returns the unwrapped envelope from the
2079
+ * `/game/leaderboard` endpoint (`{entries, totalParticipants, period, ...}`)
2080
+ * — wider than a flat array because the server includes the period and
2081
+ * a participant count alongside the entries.
2082
+ */
1914
2083
  async leaderboard(params) {
1915
2084
  return this.client.request("GET", "/game/leaderboard", {
1916
2085
  query: params
@@ -1933,12 +2102,12 @@ var GameResource = class {
1933
2102
  */
1934
2103
  subscribeBoss(realtime) {
1935
2104
  const fetchFn = async () => {
1936
- const res = await this.boss.current();
2105
+ const boss = await this.boss.current();
2106
+ const list = boss.active ? [boss] : [];
1937
2107
  return {
1938
- success: true,
1939
- data: res.data.active ? [res.data] : [],
1940
- pagination: { total: res.data.active ? 1 : 0, page: 1, pageSize: 1, hasMore: false },
1941
- meta: { requestId: "", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
2108
+ data: list,
2109
+ pagination: { total: list.length, page: 1, pageSize: 1, hasMore: false },
2110
+ hasMore: false
1942
2111
  };
1943
2112
  };
1944
2113
  return new Subscription(realtime, fetchFn, "/game/boss");
@@ -1949,12 +2118,11 @@ var GameResource = class {
1949
2118
  */
1950
2119
  subscribePlayer(realtime, userId) {
1951
2120
  const fetchFn = async () => {
1952
- const res = await this.player({ userId });
2121
+ const player = await this.player({ userId });
1953
2122
  return {
1954
- success: true,
1955
- data: [res.data],
2123
+ data: [player],
1956
2124
  pagination: { total: 1, page: 1, pageSize: 1, hasMore: false },
1957
- meta: { requestId: "", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
2125
+ hasMore: false
1958
2126
  };
1959
2127
  };
1960
2128
  return new Subscription(realtime, fetchFn, "/game/player");
@@ -1965,14 +2133,12 @@ var GameResource = class {
1965
2133
  */
1966
2134
  subscribeLeaderboard(realtime, params) {
1967
2135
  const fetchFn = async () => {
1968
- const res = await this.leaderboard(params);
1969
- const envelope = res.data;
1970
- const entries = Array.isArray(envelope) ? envelope : envelope?.entries ?? [];
2136
+ const board = await this.leaderboard(params);
2137
+ const entries = board.entries ?? [];
1971
2138
  return {
1972
- success: true,
1973
2139
  data: entries,
1974
2140
  pagination: { total: entries.length, page: 1, pageSize: 100, hasMore: false },
1975
- meta: { requestId: "", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
2141
+ hasMore: false
1976
2142
  };
1977
2143
  };
1978
2144
  return new Subscription(realtime, fetchFn, "/game/leaderboard");
@@ -1983,13 +2149,12 @@ var GameResource = class {
1983
2149
  */
1984
2150
  subscribeActivity(realtime) {
1985
2151
  const fetchFn = async () => {
1986
- const res = await this.activity.list({ limit: 50 });
1987
- const data = res.data.data || [];
2152
+ const feed = await this.activity.list({ limit: 50 });
2153
+ const items = feed.data ?? [];
1988
2154
  return {
1989
- success: true,
1990
- data,
1991
- pagination: { total: data.length, page: 1, pageSize: 50, hasMore: false },
1992
- meta: { requestId: "", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
2155
+ data: items,
2156
+ pagination: { total: items.length, page: 1, pageSize: 50, hasMore: false },
2157
+ hasMore: false
1993
2158
  };
1994
2159
  };
1995
2160
  return new Subscription(realtime, fetchFn, "/game/activity");
@@ -2227,6 +2392,35 @@ var CStarClient = class {
2227
2392
  get isTestMode() {
2228
2393
  return this._httpClient.environment === "test";
2229
2394
  }
2395
+ /**
2396
+ * Validate the configured API key and return the team + key metadata it
2397
+ * resolves to. Useful for confirming the key is wired up correctly during
2398
+ * onboarding (and for AI assistants that want a one-call sanity check).
2399
+ *
2400
+ * Closes F-018.
2401
+ *
2402
+ * ```ts
2403
+ * const me = await cstar.whoami();
2404
+ * console.log(me.teamSlug, me.environment); // 'acme', 'test'
2405
+ * ```
2406
+ */
2407
+ async whoami() {
2408
+ return this._httpClient.requestUnscoped("GET", "/auth/whoami");
2409
+ }
2410
+ /**
2411
+ * Side-channel metadata from the most recent HTTP response. Holds the
2412
+ * server `requestId` (for support escalations), `timestamp`, and the
2413
+ * `pagination` info from list endpoints. Reset on every call — read it
2414
+ * immediately after awaiting the call you care about.
2415
+ *
2416
+ * ```ts
2417
+ * const ticket = await cstar.tickets.create({...});
2418
+ * console.error('Created', ticket.id, 'request:', cstar.lastMeta.requestId);
2419
+ * ```
2420
+ */
2421
+ get lastMeta() {
2422
+ return this._httpClient.lastMeta;
2423
+ }
2230
2424
  };
2231
2425
  // Annotate the CommonJS export names for ESM import in node:
2232
2426
  0 && (module.exports = {
@@ -2234,6 +2428,7 @@ var CStarClient = class {
2234
2428
  CStarClient,
2235
2429
  CStarConflictError,
2236
2430
  CStarError,
2431
+ CStarFeatureDisabledError,
2237
2432
  CStarNotFoundError,
2238
2433
  CStarPermissionError,
2239
2434
  CStarRateLimitError,