okrapdf 0.13.0 → 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-D2JaySEg.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-2VKGPLAA.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-KJMV6TN2.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"');
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}`);
795
956
  }
796
- console.log("");
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 {
@@ -932,6 +1095,65 @@ function formatCollectionList(rows, json) {
932
1095
  );
933
1096
  return [header, ...lines].join("\n");
934
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
+ }
935
1157
  async function collectionSetVisibility(client, nameOrId, visibility) {
936
1158
  return client.request(
937
1159
  `/v1/collections/${encodeURIComponent(nameOrId)}`,
@@ -1104,6 +1326,29 @@ function formatCollectionExportFlat(result) {
1104
1326
  return parts.join("\n");
1105
1327
  }
1106
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
+
1107
1352
  export {
1108
1353
  tree,
1109
1354
  formatTreeOutput,
@@ -1152,6 +1397,12 @@ export {
1152
1397
  readDocument,
1153
1398
  collectionList,
1154
1399
  formatCollectionList,
1400
+ collectionCreate,
1401
+ collectionShow,
1402
+ formatCollectionDetail,
1403
+ collectionDelete,
1404
+ collectionAddDocs,
1405
+ collectionRemoveDocs,
1155
1406
  collectionSetVisibility,
1156
1407
  collectionQueryRaw,
1157
1408
  formatCollectionCsv,
@@ -1161,6 +1412,13 @@ export {
1161
1412
  formatExtractTable,
1162
1413
  formatExtractJson,
1163
1414
  collectionExport,
1164
- formatCollectionExportFlat
1415
+ formatCollectionExportFlat,
1416
+ localIngest,
1417
+ localStatus,
1418
+ localSummary,
1419
+ localSearch,
1420
+ localPage,
1421
+ localTables,
1422
+ localDoctor
1165
1423
  };
1166
- //# sourceMappingURL=chunk-F3LECDPP.js.map
1424
+ //# sourceMappingURL=chunk-ETARIBOV.js.map