@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.
@@ -1,3 +1,216 @@
1
+ // src/errors.ts
2
+ var ErrorType = {
3
+ AUTHENTICATION_ERROR: "authentication_error",
4
+ PERMISSION_ERROR: "permission_error",
5
+ INVALID_REQUEST_ERROR: "invalid_request_error",
6
+ NOT_FOUND_ERROR: "not_found_error",
7
+ CONFLICT_ERROR: "conflict_error",
8
+ RATE_LIMIT_ERROR: "rate_limit_error",
9
+ FEATURE_NOT_CONFIGURED: "feature_not_configured",
10
+ API_ERROR: "api_error",
11
+ SERVER_ERROR: "server_error"
12
+ };
13
+ var DEFAULT_DOC_URL = "https://cstar.help/developers/api/errors";
14
+ function normalizeArgs(messageOrStatus, maybeBody, defaults) {
15
+ if (typeof messageOrStatus === "string") {
16
+ return {
17
+ statusCode: defaults?.code === "rate_limit_error" ? 429 : 0,
18
+ body: {
19
+ type: defaults?.type ?? ErrorType.API_ERROR,
20
+ code: defaults?.code ?? "sdk_error",
21
+ message: messageOrStatus,
22
+ doc_url: defaults?.doc_url ?? DEFAULT_DOC_URL,
23
+ ...defaults?.feature ? { feature: defaults.feature } : {},
24
+ ...defaults?.enable_url ? { enable_url: defaults.enable_url } : {}
25
+ }
26
+ };
27
+ }
28
+ return {
29
+ statusCode: messageOrStatus,
30
+ body: maybeBody ?? {
31
+ type: defaults?.type ?? ErrorType.API_ERROR,
32
+ code: defaults?.code ?? "unknown_error",
33
+ message: defaults?.message ?? `Request failed with status ${messageOrStatus}`,
34
+ doc_url: defaults?.doc_url ?? DEFAULT_DOC_URL
35
+ }
36
+ };
37
+ }
38
+ var CStarError = class extends Error {
39
+ constructor(messageOrStatus, body) {
40
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body);
41
+ super(resolved.message);
42
+ this.name = "CStarError";
43
+ this.type = resolved.type;
44
+ this.code = resolved.code;
45
+ this.param = resolved.param;
46
+ this.docUrl = resolved.doc_url;
47
+ this.requestId = resolved.request_id;
48
+ this.statusCode = statusCode;
49
+ }
50
+ };
51
+ var CStarAuthenticationError = class extends CStarError {
52
+ constructor(messageOrStatus, body) {
53
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
54
+ type: ErrorType.AUTHENTICATION_ERROR,
55
+ code: "authentication_failed",
56
+ doc_url: `${DEFAULT_DOC_URL}#authentication-error`
57
+ });
58
+ super(statusCode, resolved);
59
+ this.name = "CStarAuthenticationError";
60
+ }
61
+ };
62
+ var CStarRateLimitError = class extends CStarError {
63
+ constructor(messageOrStatus, body, retryAfter) {
64
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
65
+ type: ErrorType.RATE_LIMIT_ERROR,
66
+ code: "rate_limit_exceeded",
67
+ doc_url: `${DEFAULT_DOC_URL}#rate-limit-error`
68
+ });
69
+ super(statusCode === 0 ? 429 : statusCode, resolved);
70
+ this.name = "CStarRateLimitError";
71
+ this.retryAfter = retryAfter ?? 60;
72
+ }
73
+ };
74
+ var CStarNotFoundError = class extends CStarError {
75
+ constructor(messageOrStatus, body) {
76
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
77
+ type: ErrorType.NOT_FOUND_ERROR,
78
+ code: "resource_not_found",
79
+ doc_url: `${DEFAULT_DOC_URL}#not-found-error`
80
+ });
81
+ super(statusCode, resolved);
82
+ this.name = "CStarNotFoundError";
83
+ }
84
+ };
85
+ var CStarValidationError = class extends CStarError {
86
+ constructor(messageOrStatus, body) {
87
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
88
+ type: ErrorType.INVALID_REQUEST_ERROR,
89
+ code: "validation_error",
90
+ doc_url: `${DEFAULT_DOC_URL}#validation-error`
91
+ });
92
+ super(statusCode, resolved);
93
+ this.name = "CStarValidationError";
94
+ }
95
+ };
96
+ var CStarPermissionError = class extends CStarError {
97
+ constructor(messageOrStatus, body) {
98
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
99
+ type: ErrorType.PERMISSION_ERROR,
100
+ code: "permission_denied",
101
+ doc_url: `${DEFAULT_DOC_URL}#permission-error`
102
+ });
103
+ super(statusCode, resolved);
104
+ this.name = "CStarPermissionError";
105
+ }
106
+ };
107
+ var CStarConflictError = class extends CStarError {
108
+ constructor(messageOrStatus, body) {
109
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
110
+ type: ErrorType.CONFLICT_ERROR,
111
+ code: "conflict",
112
+ doc_url: `${DEFAULT_DOC_URL}#conflict-error`
113
+ });
114
+ super(statusCode, resolved);
115
+ this.name = "CStarConflictError";
116
+ }
117
+ };
118
+ var CStarServerError = class extends CStarError {
119
+ constructor(messageOrStatus, body) {
120
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
121
+ type: ErrorType.SERVER_ERROR,
122
+ code: "server_error",
123
+ doc_url: `${DEFAULT_DOC_URL}#server-error`
124
+ });
125
+ super(statusCode, resolved);
126
+ this.name = "CStarServerError";
127
+ }
128
+ };
129
+ var CStarFeatureDisabledError = class extends CStarError {
130
+ constructor(messageOrStatus, body) {
131
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
132
+ type: ErrorType.FEATURE_NOT_CONFIGURED,
133
+ code: "feature_not_configured",
134
+ doc_url: `${DEFAULT_DOC_URL}#feature-not-configured`
135
+ });
136
+ super(statusCode, resolved);
137
+ this.name = "CStarFeatureDisabledError";
138
+ this.feature = resolved.feature ?? "unknown";
139
+ this.enableUrl = resolved.enable_url ?? "";
140
+ }
141
+ };
142
+ function parsePublicError(status, body, fallbackMessage) {
143
+ const obj = body && typeof body === "object" ? body : null;
144
+ const errVal = obj?.error;
145
+ let message = fallbackMessage;
146
+ let structured = null;
147
+ if (typeof errVal === "string") {
148
+ message = errVal;
149
+ } else if (errVal && typeof errVal === "object") {
150
+ const e = errVal;
151
+ if (typeof e.message === "string") message = e.message;
152
+ structured = {
153
+ type: typeof e.type === "string" ? e.type : ErrorType.API_ERROR,
154
+ code: typeof e.code === "string" ? e.code : "api_error",
155
+ message,
156
+ param: typeof e.param === "string" ? e.param : void 0,
157
+ doc_url: typeof e.doc_url === "string" ? e.doc_url : DEFAULT_DOC_URL,
158
+ request_id: typeof e.request_id === "string" ? e.request_id : void 0,
159
+ feature: typeof e.feature === "string" ? e.feature : void 0,
160
+ enable_url: typeof e.enable_url === "string" ? e.enable_url : void 0
161
+ };
162
+ } else if (typeof obj?.message === "string") {
163
+ message = obj.message;
164
+ }
165
+ const errorBody = structured ?? {
166
+ type: ErrorType.API_ERROR,
167
+ code: status >= 500 ? "server_error" : "request_failed",
168
+ message,
169
+ doc_url: DEFAULT_DOC_URL
170
+ };
171
+ return parseError(status, { error: errorBody });
172
+ }
173
+ var STATUS_TO_CLASS = {
174
+ 400: CStarValidationError,
175
+ 401: CStarAuthenticationError,
176
+ 403: CStarPermissionError,
177
+ 404: CStarNotFoundError,
178
+ 409: CStarConflictError,
179
+ 422: CStarValidationError,
180
+ 429: CStarRateLimitError
181
+ };
182
+ var TYPE_TO_CLASS = {
183
+ [ErrorType.AUTHENTICATION_ERROR]: CStarAuthenticationError,
184
+ [ErrorType.PERMISSION_ERROR]: CStarPermissionError,
185
+ [ErrorType.RATE_LIMIT_ERROR]: CStarRateLimitError,
186
+ [ErrorType.NOT_FOUND_ERROR]: CStarNotFoundError,
187
+ [ErrorType.INVALID_REQUEST_ERROR]: CStarValidationError,
188
+ [ErrorType.CONFLICT_ERROR]: CStarConflictError,
189
+ [ErrorType.SERVER_ERROR]: CStarServerError,
190
+ [ErrorType.FEATURE_NOT_CONFIGURED]: CStarFeatureDisabledError
191
+ };
192
+ function parseError(statusCode, body, retryAfter) {
193
+ const errorBody = body?.error ?? {
194
+ type: ErrorType.API_ERROR,
195
+ code: "unknown_error",
196
+ message: `Request failed with status ${statusCode}`,
197
+ doc_url: DEFAULT_DOC_URL
198
+ };
199
+ if (statusCode === 429) {
200
+ return new CStarRateLimitError(statusCode, errorBody, retryAfter ?? 60);
201
+ }
202
+ const StatusCls = STATUS_TO_CLASS[statusCode];
203
+ if (StatusCls) {
204
+ return new StatusCls(statusCode, errorBody);
205
+ }
206
+ const TypeCls = TYPE_TO_CLASS[errorBody.type];
207
+ if (TypeCls) {
208
+ return new TypeCls(statusCode, errorBody);
209
+ }
210
+ if (statusCode >= 500) return new CStarServerError(statusCode, errorBody);
211
+ return new CStarError(statusCode, errorBody);
212
+ }
213
+
1
214
  // src/library/index.ts
2
215
  var DEFAULT_BASE_URL = "https://www.cstar.help";
3
216
  var LibraryClient = class {
@@ -5,6 +218,13 @@ var LibraryClient = class {
5
218
  this.resolvedTeamId = null;
6
219
  this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
7
220
  this.teamSlug = config.teamSlug;
221
+ this.publishableKey = config.publishableKey ?? null;
222
+ this.mode = this.publishableKey?.startsWith("pk_test_") ? "test" : "live";
223
+ if (this.publishableKey && !/^pk_(test|live)_/.test(this.publishableKey)) {
224
+ throw new Error(
225
+ "[cStar] LibraryClient: publishableKey must start with `pk_test_` or `pk_live_`. Secret keys (`sk_*`) cannot be used in browser/anonymous contexts."
226
+ );
227
+ }
8
228
  }
9
229
  /** List all public categories with article counts. */
10
230
  async categories() {
@@ -54,13 +274,19 @@ var LibraryClient = class {
54
274
  */
55
275
  async recordView(slug) {
56
276
  const url = `${this.baseUrl}/api/library/${this.teamSlug}/articles/${slug}/view`;
277
+ const headers = { "Content-Type": "application/json" };
278
+ if (this.publishableKey) headers["Authorization"] = `Bearer ${this.publishableKey}`;
57
279
  const response = await globalThis.fetch(url, {
58
280
  method: "POST",
59
- headers: { "Content-Type": "application/json" }
281
+ headers
60
282
  });
61
283
  if (!response.ok) {
62
- const errorBody = await response.json().catch(() => ({ error: "Request failed" }));
63
- throw new Error(errorBody.error ?? `View tracking failed: ${response.status}`);
284
+ const errorBody = await response.json().catch(() => null);
285
+ throw parsePublicError(
286
+ response.status,
287
+ errorBody,
288
+ `View tracking failed: ${response.status}`
289
+ );
64
290
  }
65
291
  const body = await response.json();
66
292
  return { viewCount: body.viewCount };
@@ -82,14 +308,27 @@ var LibraryClient = class {
82
308
  */
83
309
  async submitFeedback(slug, params) {
84
310
  const url = `${this.baseUrl}/api/library/${this.teamSlug}/articles/${slug}/feedback`;
311
+ const headers = { "Content-Type": "application/json" };
312
+ if (this.publishableKey) headers["Authorization"] = `Bearer ${this.publishableKey}`;
85
313
  const response = await globalThis.fetch(url, {
86
314
  method: "POST",
87
- headers: { "Content-Type": "application/json" },
315
+ headers,
88
316
  body: JSON.stringify(params)
89
317
  });
90
- const body = await response.json();
318
+ const body = await response.json().catch(() => null);
91
319
  if (!response.ok) {
92
- throw new Error(body.error ?? `Feedback submission failed: ${response.status}`);
320
+ throw parsePublicError(
321
+ response.status,
322
+ body,
323
+ body?.error ?? `Feedback submission failed: ${response.status}`
324
+ );
325
+ }
326
+ if (body === null) {
327
+ throw parsePublicError(
328
+ response.status,
329
+ null,
330
+ `Feedback submission returned an invalid JSON response (status ${response.status})`
331
+ );
93
332
  }
94
333
  return body;
95
334
  }
@@ -120,13 +359,17 @@ var LibraryClient = class {
120
359
  const qs = params.toString();
121
360
  if (qs) url += `?${qs}`;
122
361
  }
362
+ const headers = { "Content-Type": "application/json" };
363
+ if (this.publishableKey) {
364
+ headers["Authorization"] = `Bearer ${this.publishableKey}`;
365
+ }
123
366
  const response = await globalThis.fetch(url, {
124
367
  method: "GET",
125
- headers: { "Content-Type": "application/json" }
368
+ headers
126
369
  });
127
370
  if (!response.ok) {
128
- const errorBody = await response.json().catch(() => ({ error: { message: "Request failed" } }));
129
- throw new Error(errorBody.error?.message ?? `Library API error: ${response.status}`);
371
+ const errorBody = await response.json().catch(() => null);
372
+ throw parsePublicError(response.status, errorBody, `Library API error: ${response.status}`);
130
373
  }
131
374
  return response.json();
132
375
  }
@@ -24,6 +24,160 @@ __export(quickhelp_exports, {
24
24
  QuickHelpError: () => QuickHelpError
25
25
  });
26
26
  module.exports = __toCommonJS(quickhelp_exports);
27
+
28
+ // src/errors.ts
29
+ var ErrorType = {
30
+ AUTHENTICATION_ERROR: "authentication_error",
31
+ PERMISSION_ERROR: "permission_error",
32
+ INVALID_REQUEST_ERROR: "invalid_request_error",
33
+ NOT_FOUND_ERROR: "not_found_error",
34
+ CONFLICT_ERROR: "conflict_error",
35
+ RATE_LIMIT_ERROR: "rate_limit_error",
36
+ FEATURE_NOT_CONFIGURED: "feature_not_configured",
37
+ API_ERROR: "api_error",
38
+ SERVER_ERROR: "server_error"
39
+ };
40
+ var DEFAULT_DOC_URL = "https://cstar.help/developers/api/errors";
41
+ function normalizeArgs(messageOrStatus, maybeBody, defaults) {
42
+ if (typeof messageOrStatus === "string") {
43
+ return {
44
+ statusCode: defaults?.code === "rate_limit_error" ? 429 : 0,
45
+ body: {
46
+ type: defaults?.type ?? ErrorType.API_ERROR,
47
+ code: defaults?.code ?? "sdk_error",
48
+ message: messageOrStatus,
49
+ doc_url: defaults?.doc_url ?? DEFAULT_DOC_URL,
50
+ ...defaults?.feature ? { feature: defaults.feature } : {},
51
+ ...defaults?.enable_url ? { enable_url: defaults.enable_url } : {}
52
+ }
53
+ };
54
+ }
55
+ return {
56
+ statusCode: messageOrStatus,
57
+ body: maybeBody ?? {
58
+ type: defaults?.type ?? ErrorType.API_ERROR,
59
+ code: defaults?.code ?? "unknown_error",
60
+ message: defaults?.message ?? `Request failed with status ${messageOrStatus}`,
61
+ doc_url: defaults?.doc_url ?? DEFAULT_DOC_URL
62
+ }
63
+ };
64
+ }
65
+ var CStarError = class extends Error {
66
+ constructor(messageOrStatus, body) {
67
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body);
68
+ super(resolved.message);
69
+ this.name = "CStarError";
70
+ this.type = resolved.type;
71
+ this.code = resolved.code;
72
+ this.param = resolved.param;
73
+ this.docUrl = resolved.doc_url;
74
+ this.requestId = resolved.request_id;
75
+ this.statusCode = statusCode;
76
+ }
77
+ };
78
+ var CStarAuthenticationError = class extends CStarError {
79
+ constructor(messageOrStatus, body) {
80
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
81
+ type: ErrorType.AUTHENTICATION_ERROR,
82
+ code: "authentication_failed",
83
+ doc_url: `${DEFAULT_DOC_URL}#authentication-error`
84
+ });
85
+ super(statusCode, resolved);
86
+ this.name = "CStarAuthenticationError";
87
+ }
88
+ };
89
+ var CStarRateLimitError = class extends CStarError {
90
+ constructor(messageOrStatus, body, retryAfter) {
91
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
92
+ type: ErrorType.RATE_LIMIT_ERROR,
93
+ code: "rate_limit_exceeded",
94
+ doc_url: `${DEFAULT_DOC_URL}#rate-limit-error`
95
+ });
96
+ super(statusCode === 0 ? 429 : statusCode, resolved);
97
+ this.name = "CStarRateLimitError";
98
+ this.retryAfter = retryAfter ?? 60;
99
+ }
100
+ };
101
+ var CStarNotFoundError = class extends CStarError {
102
+ constructor(messageOrStatus, body) {
103
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
104
+ type: ErrorType.NOT_FOUND_ERROR,
105
+ code: "resource_not_found",
106
+ doc_url: `${DEFAULT_DOC_URL}#not-found-error`
107
+ });
108
+ super(statusCode, resolved);
109
+ this.name = "CStarNotFoundError";
110
+ }
111
+ };
112
+ var CStarValidationError = class extends CStarError {
113
+ constructor(messageOrStatus, body) {
114
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
115
+ type: ErrorType.INVALID_REQUEST_ERROR,
116
+ code: "validation_error",
117
+ doc_url: `${DEFAULT_DOC_URL}#validation-error`
118
+ });
119
+ super(statusCode, resolved);
120
+ this.name = "CStarValidationError";
121
+ }
122
+ };
123
+ var CStarPermissionError = class extends CStarError {
124
+ constructor(messageOrStatus, body) {
125
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
126
+ type: ErrorType.PERMISSION_ERROR,
127
+ code: "permission_denied",
128
+ doc_url: `${DEFAULT_DOC_URL}#permission-error`
129
+ });
130
+ super(statusCode, resolved);
131
+ this.name = "CStarPermissionError";
132
+ }
133
+ };
134
+ var CStarConflictError = class extends CStarError {
135
+ constructor(messageOrStatus, body) {
136
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
137
+ type: ErrorType.CONFLICT_ERROR,
138
+ code: "conflict",
139
+ doc_url: `${DEFAULT_DOC_URL}#conflict-error`
140
+ });
141
+ super(statusCode, resolved);
142
+ this.name = "CStarConflictError";
143
+ }
144
+ };
145
+ var CStarServerError = class extends CStarError {
146
+ constructor(messageOrStatus, body) {
147
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
148
+ type: ErrorType.SERVER_ERROR,
149
+ code: "server_error",
150
+ doc_url: `${DEFAULT_DOC_URL}#server-error`
151
+ });
152
+ super(statusCode, resolved);
153
+ this.name = "CStarServerError";
154
+ }
155
+ };
156
+ var CStarFeatureDisabledError = class extends CStarError {
157
+ constructor(messageOrStatus, body) {
158
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
159
+ type: ErrorType.FEATURE_NOT_CONFIGURED,
160
+ code: "feature_not_configured",
161
+ doc_url: `${DEFAULT_DOC_URL}#feature-not-configured`
162
+ });
163
+ super(statusCode, resolved);
164
+ this.name = "CStarFeatureDisabledError";
165
+ this.feature = resolved.feature ?? "unknown";
166
+ this.enableUrl = resolved.enable_url ?? "";
167
+ }
168
+ };
169
+ var TYPE_TO_CLASS = {
170
+ [ErrorType.AUTHENTICATION_ERROR]: CStarAuthenticationError,
171
+ [ErrorType.PERMISSION_ERROR]: CStarPermissionError,
172
+ [ErrorType.RATE_LIMIT_ERROR]: CStarRateLimitError,
173
+ [ErrorType.NOT_FOUND_ERROR]: CStarNotFoundError,
174
+ [ErrorType.INVALID_REQUEST_ERROR]: CStarValidationError,
175
+ [ErrorType.CONFLICT_ERROR]: CStarConflictError,
176
+ [ErrorType.SERVER_ERROR]: CStarServerError,
177
+ [ErrorType.FEATURE_NOT_CONFIGURED]: CStarFeatureDisabledError
178
+ };
179
+
180
+ // src/quickhelp/index.ts
27
181
  var DEFAULT_BASE_URL = "https://www.cstar.help";
28
182
  var QuickHelpClient = class {
29
183
  constructor(config) {
@@ -32,6 +186,16 @@ var QuickHelpClient = class {
32
186
  }
33
187
  this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
34
188
  this.teamId = config.teamId;
189
+ this.publishableKey = config.publishableKey ?? null;
190
+ this.mode = this.publishableKey?.startsWith("pk_test_") ? "test" : "live";
191
+ if (this.publishableKey && !/^pk_(test|live)_/.test(this.publishableKey)) {
192
+ throw new Error(
193
+ "[cStar] QuickHelpClient: publishableKey must start with `pk_test_` or `pk_live_`. Secret keys (`sk_*`) cannot be used in browser/anonymous contexts."
194
+ );
195
+ }
196
+ }
197
+ authHeaders() {
198
+ return this.publishableKey ? { Authorization: `Bearer ${this.publishableKey}` } : {};
35
199
  }
36
200
  /**
37
201
  * Fetch the sanitized public config (title, subtitle, placeholder,
@@ -42,7 +206,7 @@ var QuickHelpClient = class {
42
206
  */
43
207
  async config() {
44
208
  const url = `${this.baseUrl}/api/quickhelp/config/${encodeURIComponent(this.teamId)}`;
45
- const res = await globalThis.fetch(url, { method: "GET" });
209
+ const res = await globalThis.fetch(url, { method: "GET", headers: this.authHeaders() });
46
210
  if (res.status === 404) return null;
47
211
  if (!res.ok) {
48
212
  const body2 = await res.json().catch(() => null);
@@ -61,7 +225,7 @@ var QuickHelpClient = class {
61
225
  const url = `${this.baseUrl}/api/quickhelp/ask`;
62
226
  const res = await globalThis.fetch(url, {
63
227
  method: "POST",
64
- headers: { "Content-Type": "application/json" },
228
+ headers: { "Content-Type": "application/json", ...this.authHeaders() },
65
229
  body: JSON.stringify({
66
230
  team_id: this.teamId,
67
231
  session_id: params.sessionId ?? null,
@@ -70,6 +234,16 @@ var QuickHelpClient = class {
70
234
  });
71
235
  const body = await res.json().catch(() => null);
72
236
  if (!res.ok || !body?.success || !body.data) {
237
+ if (res.status === 503 || body?.error?.code === "feature_not_configured") {
238
+ throw new CStarFeatureDisabledError(res.status, {
239
+ type: "feature_not_configured",
240
+ code: "feature_not_configured",
241
+ message: body?.error?.message ?? "Quick Help is not available on this team. Enable an AI Agent (Settings \u2192 AI Agents) and configure Quick Help (Settings \u2192 Quick Help) before calling .ask().",
242
+ doc_url: "https://cstar.help/developers/api/errors#feature-not-configured",
243
+ feature: "quickHelp",
244
+ enable_url: `${this.baseUrl}/team/${encodeURIComponent(this.teamId)}/settings?section=quickHelp`
245
+ });
246
+ }
73
247
  const message = body?.error?.message ?? `Quick Help ask failed: ${res.status}`;
74
248
  const err = new QuickHelpError(message, {
75
249
  status: res.status,
@@ -2,6 +2,13 @@
2
2
  interface QuickHelpConfig {
3
3
  /** Your team's UUID. Required. */
4
4
  teamId: string;
5
+ /**
6
+ * Optional publishable key (`pk_test_*` or `pk_live_*`). Sent as
7
+ * `Authorization: Bearer` so test-mode dev sessions can be scoped
8
+ * separately from live customer sessions in your analytics. Backwards-
9
+ * compatible: omit and the client behaves exactly as before.
10
+ */
11
+ publishableKey?: string;
5
12
  /** Override the base URL (defaults to https://www.cstar.help). */
6
13
  baseUrl?: string;
7
14
  }
@@ -61,7 +68,11 @@ interface QuickHelpAskResult {
61
68
  declare class QuickHelpClient {
62
69
  private readonly baseUrl;
63
70
  private readonly teamId;
71
+ private readonly publishableKey;
72
+ /** Resolved mode based on the publishable-key prefix. */
73
+ readonly mode: 'live' | 'test';
64
74
  constructor(config: QuickHelpConfig);
75
+ private authHeaders;
65
76
  /**
66
77
  * Fetch the sanitized public config (title, subtitle, placeholder,
67
78
  * theme overrides). Never includes agent id or escalation contact
@@ -2,6 +2,13 @@
2
2
  interface QuickHelpConfig {
3
3
  /** Your team's UUID. Required. */
4
4
  teamId: string;
5
+ /**
6
+ * Optional publishable key (`pk_test_*` or `pk_live_*`). Sent as
7
+ * `Authorization: Bearer` so test-mode dev sessions can be scoped
8
+ * separately from live customer sessions in your analytics. Backwards-
9
+ * compatible: omit and the client behaves exactly as before.
10
+ */
11
+ publishableKey?: string;
5
12
  /** Override the base URL (defaults to https://www.cstar.help). */
6
13
  baseUrl?: string;
7
14
  }
@@ -61,7 +68,11 @@ interface QuickHelpAskResult {
61
68
  declare class QuickHelpClient {
62
69
  private readonly baseUrl;
63
70
  private readonly teamId;
71
+ private readonly publishableKey;
72
+ /** Resolved mode based on the publishable-key prefix. */
73
+ readonly mode: 'live' | 'test';
64
74
  constructor(config: QuickHelpConfig);
75
+ private authHeaders;
65
76
  /**
66
77
  * Fetch the sanitized public config (title, subtitle, placeholder,
67
78
  * theme overrides). Never includes agent id or escalation contact