openzoo 0.11.1 → 0.11.2

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.
Files changed (2) hide show
  1. package/lib/bindpath.js +47 -2
  2. package/package.json +1 -1
package/lib/bindpath.js CHANGED
@@ -38,6 +38,35 @@ const DEFAULT_EXTS = [
38
38
 
39
39
  const SKIP_DIRS = new Set(['node_modules', '.git', 'target', 'dist', 'build', '.next', '__pycache__', '.venv']);
40
40
 
41
+ /** Refuse rather than grind. A directory of exports can be hundreds of MB;
42
+ * binding that takes many minutes and mostly buys markup. Raise deliberately. */
43
+ export const MAX_TOTAL_BYTES = Number(process.env.OPENZOO_BIND_MAX_BYTES || 32 * 1024 * 1024);
44
+
45
+ /**
46
+ * HTML → readable text. Chat/forum exports are ~90% markup: binding the raw
47
+ * pages wastes the corpus on tags and makes retrieval rank div soup against
48
+ * the question. Deliberately crude (no parser dependency) but it keeps the
49
+ * text nodes in document order, which is all retrieval needs.
50
+ */
51
+ export function stripHtml(html) {
52
+ return html
53
+ .replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1>/gi, ' ')
54
+ .replace(/<!--[\s\S]*?-->/g, ' ')
55
+ .replace(/<br\s*\/?>/gi, '\n')
56
+ .replace(/<\/(p|div|li|tr|h[1-6])>/gi, '\n')
57
+ .replace(/<[^>]+>/g, ' ')
58
+ .replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&').replace(/&lt;/g, '<')
59
+ .replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#39;/g, "'")
60
+ .replace(/[ \t]+/g, ' ')
61
+ .replace(/\n\s*\n\s*\n+/g, '\n\n')
62
+ .trim();
63
+ }
64
+
65
+ const readAsText = (file) => {
66
+ const raw = fs.readFileSync(file, 'utf8');
67
+ return /\.html?$/i.test(file) ? stripHtml(raw) : raw;
68
+ };
69
+
41
70
  /** Every bindable file under `root` (or just `root` if it is a file). */
42
71
  export function collectFiles(root, { exts = DEFAULT_EXTS, maxFiles = 5000 } = {}) {
43
72
  const st = fs.statSync(root);
@@ -109,11 +138,27 @@ export async function bindPath(target, { exts, onProgress, force = false } = {})
109
138
  const files = collectFiles(resolved, exts ? { exts } : {});
110
139
  if (!files.length) throw new Error(`nothing bindable under ${resolved} (looked for text-like files)`);
111
140
 
141
+ // Size the job BEFORE reading anything. A 2GB export folder is 100MB of
142
+ // text-like files, and silently grinding through it for minutes reads as a
143
+ // hang — an actionable refusal is better than a spinner.
144
+ const onDisk = files.reduce((n, f) => n + fs.statSync(f).size, 0);
145
+ if (onDisk > MAX_TOTAL_BYTES) {
146
+ const biggest = files
147
+ .map((f) => ({ f, size: fs.statSync(f).size }))
148
+ .sort((a, b) => b.size - a.size).slice(0, 3)
149
+ .map(({ f, size }) => `${path.basename(f)} (${(size / 1048576).toFixed(1)}MB)`);
150
+ throw new Error(
151
+ `${(onDisk / 1048576).toFixed(0)}MB across ${files.length} files exceeds the ${(MAX_TOTAL_BYTES / 1048576).toFixed(0)}MB bind limit. `
152
+ + `Largest: ${biggest.join(', ')}. Point at one file, narrow with --ext/ext, `
153
+ + `or raise OPENZOO_BIND_MAX_BYTES if you really want all of it.`,
154
+ );
155
+ }
156
+
112
157
  // Each file is prefixed with its path so retrieval can cite where a passage
113
158
  // came from — a corpus of concatenated files with no provenance is much
114
- // less useful to answer from.
159
+ // less useful to answer from. HTML is reduced to its text (see readAsText).
115
160
  const text = files
116
- .map((f) => `===== ${path.relative(path.dirname(resolved), f) || path.basename(f)} =====\n${fs.readFileSync(f, 'utf8')}`)
161
+ .map((f) => `===== ${path.relative(path.dirname(resolved), f) || path.basename(f)} =====\n${readAsText(f)}`)
117
162
  .join('\n\n');
118
163
 
119
164
  const bytes = Buffer.byteLength(text);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "description": "Local x402-paying proxy + MCP server for openzoo.fun — point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental.",
5
5
  "license": "MIT",
6
6
  "type": "module",