@cstar.help/js 0.12.1 → 0.14.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,14 +24,215 @@ __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) {
30
- if (!config?.teamId) {
31
- throw new Error("QuickHelpClient: teamId is required");
184
+ this.resolveTeamIdPromise = null;
185
+ if (!config?.teamId && !config?.teamSlug) {
186
+ throw new Error(
187
+ 'QuickHelpClient: either `teamId` or `teamSlug` is required. Pass `teamSlug` (e.g. "acme") for the easiest setup.'
188
+ );
32
189
  }
33
190
  this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
34
- this.teamId = config.teamId;
191
+ this.teamId = config.teamId ?? null;
192
+ this.teamSlug = config.teamSlug ?? null;
193
+ this.publishableKey = config.publishableKey ?? null;
194
+ this.mode = this.publishableKey?.startsWith("pk_test_") ? "test" : "live";
195
+ if (this.publishableKey && !/^pk_(test|live)_/.test(this.publishableKey)) {
196
+ throw new Error(
197
+ "[cStar] QuickHelpClient: publishableKey must start with `pk_test_` or `pk_live_`. Secret keys (`sk_*`) cannot be used in browser/anonymous contexts."
198
+ );
199
+ }
200
+ }
201
+ authHeaders() {
202
+ return this.publishableKey ? { Authorization: `Bearer ${this.publishableKey}` } : {};
203
+ }
204
+ /**
205
+ * Resolve `teamSlug` → `teamId` lazily on the first call that needs it.
206
+ * Cached for the life of the client. NEW-v2-05.
207
+ */
208
+ async resolveTeamId() {
209
+ if (this.teamId) return this.teamId;
210
+ if (!this.teamSlug) throw new Error("QuickHelpClient: no teamId or teamSlug set");
211
+ if (this.resolveTeamIdPromise) return this.resolveTeamIdPromise;
212
+ this.resolveTeamIdPromise = (async () => {
213
+ const url = `${this.baseUrl}/api/widget/team-by-slug?teamSlug=${encodeURIComponent(this.teamSlug)}`;
214
+ const res = await globalThis.fetch(url, { method: "GET", headers: this.authHeaders() });
215
+ if (!res.ok) {
216
+ throw new Error(
217
+ `QuickHelpClient: could not resolve teamSlug "${this.teamSlug}" (HTTP ${res.status}).`
218
+ );
219
+ }
220
+ const body = await res.json();
221
+ const id = body?.data?.teamId;
222
+ if (!id) {
223
+ throw new Error(
224
+ `QuickHelpClient: lookup for teamSlug "${this.teamSlug}" returned no teamId.`
225
+ );
226
+ }
227
+ this.teamId = id;
228
+ return id;
229
+ })();
230
+ try {
231
+ return await this.resolveTeamIdPromise;
232
+ } catch (err) {
233
+ this.resolveTeamIdPromise = null;
234
+ throw err;
235
+ }
35
236
  }
36
237
  /**
37
238
  * Fetch the sanitized public config (title, subtitle, placeholder,
@@ -41,8 +242,9 @@ var QuickHelpClient = class {
41
242
  * Returns `null` if Quick Help is disabled on the team (404).
42
243
  */
43
244
  async config() {
44
- const url = `${this.baseUrl}/api/quickhelp/config/${encodeURIComponent(this.teamId)}`;
45
- const res = await globalThis.fetch(url, { method: "GET" });
245
+ const teamId = await this.resolveTeamId();
246
+ const url = `${this.baseUrl}/api/quickhelp/config/${encodeURIComponent(teamId)}`;
247
+ const res = await globalThis.fetch(url, { method: "GET", headers: this.authHeaders() });
46
248
  if (res.status === 404) return null;
47
249
  if (!res.ok) {
48
250
  const body2 = await res.json().catch(() => null);
@@ -58,18 +260,29 @@ var QuickHelpClient = class {
58
260
  * property set from the response body.
59
261
  */
60
262
  async ask(params) {
263
+ const teamId = await this.resolveTeamId();
61
264
  const url = `${this.baseUrl}/api/quickhelp/ask`;
62
265
  const res = await globalThis.fetch(url, {
63
266
  method: "POST",
64
- headers: { "Content-Type": "application/json" },
267
+ headers: { "Content-Type": "application/json", ...this.authHeaders() },
65
268
  body: JSON.stringify({
66
- team_id: this.teamId,
269
+ team_id: teamId,
67
270
  session_id: params.sessionId ?? null,
68
271
  question: params.question
69
272
  })
70
273
  });
71
274
  const body = await res.json().catch(() => null);
72
275
  if (!res.ok || !body?.success || !body.data) {
276
+ if (res.status === 503 || body?.error?.code === "feature_not_configured") {
277
+ throw new CStarFeatureDisabledError(res.status, {
278
+ type: "feature_not_configured",
279
+ code: "feature_not_configured",
280
+ 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().",
281
+ doc_url: "https://cstar.help/developers/api/errors#feature-not-configured",
282
+ feature: "quickHelp",
283
+ enable_url: `${this.baseUrl}/team/${encodeURIComponent(teamId)}/settings?section=quickHelp`
284
+ });
285
+ }
73
286
  const message = body?.error?.message ?? `Quick Help ask failed: ${res.status}`;
74
287
  const err = new QuickHelpError(message, {
75
288
  status: res.status,
@@ -1,7 +1,28 @@
1
- /** Options for the QuickHelpClient. No auth — Quick Help is anonymous by design. */
1
+ /**
2
+ * Options for the QuickHelpClient. No auth — Quick Help is anonymous by
3
+ * design.
4
+ *
5
+ * NEW-v2-05: pass either `teamId` (UUID, fastest — no roundtrip) or the
6
+ * easier-to-discover `teamSlug`. The slug is resolved to a UUID on the
7
+ * first network call and cached for the life of the client. At least one
8
+ * is required.
9
+ */
2
10
  interface QuickHelpConfig {
3
- /** Your team's UUID. Required. */
4
- teamId: string;
11
+ /** Your team's UUID. Either this or `teamSlug` is required. */
12
+ teamId?: string;
13
+ /**
14
+ * Your team's slug (e.g. `acme`). Resolved to `teamId` lazily via the
15
+ * public `/api/widget/team-by-slug` endpoint on the first call. Cached
16
+ * thereafter. Either this or `teamId` is required.
17
+ */
18
+ teamSlug?: string;
19
+ /**
20
+ * Optional publishable key (`pk_test_*` or `pk_live_*`). Sent as
21
+ * `Authorization: Bearer` so test-mode dev sessions can be scoped
22
+ * separately from live customer sessions in your analytics. Backwards-
23
+ * compatible: omit and the client behaves exactly as before.
24
+ */
25
+ publishableKey?: string;
5
26
  /** Override the base URL (defaults to https://www.cstar.help). */
6
27
  baseUrl?: string;
7
28
  }
@@ -60,8 +81,20 @@ interface QuickHelpAskResult {
60
81
  */
61
82
  declare class QuickHelpClient {
62
83
  private readonly baseUrl;
63
- private readonly teamId;
84
+ /** Cached resolved teamId. `null` until the first lookup completes. */
85
+ private teamId;
86
+ private readonly teamSlug;
87
+ private resolveTeamIdPromise;
88
+ private readonly publishableKey;
89
+ /** Resolved mode based on the publishable-key prefix. */
90
+ readonly mode: 'live' | 'test';
64
91
  constructor(config: QuickHelpConfig);
92
+ private authHeaders;
93
+ /**
94
+ * Resolve `teamSlug` → `teamId` lazily on the first call that needs it.
95
+ * Cached for the life of the client. NEW-v2-05.
96
+ */
97
+ private resolveTeamId;
65
98
  /**
66
99
  * Fetch the sanitized public config (title, subtitle, placeholder,
67
100
  * theme overrides). Never includes agent id or escalation contact
@@ -1,7 +1,28 @@
1
- /** Options for the QuickHelpClient. No auth — Quick Help is anonymous by design. */
1
+ /**
2
+ * Options for the QuickHelpClient. No auth — Quick Help is anonymous by
3
+ * design.
4
+ *
5
+ * NEW-v2-05: pass either `teamId` (UUID, fastest — no roundtrip) or the
6
+ * easier-to-discover `teamSlug`. The slug is resolved to a UUID on the
7
+ * first network call and cached for the life of the client. At least one
8
+ * is required.
9
+ */
2
10
  interface QuickHelpConfig {
3
- /** Your team's UUID. Required. */
4
- teamId: string;
11
+ /** Your team's UUID. Either this or `teamSlug` is required. */
12
+ teamId?: string;
13
+ /**
14
+ * Your team's slug (e.g. `acme`). Resolved to `teamId` lazily via the
15
+ * public `/api/widget/team-by-slug` endpoint on the first call. Cached
16
+ * thereafter. Either this or `teamId` is required.
17
+ */
18
+ teamSlug?: string;
19
+ /**
20
+ * Optional publishable key (`pk_test_*` or `pk_live_*`). Sent as
21
+ * `Authorization: Bearer` so test-mode dev sessions can be scoped
22
+ * separately from live customer sessions in your analytics. Backwards-
23
+ * compatible: omit and the client behaves exactly as before.
24
+ */
25
+ publishableKey?: string;
5
26
  /** Override the base URL (defaults to https://www.cstar.help). */
6
27
  baseUrl?: string;
7
28
  }
@@ -60,8 +81,20 @@ interface QuickHelpAskResult {
60
81
  */
61
82
  declare class QuickHelpClient {
62
83
  private readonly baseUrl;
63
- private readonly teamId;
84
+ /** Cached resolved teamId. `null` until the first lookup completes. */
85
+ private teamId;
86
+ private readonly teamSlug;
87
+ private resolveTeamIdPromise;
88
+ private readonly publishableKey;
89
+ /** Resolved mode based on the publishable-key prefix. */
90
+ readonly mode: 'live' | 'test';
64
91
  constructor(config: QuickHelpConfig);
92
+ private authHeaders;
93
+ /**
94
+ * Resolve `teamSlug` → `teamId` lazily on the first call that needs it.
95
+ * Cached for the life of the client. NEW-v2-05.
96
+ */
97
+ private resolveTeamId;
65
98
  /**
66
99
  * Fetch the sanitized public config (title, subtitle, placeholder,
67
100
  * theme overrides). Never includes agent id or escalation contact