@koda-sl/baker-cli 0.254.0 → 0.254.1

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 CHANGED
@@ -2735,6 +2735,15 @@ When `list` or `search` returns nothing, the envelope carries `meta.empty` expla
2735
2735
 
2736
2736
  If the source check itself fails, `meta.empty` is omitted entirely and the hint says so — an unverified absence is never reported as one.
2737
2737
 
2738
+ `list` is paged. `--limit` is the page size (default 50); when the library continues past the page you got, the envelope carries `meta.next_cursor` and a hint, and `meta.empty` is omitted — a page that is not the last page establishes nothing about the corpus. Read on with the same filters plus `--cursor`:
2739
+
2740
+ ```bash
2741
+ baker testimonials list --sentiment positive --limit 50
2742
+ baker testimonials list --sentiment positive --limit 50 --cursor <meta.next_cursor>
2743
+ ```
2744
+
2745
+ No `meta.next_cursor` means the library ended there.
2746
+
2738
2747
  ### `baker testimonials tags`
2739
2748
 
2740
2749
  List the available testimonial tag names — built-in defaults plus the company's custom tags. Use it before filtering with `--tags`. Defaults to a markdown list (`--output json` for the `{ ok, data }` envelope).
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ApiError,
3
3
  apiGet
4
- } from "./chunk-JKSMLAQN.js";
4
+ } from "./chunk-GU7IWEBC.js";
5
5
  import {
6
6
  getEnv
7
7
  } from "./chunk-DZUVUGEP.js";
@@ -295,4 +295,4 @@ export {
295
295
  writeAdsOutput,
296
296
  resolveCustomerId
297
297
  };
298
- //# sourceMappingURL=chunk-5JHSX3ZB.js.map
298
+ //# sourceMappingURL=chunk-6F52WYB7.js.map
@@ -110,7 +110,7 @@ async function handleResponse(response) {
110
110
  throw new ApiError("INTERNAL_ERROR", "Failed to parse API response as JSON");
111
111
  }
112
112
  }
113
- async function apiGet(path, params) {
113
+ async function apiGetWithHeaders(path, params) {
114
114
  const env = getEnv();
115
115
  const url = new URL(path, env.BAKER_API_URL);
116
116
  if (params) {
@@ -148,7 +148,10 @@ async function apiGet(path, params) {
148
148
  responseBody: await readBodyForLog(response),
149
149
  durationMs: Date.now() - startedAt
150
150
  });
151
- return handleResponse(response);
151
+ return { data: await handleResponse(response), headers: response.headers };
152
+ }
153
+ async function apiGet(path, params) {
154
+ return (await apiGetWithHeaders(path, params)).data;
152
155
  }
153
156
  async function apiPost(path, body, opts) {
154
157
  const env = getEnv();
@@ -198,7 +201,8 @@ async function apiPost(path, body, opts) {
198
201
  export {
199
202
  ApiError,
200
203
  validateConvexId,
204
+ apiGetWithHeaders,
201
205
  apiGet,
202
206
  apiPost
203
207
  };
204
- //# sourceMappingURL=chunk-JKSMLAQN.js.map
208
+ //# sourceMappingURL=chunk-GU7IWEBC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import { debugLogHttp, readBodyForLog } from \"./debugLog.ts\";\nimport { getEnv } from \"./env.ts\";\n\nconst MAX_RATE_LIMIT_RETRIES = 3;\nconst MAX_TOTAL_WAIT_MS = 2 * 60 * 1000;\n\nasync function fetchWithRateLimitRetry(url: string, init: RequestInit): Promise<Response> {\n let totalWaited = 0;\n\n for (let attempt = 0; attempt <= MAX_RATE_LIMIT_RETRIES; attempt++) {\n const response = await fetch(url, init);\n\n if (response.status !== 429 || attempt >= MAX_RATE_LIMIT_RETRIES) {\n return response;\n }\n\n const retryAfterHeader = response.headers.get(\"Retry-After\");\n const waitMs = retryAfterHeader ? Number(retryAfterHeader) * 1000 : 2000 * 2 ** attempt;\n\n if (totalWaited + waitMs > MAX_TOTAL_WAIT_MS) {\n return response;\n }\n\n totalWaited += waitMs;\n await new Promise((resolve) => setTimeout(resolve, waitMs));\n }\n\n return fetch(url, init);\n}\n\ntype ErrorCode =\n | \"UNAUTHORIZED\"\n | \"FORBIDDEN\"\n | \"NOT_FOUND\"\n | \"CONFLICT\"\n | \"VALIDATION_ERROR\"\n | \"RATE_LIMITED\"\n | \"INTERNAL_ERROR\"\n | \"NETWORK_ERROR\"\n | \"TIMEOUT\"\n | \"IMAGE_PROCESSING_ERROR\";\n\nexport class ApiError extends Error {\n code: ErrorCode;\n\n constructor(code: ErrorCode, message: string) {\n super(message);\n this.name = \"ApiError\";\n this.code = code;\n }\n}\n\nconst CONVEX_ID_RE = /^[a-zA-Z0-9_]+$/;\n\nfunction hasControlCharacters(value: string): boolean {\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n // Allow tab (9), newline (10), carriage return (13)\n if (code < 32 && code !== 9 && code !== 10 && code !== 13) {\n return true;\n }\n }\n return false;\n}\n\nfunction validateStringValue(value: string): void {\n if (hasControlCharacters(value)) {\n throw new ApiError(\"VALIDATION_ERROR\", \"String value contains invalid control characters\");\n }\n}\n\nexport function validateConvexId(id: string): void {\n if (!CONVEX_ID_RE.test(id)) {\n throw new ApiError(\"VALIDATION_ERROR\", `Invalid ID format: \"${id}\". Expected alphanumeric string.`);\n }\n}\n\nfunction sanitizeParams(params: Record<string, string>): Record<string, string> {\n const sanitized: Record<string, string> = {};\n for (const [key, value] of Object.entries(params)) {\n validateStringValue(value);\n sanitized[key] = value;\n }\n return sanitized;\n}\n\n/**\n * Sent on every call so the backend can attribute immediately-applied writes\n * (library images and videos, followed advertisers) to the Session they were\n * made from. Absent outside a chat-attached environment.\n */\nfunction chatHeader(): Record<string, string> {\n const chatId = getEnv().BAKER_CHAT_ID;\n return chatId ? { \"x-baker-chat-id\": chatId } : {};\n}\n\nfunction mapHttpError(status: number): ErrorCode {\n if (status === 401) {\n return \"UNAUTHORIZED\";\n }\n // 403 is never \"not connected\" — every backend `FORBIDDEN` means connected but\n // not allowed (an asset outside the granted scope, a capability the account\n // lacks). Folding it into UNAUTHORIZED sent agents down the reconnect path,\n // which cannot fix a permission gap and wastes the user's time.\n if (status === 403) {\n return \"FORBIDDEN\";\n }\n if (status === 404) {\n return \"NOT_FOUND\";\n }\n if (status === 422 || status === 400) {\n return \"VALIDATION_ERROR\";\n }\n // 409 is a state the caller has to change, never a fault to sit out. It is\n // what the backend returns for \"this workspace has not switched analytics on\n // yet\" and its siblings — folded into INTERNAL_ERROR, that reaches an agent\n // as a transient failure worth retrying, which it never is.\n if (status === 409) {\n return \"CONFLICT\";\n }\n if (status === 429) {\n return \"RATE_LIMITED\";\n }\n return \"INTERNAL_ERROR\";\n}\n\nasync function handleResponse<T>(response: Response): Promise<T> {\n const body = await response.text();\n\n if (!response.ok) {\n let message = `HTTP ${response.status}: ${response.statusText}`;\n try {\n const parsed = JSON.parse(body) as { error?: string | { message?: string }; message?: string };\n if (typeof parsed.error === \"string\") {\n message = parsed.error;\n } else if (parsed.error?.message) {\n message = parsed.error.message;\n } else if (parsed.message) {\n message = parsed.message;\n }\n } catch {\n // Use default message\n }\n throw new ApiError(mapHttpError(response.status), message);\n }\n\n try {\n return JSON.parse(body) as T;\n } catch {\n throw new ApiError(\"INTERNAL_ERROR\", \"Failed to parse API response as JSON\");\n }\n}\n\n/**\n * A GET that also hands back the response headers.\n *\n * Some endpoints have to say something *about* the answer that the answer has\n * no room for — `GET /api/testimonials` returns a bare array and sets\n * `X-Baker-Next-Cursor` when rows past this page remain unexamined, so the rows\n * alone cannot tell one page of a library from the whole of it.\n */\nexport async function apiGetWithHeaders<T>(\n path: string,\n params?: Record<string, string>,\n): Promise<{ data: T; headers: Headers }> {\n const env = getEnv();\n const url = new URL(path, env.BAKER_API_URL);\n if (params) {\n const clean = sanitizeParams(params);\n for (const [key, value] of Object.entries(clean)) {\n url.searchParams.set(key, value);\n }\n }\n\n const urlStr = url.toString();\n const startedAt = Date.now();\n let response: Response;\n try {\n response = await fetchWithRateLimitRetry(urlStr, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${env.BAKER_API_KEY}`,\n Accept: \"application/json\",\n ...chatHeader(),\n },\n signal: AbortSignal.timeout(60_000),\n });\n } catch (err) {\n debugLogHttp({ source: \"cli\", method: \"GET\", url: urlStr, durationMs: Date.now() - startedAt, error: err });\n if (err instanceof Error && (err.name === \"TimeoutError\" || err.name === \"AbortError\")) {\n throw new ApiError(\"TIMEOUT\", \"Request timed out after 60 seconds\");\n }\n throw new ApiError(\"NETWORK_ERROR\", `Request failed: ${err instanceof Error ? err.message : \"Unknown error\"}`);\n }\n\n debugLogHttp({\n source: \"cli\",\n method: \"GET\",\n url: urlStr,\n status: response.status,\n ok: response.ok,\n responseBody: await readBodyForLog(response),\n durationMs: Date.now() - startedAt,\n });\n return { data: await handleResponse<T>(response), headers: response.headers };\n}\n\nexport async function apiGet<T>(path: string, params?: Record<string, string>): Promise<T> {\n return (await apiGetWithHeaders<T>(path, params)).data;\n}\n\nexport async function apiPost<T>(path: string, body: unknown, opts?: { timeoutMs?: number }): Promise<T> {\n const env = getEnv();\n const timeoutMs = opts?.timeoutMs ?? 60_000;\n const urlStr = new URL(path, env.BAKER_API_URL).toString();\n const startedAt = Date.now();\n let response: Response;\n try {\n response = await fetchWithRateLimitRetry(urlStr, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${env.BAKER_API_KEY}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...chatHeader(),\n },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (err) {\n debugLogHttp({\n source: \"cli\",\n method: \"POST\",\n url: urlStr,\n requestBody: body,\n durationMs: Date.now() - startedAt,\n error: err,\n });\n if (err instanceof Error && (err.name === \"TimeoutError\" || err.name === \"AbortError\")) {\n throw new ApiError(\"TIMEOUT\", `Request timed out after ${Math.round(timeoutMs / 1000)} seconds`);\n }\n throw new ApiError(\"NETWORK_ERROR\", `Request failed: ${err instanceof Error ? err.message : \"Unknown error\"}`);\n }\n\n debugLogHttp({\n source: \"cli\",\n method: \"POST\",\n url: urlStr,\n requestBody: body,\n status: response.status,\n ok: response.ok,\n responseBody: await readBodyForLog(response),\n durationMs: Date.now() - startedAt,\n });\n return handleResponse<T>(response);\n}\n"],"mappings":"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","names":[]}
@@ -2,10 +2,10 @@ import {
2
2
  handleConnectionError,
3
3
  needsConnectionFix,
4
4
  writeAdsJson
5
- } from "./chunk-5JHSX3ZB.js";
5
+ } from "./chunk-6F52WYB7.js";
6
6
  import {
7
7
  ApiError
8
- } from "./chunk-JKSMLAQN.js";
8
+ } from "./chunk-GU7IWEBC.js";
9
9
  import {
10
10
  getEnv
11
11
  } from "./chunk-DZUVUGEP.js";
@@ -108,4 +108,4 @@ export {
108
108
  csvOrJson,
109
109
  resolveEffectiveStatus
110
110
  };
111
- //# sourceMappingURL=chunk-6MAVG6LO.js.map
111
+ //# sourceMappingURL=chunk-KSOGK7SJ.js.map
package/dist/cli.js CHANGED
@@ -67,7 +67,7 @@ import {
67
67
  resolveAccountIdArg,
68
68
  resolveEffectiveStatus,
69
69
  todayIso
70
- } from "./chunk-6MAVG6LO.js";
70
+ } from "./chunk-KSOGK7SJ.js";
71
71
  import {
72
72
  buildQueryCacheKey,
73
73
  cacheGet,
@@ -83,13 +83,14 @@ import {
83
83
  writeAdsJson,
84
84
  writeAdsOutput,
85
85
  writeJsonEnvelope
86
- } from "./chunk-5JHSX3ZB.js";
86
+ } from "./chunk-6F52WYB7.js";
87
87
  import {
88
88
  ApiError,
89
89
  apiGet,
90
+ apiGetWithHeaders,
90
91
  apiPost,
91
92
  validateConvexId
92
- } from "./chunk-JKSMLAQN.js";
93
+ } from "./chunk-GU7IWEBC.js";
93
94
  import {
94
95
  installStreamTaps,
95
96
  logInvocation
@@ -6183,7 +6184,10 @@ var testimonialsListRequestSchema = z20.object({
6183
6184
  status: testimonialStatusSchema.optional(),
6184
6185
  sentiment: testimonialSentimentSchema.optional(),
6185
6186
  language: z20.string().min(2).max(5).optional(),
6186
- limit: z20.coerce.number().int().positive().max(200).optional()
6187
+ limit: z20.coerce.number().int().positive().max(200).optional(),
6188
+ // Opaque, from the previous response's `X-Baker-Next-Cursor`. Absent on the
6189
+ // first page; its absence on a response means the corpus is exhausted.
6190
+ cursor: z20.string().optional()
6187
6191
  });
6188
6192
  var testimonialsListResponseSchema = z20.array(testimonialDocSchema);
6189
6193
  var testimonialsGetRequestSchema = z20.object({ id: z20.string().min(1, "Missing id parameter") });
@@ -21014,10 +21018,10 @@ function duplicateCommand2(entity, label) {
21014
21018
  replace: { type: "boolean", description: "Pause the original once the copy publishes" }
21015
21019
  },
21016
21020
  run: async ({ args }) => {
21017
- const { apiPost: apiPost2 } = await import("./client-3TNO43FO.js");
21021
+ const { apiPost: apiPost2 } = await import("./client-VDCCDEHE.js");
21018
21022
  const { requireChatId: requireChatId2 } = await import("./env-FWMZXMQK.js");
21019
- const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-B3OCLLS5.js");
21020
- const { handleMetaError: handleMetaError2 } = await import("./shared-J24NXBW4.js");
21023
+ const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-JBYI5IVX.js");
21024
+ const { handleMetaError: handleMetaError2 } = await import("./shared-GWFXJXKJ.js");
21021
21025
  try {
21022
21026
  const accountId = bareAccountId2(args);
21023
21027
  const chatId = requireChatId2();
@@ -50105,14 +50109,30 @@ function emptyReasonDetail(reason, sourceCount, activeFilters) {
50105
50109
  }
50106
50110
  return `${sourceCount} review source${sourceCount === 1 ? " is" : "s are"} connected but hold no testimonials yet \u2014 the source is there, the rows are not. Ingestion may still be running.`;
50107
50111
  }
50112
+ function morePagesHint(resultCount, cursor) {
50113
+ const found = resultCount === 0 ? "This page held no matches" : `This page held ${resultCount}`;
50114
+ return `${found}, and the library continues past it \u2014 so this is one page, not the client's whole set. Do not report it as the corpus and do not conclude anything is missing from it. Read the next page with \`baker testimonials list --cursor ${cursor}\` (same filters), or go straight at the rows you want with \`baker testimonials search <query>\`.`;
50115
+ }
50116
+ function endOfWalkHint(command) {
50117
+ const noun = command === "search" ? "results" : "rows";
50118
+ return `That was the end of the library \u2014 no more ${noun} past the page before this one. Nothing here says the corpus is empty (the earlier pages held it), so answer from what those pages returned rather than re-reading.`;
50119
+ }
50108
50120
  async function measureEmptyCorpus({
50109
50121
  command,
50110
50122
  resultCount,
50111
- activeFilters
50123
+ activeFilters,
50124
+ nextCursor = null,
50125
+ continued = false
50112
50126
  }) {
50127
+ if (nextCursor) {
50128
+ return { hints: [morePagesHint(resultCount, nextCursor)] };
50129
+ }
50113
50130
  if (resultCount > 0) {
50114
50131
  return { hints: [] };
50115
50132
  }
50133
+ if (continued) {
50134
+ return { hints: [endOfWalkHint(command)] };
50135
+ }
50116
50136
  if (activeFilters.length > 0) {
50117
50137
  const reason2 = "filtered_out";
50118
50138
  return {
@@ -50191,7 +50211,12 @@ registerSchema({
50191
50211
  },
50192
50212
  language: { type: "string", description: "Filter by language code (e.g. en, es)", required: false },
50193
50213
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false },
50194
- limit: { type: "number", description: "Max results (default 50)", required: false, default: 50 }
50214
+ limit: { type: "number", description: "Max results per page (default 50)", required: false, default: 50 },
50215
+ cursor: {
50216
+ type: "string",
50217
+ description: "Read the next page: pass meta.next_cursor from the previous call, with the same filters.",
50218
+ required: false
50219
+ }
50195
50220
  }
50196
50221
  });
50197
50222
  var PARAM_NAMES = {
@@ -50202,7 +50227,8 @@ var PARAM_NAMES = {
50202
50227
  sentiment: "sentiment",
50203
50228
  language: "language",
50204
50229
  tags: "tags",
50205
- limit: "limit"
50230
+ limit: "limit",
50231
+ cursor: "cursor"
50206
50232
  };
50207
50233
  function buildListParams(args) {
50208
50234
  const params = {};
@@ -50227,22 +50253,30 @@ var listCommand18 = defineCommand205({
50227
50253
  sentiment: { type: "string", description: "Filter: positive|neutral|negative", required: false },
50228
50254
  language: { type: "string", description: "Filter by language code (e.g. en, es)", required: false },
50229
50255
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false },
50230
- limit: { type: "string", description: "Max results (default 50)", required: false },
50256
+ limit: { type: "string", description: "Max results per page (default 50)", required: false },
50257
+ cursor: { type: "string", description: "meta.next_cursor from the previous page", required: false },
50231
50258
  output: { type: "string", description: "Output format: json|files|md", required: false, default: "json" },
50232
50259
  fields: { type: "string", description: "Comma-separated field names to include", required: false },
50233
50260
  full: { type: "boolean", description: "Include full metadata", required: false, default: false }
50234
50261
  },
50235
50262
  run: async ({ args }) => {
50236
50263
  try {
50237
- const data = await apiGet("/api/testimonials", buildListParams(args));
50264
+ const { data, headers } = await apiGetWithHeaders(
50265
+ "/api/testimonials",
50266
+ buildListParams(args)
50267
+ );
50268
+ const nextCursor = headers.get("x-baker-next-cursor");
50238
50269
  const { hints: hints2, empty } = await measureEmptyCorpus({
50239
50270
  command: "list",
50240
50271
  resultCount: data.length,
50241
- activeFilters: activeFilterFlags(args, FILTER_FLAGS)
50272
+ activeFilters: activeFilterFlags(args, FILTER_FLAGS),
50273
+ nextCursor,
50274
+ continued: Boolean(args.cursor)
50242
50275
  });
50243
50276
  const outputFormat = args.output || "json";
50277
+ const meta = { ...empty ? { empty } : {}, ...nextCursor ? { next_cursor: nextCursor } : {} };
50244
50278
  writeOutput(
50245
- { ok: true, data, ...empty ? { meta: { empty } } : {}, ...hints2.length > 0 ? { hints: hints2 } : {} },
50279
+ { ok: true, data, ...Object.keys(meta).length > 0 ? { meta } : {}, ...hints2.length > 0 ? { hints: hints2 } : {} },
50246
50280
  outputFormat,
50247
50281
  args.fields ? args.fields.split(",") : void 0,
50248
50282
  args.full,