ovrule-lab 0.1.3 → 0.2.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
@@ -8,6 +8,8 @@ TypeScript client for Ovrule case-file classification, guardrails, and receipt v
8
8
  npm install ovrule-lab
9
9
  ```
10
10
 
11
+ On the first successful `classify()` or `guard()` call in a process, the SDK prints a one-line ready message plus a permalink to the created case. Later successful audits print only the case permalink. Set `OVRULE_QUIET=1` to suppress those logs.
12
+
11
13
  ## 1. Simple classify
12
14
 
13
15
  ```ts
package/dist/index.js CHANGED
@@ -1,3 +1,32 @@
1
+ const DEFAULT_OVRULE_BASE_URL = "https://decision-receipt-lab.vercel.app";
2
+ const SDK_VERSION = "0.2.1";
3
+ let hasShownReadyMessage = false;
4
+ function isQuietModeEnabled() {
5
+ return (typeof process !== "undefined" &&
6
+ typeof process.env === "object" &&
7
+ typeof process.env.OVRULE_QUIET !== "undefined" &&
8
+ process.env.OVRULE_QUIET !== "");
9
+ }
10
+ function getConsoleBaseUrl(baseUrl) {
11
+ if (baseUrl) {
12
+ return baseUrl;
13
+ }
14
+ if (typeof window !== "undefined" && window.location?.origin) {
15
+ return window.location.origin;
16
+ }
17
+ return DEFAULT_OVRULE_BASE_URL;
18
+ }
19
+ function logSuccessfulAudit(receipt, baseUrl) {
20
+ if (isQuietModeEnabled()) {
21
+ return;
22
+ }
23
+ const caseUrl = `${getConsoleBaseUrl(baseUrl)}/case/${receipt.receiptId}`;
24
+ if (!hasShownReadyMessage) {
25
+ console.info(`[ovrule-lab v${SDK_VERSION}] ready · docs: ${DEFAULT_OVRULE_BASE_URL}/docs`);
26
+ hasShownReadyMessage = true;
27
+ }
28
+ console.info(`[ovrule-lab] ✓ audited via ${caseUrl}`);
29
+ }
1
30
  function normalizeAction(action, options) {
2
31
  if (typeof action === "string") {
3
32
  return {
@@ -17,11 +46,38 @@ async function readJson(response) {
17
46
  }
18
47
  return data;
19
48
  }
49
+ function parseSseBlock(block) {
50
+ const lines = block.split("\n");
51
+ const eventName = lines.find((line) => line.startsWith("event:"))?.slice(6).trim();
52
+ const data = lines
53
+ .filter((line) => line.startsWith("data:"))
54
+ .map((line) => line.slice(5).trim())
55
+ .join("\n");
56
+ if (!eventName || !data) {
57
+ return null;
58
+ }
59
+ return {
60
+ eventName,
61
+ data: JSON.parse(data),
62
+ };
63
+ }
64
+ function applySseEvent(parsed, currentReceipt) {
65
+ if (!parsed) {
66
+ return currentReceipt;
67
+ }
68
+ if (parsed.data.type === "session.error") {
69
+ throw new Error(parsed.data.message);
70
+ }
71
+ if (parsed.data.type === "analysis.completed") {
72
+ return parsed.data.receipt;
73
+ }
74
+ return currentReceipt;
75
+ }
20
76
  export class OvruleClient {
21
77
  baseUrl;
22
78
  fetchImpl;
23
79
  constructor(options = {}) {
24
- this.baseUrl = options.baseUrl?.replace(/\/$/, "") ?? "";
80
+ this.baseUrl = options.baseUrl?.replace(/\/$/, "") ?? DEFAULT_OVRULE_BASE_URL;
25
81
  this.fetchImpl = options.fetch ?? globalThis.fetch;
26
82
  if (!this.fetchImpl) {
27
83
  throw new Error("No fetch implementation available. Pass one in OvruleClient options.");
@@ -57,34 +113,26 @@ export class OvruleClient {
57
113
  let finalReceipt = null;
58
114
  while (true) {
59
115
  const { done, value } = await reader.read();
60
- if (done) {
61
- break;
116
+ if (value) {
117
+ buffer += decoder.decode(value, { stream: !done });
62
118
  }
63
- buffer += decoder.decode(value, { stream: true });
64
119
  const blocks = buffer.split("\n\n");
65
120
  buffer = blocks.pop() ?? "";
66
121
  for (const block of blocks) {
67
- const lines = block.split("\n");
68
- const eventName = lines.find((line) => line.startsWith("event:"))?.slice(6).trim();
69
- const data = lines
70
- .filter((line) => line.startsWith("data:"))
71
- .map((line) => line.slice(5).trim())
72
- .join("\n");
73
- if (!eventName || !data) {
74
- continue;
75
- }
76
- const parsed = JSON.parse(data);
77
- if (parsed.type === "analysis.completed") {
78
- finalReceipt = parsed.receipt;
79
- }
80
- if (parsed.type === "session.error") {
81
- throw new Error(parsed.message);
82
- }
122
+ finalReceipt = applySseEvent(parseSseBlock(block), finalReceipt);
83
123
  }
124
+ if (done) {
125
+ break;
126
+ }
127
+ }
128
+ buffer += decoder.decode();
129
+ if (buffer.trim()) {
130
+ finalReceipt = applySseEvent(parseSseBlock(buffer.trim()), finalReceipt);
84
131
  }
85
132
  if (!finalReceipt) {
86
133
  throw new Error("Ovrule classify stream finished without a final receipt.");
87
134
  }
135
+ logSuccessfulAudit(finalReceipt, this.baseUrl);
88
136
  return finalReceipt;
89
137
  }
90
138
  async guard(action, options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ovrule-lab",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "TypeScript SDK for Ovrule case-file classification, guardrails, and receipt verification.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -14,18 +14,12 @@
14
14
  },
15
15
  "files": [
16
16
  "dist",
17
- "README.md",
18
- "postinstall.js",
19
- "bin/welcome.js"
17
+ "README.md"
20
18
  ],
21
- "bin": {
22
- "ovrule-lab": "./bin/welcome.js"
23
- },
24
19
  "scripts": {
25
20
  "build": "tsc -p tsconfig.json",
26
21
  "clean": "rm -rf dist",
27
- "prepublishOnly": "npm run clean && npm run build",
28
- "postinstall": "node postinstall.js"
22
+ "prepublishOnly": "npm run clean && npm run build"
29
23
  },
30
24
  "keywords": [
31
25
  "ovrule",
package/bin/welcome.js DELETED
@@ -1,36 +0,0 @@
1
- #!/usr/bin/env node
2
- /* eslint-disable no-console */
3
-
4
- const reset = "\x1b[0m";
5
- const dim = "\x1b[2m";
6
- const bold = "\x1b[1m";
7
- const cyan = "\x1b[36m";
8
- const magenta = "\x1b[35m";
9
- const yellow = "\x1b[33m";
10
-
11
- const lines = [
12
- "",
13
- `${bold}${cyan} ╔═══════════════════════════════════════════════╗${reset}`,
14
- `${bold}${cyan} ║${reset} ${bold}O${reset}${magenta}━━${reset}${bold}V${reset} ${bold}Ovrule${reset} · SDK v0.1.3 ${bold}${cyan}║${reset}`,
15
- `${bold}${cyan} ║${reset} ${dim}Auditable receipts for AI agents${reset} ${bold}${cyan}║${reset}`,
16
- `${bold}${cyan} ╚═══════════════════════════════════════════════╝${reset}`,
17
- "",
18
- ` ${bold}Quickstart${reset}`,
19
- ` ${dim}import { classify } from "ovrule-lab";${reset}`,
20
- ` ${dim}const receipt = await classify("your agent action", {${reset}`,
21
- ` ${dim} baseUrl: "https://decision-receipt-lab.vercel.app"${reset}`,
22
- ` ${dim}});${reset}`,
23
- "",
24
- ` ${bold}Methods${reset}`,
25
- ` ${dim}classify(action, opts)${reset} — full case file audit`,
26
- ` ${dim}guard(action, opts)${reset} — returns { allowed, decision, ... }`,
27
- ` ${dim}verify(receipt, sig, opts)${reset} — verify a signed receipt`,
28
- "",
29
- ` ${bold}Docs${reset} ${yellow}https://decision-receipt-lab.vercel.app/docs${reset}`,
30
- ` ${bold}GitHub${reset} ${yellow}https://github.com/elakumuk/decision-receipt-lab${reset}`,
31
- "",
32
- ` ${dim}Built with Codex for the OpenAI Creator Challenge, April 2026.${reset}`,
33
- "",
34
- ];
35
-
36
- console.log(lines.join("\n"));
package/postinstall.js DELETED
@@ -1,41 +0,0 @@
1
- #!/usr/bin/env node
2
- /* eslint-disable no-console */
3
-
4
- // Allow users to opt out
5
- if (process.env.OVRULE_SILENT) {
6
- process.exit(0);
7
- }
8
-
9
- // Skip in CI to avoid cluttering build logs
10
- if (process.env.CI) {
11
- process.exit(0);
12
- }
13
-
14
- const reset = "\x1b[0m";
15
- const dim = "\x1b[2m";
16
- const bold = "\x1b[1m";
17
- const cyan = "\x1b[36m";
18
- const magenta = "\x1b[35m";
19
- const yellow = "\x1b[33m";
20
-
21
- const lines = [
22
- "",
23
- `${bold}${cyan} ╔═══════════════════════════════════════════════╗${reset}`,
24
- `${bold}${cyan} ║${reset} ${bold}O${reset}${magenta}━━${reset}${bold}V${reset} ${bold}Ovrule${reset} · SDK v0.1.2 ${bold}${cyan}║${reset}`,
25
- `${bold}${cyan} ║${reset} ${dim}Auditable receipts for AI agents${reset} ${bold}${cyan}║${reset}`,
26
- `${bold}${cyan} ╚═══════════════════════════════════════════════╝${reset}`,
27
- "",
28
- ` ${bold}Quickstart${reset}`,
29
- ` ${dim}import { classify } from "ovrule-lab";${reset}`,
30
- ` ${dim}const receipt = await classify("your agent action", {${reset}`,
31
- ` ${dim} baseUrl: "https://decision-receipt-lab.vercel.app"${reset}`,
32
- ` ${dim}});${reset}`,
33
- "",
34
- ` ${bold}Docs${reset} ${yellow}https://decision-receipt-lab.vercel.app/docs${reset}`,
35
- ` ${bold}GitHub${reset} ${yellow}https://github.com/elakumuk/decision-receipt-lab${reset}`,
36
- "",
37
- ` ${dim}Built with Codex for the OpenAI Creator Challenge, April 2026.${reset}`,
38
- "",
39
- ];
40
-
41
- console.log(lines.join("\n"));