okrapdf 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.
package/README.md CHANGED
@@ -28,6 +28,17 @@ https://api.okrapdf.com/v1/documents/doc-441a8a0be0e94914b982
28
28
 
29
29
  This is a full OpenAI-compatible base URL. Plug it into any client.
30
30
 
31
+ ## Choose Your Entry Point
32
+
33
+ | I want to... | Start here |
34
+ |---|---|
35
+ | **Use TypeScript/JS** | `okra.sessions.create()` + `session.prompt()` — [SDK Methods](#sdk-methods) |
36
+ | **Use the CLI** | `okra auth login` + `okra upload` — [CLI](#cli) |
37
+ | **Use Claude Code or AI agents** | MCP server — [Setup guide](https://docs.okrapdf.com/integrations/mcp-server) |
38
+ | **Use any LLM client** | OpenAI-compatible endpoint — [OpenAI SDK](#use-with-openai-sdk) |
39
+ | **Build a React app** | `okrapdf/react` hooks — [Sub-path exports](#sub-path-exports) |
40
+ | **Query across documents** | Collections fan-out — [Collections](#collections--fan-out-query-to-csv) |
41
+
31
42
  ## What You Get
32
43
 
33
44
  Upload a PDF and OkraPDF gives you predictable URLs for everything:
@@ -228,7 +239,7 @@ doc_id,file_name,answer,cost_usd
228
239
  "doc-c4d562...","MSFT-10K-2025.pdf","Revenue: $254.2B, Net Income: $97.1B, YoY Growth: 16%",0.0051
229
240
  ```
230
241
 
231
- Works with structured output too pass a Zod schema and each doc extracts typed data:
242
+ Experimental: structured collection fan-out is still available via a schema, but it is not part of the stable v0.14 surface:
232
243
 
233
244
  ```ts
234
245
  const FinancialReport = z.object({
@@ -273,11 +284,25 @@ for await (const event of stream) {
273
284
  ## CLI
274
285
 
275
286
  ```bash
276
- npx okrapdf upload ./invoice.pdf
277
- npx okrapdf pages doc-abc123
278
- npx okrapdf chat doc-abc123 "What is the total?"
287
+ npm install -g okrapdf
288
+ okra auth login
289
+
290
+ okra upload ./invoice.pdf
291
+ okra chat "What is the total?" --doc doc-abc123
292
+ okra extract ./invoice.pdf --schema ./invoice.schema.json
293
+ okra collection query earnings "What changed quarter over quarter?" -o earnings.csv
279
294
  ```
280
295
 
296
+ Use `npx okrapdf ...` for one-off runs if you do not want a global install.
297
+
298
+ The default CLI help intentionally focuses on the promoted workflows: auth,
299
+ upload, extract, chat, document read/list/delete, and collection query.
300
+ Advanced inspection and local-only commands still exist, but they are
301
+ intentionally hidden from default help during the clean-house release candidate.
302
+ Experimental structured collection extraction still exists via
303
+ `okra collection extract ...` and `okra collection query ... --schema ...`,
304
+ but it is intentionally not part of the stable v0.14 surface.
305
+
281
306
  ## License
282
307
 
283
308
  MIT
package/dist/browser.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { doc } from './url.js';
2
- import './types-DDm2eEL0.js';
2
+ import './types-SYOi8k1l.js';
3
3
  import 'zod';
4
4
 
5
5
  declare const _default: {
package/dist/browser.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  doc
3
- } from "./chunk-QKII53VN.js";
3
+ } from "./chunk-HPTXRSWK.js";
4
4
 
5
5
  // src/browser.ts
6
6
  var runtimeGlobal = globalThis;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  OkraClient
3
- } from "./chunk-2HJPTW6S.js";
3
+ } from "./chunk-YGIBZV5J.js";
4
4
 
5
5
  // src/workflows.ts
6
6
  var DEFAULT_WORKFLOW_CAPABILITIES = {
@@ -152,4 +152,4 @@ export {
152
152
  withQualityScore,
153
153
  withSecret
154
154
  };
155
- //# sourceMappingURL=chunk-YVUL6ZLA.js.map
155
+ //# sourceMappingURL=chunk-7BAEYVWG.js.map
@@ -1,3 +1,12 @@
1
+ import {
2
+ doctorLocalHarness,
3
+ findLocalTables,
4
+ getLocalDocumentStatus,
5
+ ingestLocalDocument,
6
+ readLocalPage,
7
+ searchLocalDocument,
8
+ summarizeLocalDocument
9
+ } from "./chunk-MSZQPLMQ.js";
1
10
  import {
2
11
  OkraRuntimeError
3
12
  } from "./chunk-NIZM2ETT.js";
@@ -718,6 +727,17 @@ function getApiKeySource() {
718
727
 
719
728
  // src/cli/commands/auth.ts
720
729
  import * as readline from "readline";
730
+ var API_KEY_URL = "https://app.okrapdf.com/settings";
731
+ var DEFAULT_BASE_URL = "https://api.okrapdf.com";
732
+ function writeStdout(message) {
733
+ process.stdout.write(message + "\n");
734
+ }
735
+ function writeStderr(message) {
736
+ process.stderr.write(message + "\n");
737
+ }
738
+ function normalizeBaseUrl(baseUrl) {
739
+ return baseUrl.replace(/\/+$/, "");
740
+ }
721
741
  function isLikelyApiKey(value) {
722
742
  return value.startsWith("okra_");
723
743
  }
@@ -730,6 +750,67 @@ function persistApiKey(apiKey) {
730
750
  config.apiKey = apiKey;
731
751
  writeGlobalConfig(config);
732
752
  }
753
+ function extractErrorMessage(payload) {
754
+ if (typeof payload === "string" && payload.trim() !== "") return payload;
755
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return void 0;
756
+ const record = payload;
757
+ if (typeof record.error === "string" && record.error.trim() !== "") return record.error;
758
+ if (typeof record.message === "string" && record.message.trim() !== "") return record.message;
759
+ return void 0;
760
+ }
761
+ async function parseJsonSafely(response) {
762
+ const text = await response.text();
763
+ if (!text.trim()) return null;
764
+ try {
765
+ return JSON.parse(text);
766
+ } catch {
767
+ return text;
768
+ }
769
+ }
770
+ async function verifyApiKey(apiKey) {
771
+ const baseUrl = normalizeBaseUrl(getBaseUrl() || DEFAULT_BASE_URL);
772
+ const verifyUrl = `${baseUrl}/auth/verify`;
773
+ let response;
774
+ try {
775
+ response = await fetch(verifyUrl, {
776
+ method: "GET",
777
+ headers: { Authorization: `Bearer ${apiKey}` }
778
+ });
779
+ } catch (error) {
780
+ throw new OkraRuntimeError(
781
+ "HTTP_ERROR",
782
+ `Could not reach ${verifyUrl} to verify the API key`,
783
+ 503,
784
+ error
785
+ );
786
+ }
787
+ const payload = await parseJsonSafely(response);
788
+ if (response.status === 401) {
789
+ throw new OkraRuntimeError(
790
+ "UNAUTHORIZED",
791
+ extractErrorMessage(payload) || "Invalid API key",
792
+ 401,
793
+ payload
794
+ );
795
+ }
796
+ if (!response.ok) {
797
+ throw new OkraRuntimeError(
798
+ "HTTP_ERROR",
799
+ extractErrorMessage(payload) || `API key verification failed (${response.status})`,
800
+ response.status || 500,
801
+ payload
802
+ );
803
+ }
804
+ if (!payload || typeof payload !== "object" || payload.authenticated !== true) {
805
+ throw new OkraRuntimeError(
806
+ "INVALID_RESPONSE",
807
+ "API key verification returned an unexpected response",
808
+ 502,
809
+ payload
810
+ );
811
+ }
812
+ return payload;
813
+ }
733
814
  function prompt(question) {
734
815
  const rl = readline.createInterface({
735
816
  input: process.stdin,
@@ -742,66 +823,146 @@ function prompt(question) {
742
823
  });
743
824
  });
744
825
  }
826
+ function promptSecret(question) {
827
+ if (!process.stdin.isTTY || !process.stdout.isTTY || typeof process.stdin.setRawMode !== "function") {
828
+ return prompt(question);
829
+ }
830
+ return new Promise((resolve, reject) => {
831
+ const stdin = process.stdin;
832
+ const stdout = process.stdout;
833
+ const chars = [];
834
+ const wasRaw = stdin.isRaw === true;
835
+ const cleanup = () => {
836
+ stdin.removeListener("data", onData);
837
+ stdin.setRawMode?.(wasRaw);
838
+ stdin.pause();
839
+ };
840
+ const finish = () => {
841
+ cleanup();
842
+ stdout.write("\n");
843
+ resolve(chars.join("").trim());
844
+ };
845
+ const cancel = () => {
846
+ cleanup();
847
+ stdout.write("\n");
848
+ reject(new OkraRuntimeError("INVALID_REQUEST", "Authentication cancelled", 400));
849
+ };
850
+ const onData = (chunk) => {
851
+ const input = typeof chunk === "string" ? chunk : chunk.toString("utf8");
852
+ for (const char of input) {
853
+ if (char === "") {
854
+ cancel();
855
+ return;
856
+ }
857
+ if (char === "\r" || char === "\n") {
858
+ finish();
859
+ return;
860
+ }
861
+ if (char === "\x7F" || char === "\b") {
862
+ if (chars.length > 0) chars.pop();
863
+ continue;
864
+ }
865
+ chars.push(char);
866
+ }
867
+ };
868
+ stdout.write(question);
869
+ stdin.setEncoding("utf8");
870
+ stdin.setRawMode(true);
871
+ stdin.resume();
872
+ stdin.on("data", onData);
873
+ });
874
+ }
745
875
  async function authLogin(providedApiKey) {
746
876
  if (providedApiKey) {
747
877
  await authSetKey(providedApiKey);
748
878
  return;
749
879
  }
750
- console.log("okra CLI Authentication");
751
- console.log("");
752
- console.log("Get your API key from: https://app.okrapdf.com/settings");
753
- console.log("");
754
- const apiKey = await prompt("Enter your API key: ");
880
+ writeStdout("Okra CLI authentication");
881
+ writeStdout("");
882
+ writeStdout("Get your API key at:");
883
+ writeStdout(` ${API_KEY_URL}`);
884
+ writeStdout("");
885
+ const apiKey = await promptSecret("Enter your API key: ");
755
886
  if (!apiKey) {
756
- console.error("Error: API key cannot be empty");
887
+ writeStderr("Error: API key cannot be empty");
757
888
  process.exit(1);
758
889
  }
759
890
  if (!isLikelyApiKey(apiKey)) {
760
- console.warn('Warning: API key should start with "okra_"');
891
+ writeStderr('Warning: API key should start with "okra_"');
761
892
  }
893
+ const verified = await verifyApiKey(apiKey);
762
894
  persistApiKey(apiKey);
763
- console.log("");
764
- console.log(`\u2713 API key saved to ${getGlobalConfigPath()}`);
765
- console.log("");
766
- console.log("You can now use okra commands without setting OKRA_API_KEY");
895
+ writeStdout("");
896
+ writeStdout(`Verified API key for ${verified.user_id}${verified.key_name ? ` (${verified.key_name})` : ""}`);
897
+ writeStdout(`Saved API key to ${getGlobalConfigPath()}`);
898
+ writeStdout("");
899
+ writeStdout("Next:");
900
+ writeStdout(" okra upload ./report.pdf");
767
901
  }
768
902
  async function authSetKey(apiKey) {
769
903
  const trimmed = apiKey.trim();
770
904
  if (!trimmed) {
771
- console.error("Error: API key cannot be empty");
905
+ writeStderr("Error: API key cannot be empty");
772
906
  process.exit(1);
773
907
  }
774
908
  if (!isLikelyApiKey(trimmed)) {
775
- console.warn('Warning: API key should start with "okra_"');
909
+ writeStderr('Warning: API key should start with "okra_"');
776
910
  }
911
+ const verified = await verifyApiKey(trimmed);
777
912
  persistApiKey(trimmed);
778
- console.log(`\u2713 API key saved to ${getGlobalConfigPath()}`);
913
+ writeStdout(`Verified API key for ${verified.user_id}${verified.key_name ? ` (${verified.key_name})` : ""}`);
914
+ writeStdout(`Saved API key to ${getGlobalConfigPath()}`);
779
915
  }
780
916
  async function authStatus() {
781
917
  const apiKey = getApiKey();
782
918
  const source = getApiKeySource();
783
- console.log("okra CLI Authentication Status");
784
- console.log("");
919
+ writeStdout("Okra CLI authentication status");
920
+ writeStdout("");
785
921
  if (apiKey) {
786
- const maskedKey = maskApiKey(apiKey);
787
- console.log(`\u2713 Authenticated: ${maskedKey}`);
788
- console.log(` Source: ${source}`);
922
+ try {
923
+ const verified = await verifyApiKey(apiKey);
924
+ const maskedKey = maskApiKey(apiKey);
925
+ writeStdout(`Authenticated: ${maskedKey}`);
926
+ writeStdout(`Source: ${source}`);
927
+ writeStdout(`User: ${verified.user_id}`);
928
+ if (verified.key_name || verified.key_type) {
929
+ const name = verified.key_name || "API key";
930
+ const type = verified.key_type ? ` (${verified.key_type})` : "";
931
+ writeStdout(`Key: ${name}${type}`);
932
+ }
933
+ if (verified.scope) {
934
+ writeStdout(`Scope: ${verified.scope}`);
935
+ }
936
+ } catch (error) {
937
+ if (error instanceof OkraRuntimeError && error.status === 401) {
938
+ throw new OkraRuntimeError(
939
+ "UNAUTHORIZED",
940
+ `Configured API key is invalid (${maskApiKey(apiKey)} from ${source}). Replace it with \`okra auth login\` or update OKRA_API_KEY.`,
941
+ 401,
942
+ error
943
+ );
944
+ }
945
+ throw error;
946
+ }
789
947
  } else {
790
- console.log("\u2717 Not authenticated");
791
- console.log("");
792
- console.log("Set API key via:");
793
- console.log(" okra auth login");
794
- console.log(' export OKRA_API_KEY="okra_xxx"');
795
- }
796
- console.log("");
948
+ writeStdout("Not authenticated");
949
+ writeStdout("");
950
+ writeStdout("Set an API key with:");
951
+ writeStdout(" okra auth login");
952
+ writeStdout(' export OKRA_API_KEY="okra_xxx"');
953
+ writeStdout("");
954
+ writeStdout("Get a key at:");
955
+ writeStdout(` ${API_KEY_URL}`);
956
+ }
957
+ writeStdout("");
797
958
  }
798
959
  async function authToken() {
799
960
  const apiKey = getApiKey();
800
961
  if (!apiKey) {
801
- console.error("Error: No API key configured");
962
+ writeStderr("Error: No API key configured");
802
963
  process.exit(1);
803
964
  }
804
- console.log(apiKey);
965
+ writeStdout(apiKey);
805
966
  }
806
967
  async function authWhoAmI() {
807
968
  await authStatus();
@@ -809,14 +970,14 @@ async function authWhoAmI() {
809
970
  async function authLogout() {
810
971
  const config = readGlobalConfig();
811
972
  if (!config || !config.apiKey) {
812
- console.log("No API key found in global config");
973
+ writeStdout("No API key found in global config");
813
974
  return;
814
975
  }
815
976
  delete config.apiKey;
816
977
  writeGlobalConfig(config);
817
- console.log(`\u2713 API key removed from ${getGlobalConfigPath()}`);
818
- console.log("");
819
- console.log("Note: Environment variables and project configs are not affected");
978
+ writeStdout(`Removed API key from ${getGlobalConfigPath()}`);
979
+ writeStdout("");
980
+ writeStdout("Environment variables and project configs are unchanged.");
820
981
  }
821
982
 
822
983
  // src/cli/output.ts
@@ -855,7 +1016,9 @@ function handleError(error, json) {
855
1016
  // src/cli/commands/upload.ts
856
1017
  async function upload(client, source, opts) {
857
1018
  progress(`Uploading ${source}\u2026`, opts.quiet);
858
- const session = await client.upload(source);
1019
+ const session = await client.upload(source, {
1020
+ ...opts.vendorOptions ? { vendorOptions: opts.vendorOptions } : {}
1021
+ });
859
1022
  const docId = session.id;
860
1023
  progress(`Document ID: ${docId}`, opts.quiet);
861
1024
  if (opts.noWait) {
@@ -870,7 +1033,7 @@ async function upload(client, source, opts) {
870
1033
  full_md: `${base}/full.md`,
871
1034
  page_png: `${base}/d_shimmer/pg_{N}.png`,
872
1035
  page_md: `${base}/pg_{N}.md`,
873
- completion: `https://api.okrapdf.com/document/${docId}/completion`,
1036
+ completion: `https://api.okrapdf.com/v1/documents/${docId}/completion`,
874
1037
  original: `${base}/original.pdf`
875
1038
  };
876
1039
  return {
@@ -881,6 +1044,44 @@ async function upload(client, source, opts) {
881
1044
  };
882
1045
  }
883
1046
 
1047
+ // src/cli/commands/list.ts
1048
+ async function listDocuments(client, _opts) {
1049
+ const documents = await client.listDocuments();
1050
+ return { documents };
1051
+ }
1052
+ function formatDocumentList(documents, json) {
1053
+ if (json) {
1054
+ return JSON.stringify(documents);
1055
+ }
1056
+ if (documents.length === 0) {
1057
+ return "No documents found.";
1058
+ }
1059
+ const lines = [];
1060
+ const header = ["ID", "File", "Phase", "Pages", "Created"];
1061
+ lines.push(header.join(" "));
1062
+ lines.push(header.map((h) => "-".repeat(h.length)).join(" "));
1063
+ for (const doc of documents) {
1064
+ lines.push([
1065
+ doc.id,
1066
+ doc.file_name ?? "\u2014",
1067
+ doc.phase,
1068
+ doc.pages_total != null ? String(doc.pages_total) : "\u2014",
1069
+ doc.created_at ? new Date(doc.created_at).toLocaleDateString() : "\u2014"
1070
+ ].join(" "));
1071
+ }
1072
+ return lines.join("\n");
1073
+ }
1074
+
1075
+ // src/cli/commands/delete.ts
1076
+ async function deleteDocument(client, documentId, _opts) {
1077
+ return client.deleteDocument(documentId);
1078
+ }
1079
+
1080
+ // src/cli/commands/read.ts
1081
+ async function readDocument(client, documentId, opts) {
1082
+ return client.read(documentId, { pages: opts.pages });
1083
+ }
1084
+
884
1085
  // src/cli/commands/collection.ts
885
1086
  async function collectionList(client, _opts) {
886
1087
  return client.collections.list();
@@ -894,6 +1095,65 @@ function formatCollectionList(rows, json) {
894
1095
  );
895
1096
  return [header, ...lines].join("\n");
896
1097
  }
1098
+ async function collectionCreate(client, name, opts) {
1099
+ const body = { name };
1100
+ if (opts.description) body.description = opts.description;
1101
+ if (opts.docs) body.document_ids = opts.docs.split(",").map((s) => s.trim());
1102
+ return client.request("/v1/collections", {
1103
+ method: "POST",
1104
+ headers: { "Content-Type": "application/json" },
1105
+ body: JSON.stringify(body)
1106
+ });
1107
+ }
1108
+ async function collectionShow(client, nameOrId) {
1109
+ return client.request(
1110
+ `/v1/collections/${encodeURIComponent(nameOrId)}`
1111
+ );
1112
+ }
1113
+ function formatCollectionDetail(detail, json) {
1114
+ if (json) return JSON.stringify(detail);
1115
+ const lines = [
1116
+ `ID: ${detail.id}`,
1117
+ `Name: ${detail.name}`,
1118
+ `Description: ${detail.description || "(none)"}`,
1119
+ `Visibility: ${detail.visibility || "private"}`,
1120
+ `Documents: ${detail.document_count}`
1121
+ ];
1122
+ if (detail.documents?.length) {
1123
+ lines.push("");
1124
+ lines.push("DOC_ID FILE STATUS");
1125
+ for (const d of detail.documents) {
1126
+ lines.push(`${d.id} ${d.file_name || ""} ${d.status || ""}`);
1127
+ }
1128
+ }
1129
+ return lines.join("\n");
1130
+ }
1131
+ async function collectionDelete(client, nameOrId) {
1132
+ return client.request(
1133
+ `/v1/collections/${encodeURIComponent(nameOrId)}`,
1134
+ { method: "DELETE" }
1135
+ );
1136
+ }
1137
+ async function collectionAddDocs(client, nameOrId, documentIds) {
1138
+ return client.request(
1139
+ `/v1/collections/${encodeURIComponent(nameOrId)}/documents`,
1140
+ {
1141
+ method: "POST",
1142
+ headers: { "Content-Type": "application/json" },
1143
+ body: JSON.stringify({ document_ids: documentIds })
1144
+ }
1145
+ );
1146
+ }
1147
+ async function collectionRemoveDocs(client, nameOrId, documentIds) {
1148
+ return client.request(
1149
+ `/v1/collections/${encodeURIComponent(nameOrId)}/documents`,
1150
+ {
1151
+ method: "DELETE",
1152
+ headers: { "Content-Type": "application/json" },
1153
+ body: JSON.stringify({ document_ids: documentIds })
1154
+ }
1155
+ );
1156
+ }
897
1157
  async function collectionSetVisibility(client, nameOrId, visibility) {
898
1158
  return client.request(
899
1159
  `/v1/collections/${encodeURIComponent(nameOrId)}`,
@@ -908,8 +1168,13 @@ async function collectionQueryRaw(client, nameOrId, question, opts) {
908
1168
  progress(`Querying collection "${nameOrId}"\u2026`, opts.quiet);
909
1169
  let schema;
910
1170
  if (opts.schema) {
911
- const { readFileSync: readFileSync2 } = await import("fs");
912
- schema = JSON.parse(readFileSync2(opts.schema, "utf8"));
1171
+ if (typeof opts.schema === "object") {
1172
+ schema = opts.schema;
1173
+ } else {
1174
+ const { readFileSync: readFileSync2 } = await import("fs");
1175
+ const filePath = opts.schema;
1176
+ schema = JSON.parse(readFileSync2(filePath, "utf8"));
1177
+ }
913
1178
  }
914
1179
  const stream = client.collections.query(nameOrId, question, {
915
1180
  ...schema ? { schema } : {}
@@ -925,7 +1190,8 @@ async function collectionQueryRaw(client, nameOrId, question, opts) {
925
1190
  answer: event.answer ?? "",
926
1191
  cost_usd: event.usage?.cost_usd ?? 0,
927
1192
  duration_ms: event.duration_ms ?? 0,
928
- error: event.error
1193
+ error: event.error,
1194
+ data: event.data
929
1195
  });
930
1196
  progress(
931
1197
  ` ${event.status === "fulfilled" ? "+" : "!"} ${event.doc_id} (${event.duration_ms}ms)`,
@@ -973,6 +1239,63 @@ function formatCollectionTable(results) {
973
1239
  function formatQueryJsonl(results) {
974
1240
  return results.map((r) => JSON.stringify(r)).join("\n");
975
1241
  }
1242
+ function collectDataKeys(results) {
1243
+ const seen = /* @__PURE__ */ new Set();
1244
+ const keys = [];
1245
+ for (const r of results) {
1246
+ if (!r.data) continue;
1247
+ for (const k of Object.keys(r.data)) {
1248
+ if (!seen.has(k)) {
1249
+ seen.add(k);
1250
+ keys.push(k);
1251
+ }
1252
+ }
1253
+ }
1254
+ return keys;
1255
+ }
1256
+ function formatExtractCsv(results) {
1257
+ const dataKeys = collectDataKeys(results);
1258
+ const header = ["doc_id", ...dataKeys, "cost_usd", "duration_ms"].join(",");
1259
+ const rows = results.filter((r) => r.status === "fulfilled").map((r) => {
1260
+ const dataCols = dataKeys.map((k) => {
1261
+ const v = r.data?.[k];
1262
+ if (v == null) return "";
1263
+ if (typeof v === "string") return csvEscape(v);
1264
+ return String(v);
1265
+ });
1266
+ return [r.doc_id, ...dataCols, r.cost_usd, r.duration_ms].join(",");
1267
+ });
1268
+ return [header, ...rows].join("\n");
1269
+ }
1270
+ function formatExtractTable(results) {
1271
+ const fulfilled = results.filter((r) => r.status === "fulfilled" && r.data);
1272
+ if (fulfilled.length === 0) return "No results with structured data.";
1273
+ const dataKeys = collectDataKeys(fulfilled);
1274
+ const colHeaders = dataKeys.map((k) => k.toUpperCase());
1275
+ const colWidths = colHeaders.map((h, i) => {
1276
+ const key = dataKeys[i];
1277
+ const maxData = fulfilled.reduce((max, r) => {
1278
+ const v = r.data?.[key];
1279
+ const len = v == null ? 0 : String(v).length;
1280
+ return Math.max(max, len);
1281
+ }, 0);
1282
+ return Math.max(h.length, Math.min(maxData, 40));
1283
+ });
1284
+ const pad = (s, w) => s.length > w ? s.slice(0, w - 1) + "\u2026" : s.padEnd(w);
1285
+ const headerLine = colHeaders.map((h, i) => pad(h, colWidths[i])).join(" ");
1286
+ const rows = fulfilled.map(
1287
+ (r) => dataKeys.map((k, i) => {
1288
+ const v = r.data?.[k];
1289
+ const s = v == null ? "" : String(v);
1290
+ return pad(s, colWidths[i]);
1291
+ }).join(" ")
1292
+ );
1293
+ return [headerLine, ...rows].join("\n");
1294
+ }
1295
+ function formatExtractJson(results) {
1296
+ const items = results.filter((r) => r.status === "fulfilled").map((r) => ({ doc_id: r.doc_id, data: r.data ?? {} }));
1297
+ return JSON.stringify(items, null, 2);
1298
+ }
976
1299
  async function collectionExport(client, nameOrId, opts) {
977
1300
  if (opts.zip) {
978
1301
  progress(`Exporting markdown zip from "${nameOrId}"\u2026`, opts.quiet);
@@ -1003,6 +1326,29 @@ function formatCollectionExportFlat(result) {
1003
1326
  return parts.join("\n");
1004
1327
  }
1005
1328
 
1329
+ // src/cli/commands/local.ts
1330
+ function localIngest(source, options = {}) {
1331
+ return ingestLocalDocument(source, options.dataDir);
1332
+ }
1333
+ function localStatus(documentId, options = {}) {
1334
+ return getLocalDocumentStatus(documentId, options.dataDir);
1335
+ }
1336
+ function localSummary(documentId, options = {}) {
1337
+ return summarizeLocalDocument(documentId, options.dataDir);
1338
+ }
1339
+ function localSearch(documentId, query, options = {}) {
1340
+ return searchLocalDocument(documentId, query, options.dataDir);
1341
+ }
1342
+ function localPage(documentId, pageNumber, options = {}) {
1343
+ return readLocalPage(documentId, pageNumber, options.dataDir);
1344
+ }
1345
+ function localTables(documentId, query, options = {}) {
1346
+ return findLocalTables(documentId, query, options.dataDir);
1347
+ }
1348
+ function localDoctor(options = {}) {
1349
+ return doctorLocalHarness(options.dataDir);
1350
+ }
1351
+
1006
1352
  export {
1007
1353
  tree,
1008
1354
  formatTreeOutput,
@@ -1045,14 +1391,34 @@ export {
1045
1391
  progress,
1046
1392
  handleError,
1047
1393
  upload,
1394
+ listDocuments,
1395
+ formatDocumentList,
1396
+ deleteDocument,
1397
+ readDocument,
1048
1398
  collectionList,
1049
1399
  formatCollectionList,
1400
+ collectionCreate,
1401
+ collectionShow,
1402
+ formatCollectionDetail,
1403
+ collectionDelete,
1404
+ collectionAddDocs,
1405
+ collectionRemoveDocs,
1050
1406
  collectionSetVisibility,
1051
1407
  collectionQueryRaw,
1052
1408
  formatCollectionCsv,
1053
1409
  formatCollectionTable,
1054
1410
  formatQueryJsonl,
1411
+ formatExtractCsv,
1412
+ formatExtractTable,
1413
+ formatExtractJson,
1055
1414
  collectionExport,
1056
- formatCollectionExportFlat
1415
+ formatCollectionExportFlat,
1416
+ localIngest,
1417
+ localStatus,
1418
+ localSummary,
1419
+ localSearch,
1420
+ localPage,
1421
+ localTables,
1422
+ localDoctor
1057
1423
  };
1058
- //# sourceMappingURL=chunk-XOHPZW3V.js.map
1424
+ //# sourceMappingURL=chunk-ETARIBOV.js.map