@cnrai/pave 0.3.32 → 0.3.34

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 (83) hide show
  1. package/MARKETPLACE.md +406 -0
  2. package/README.md +218 -21
  3. package/build-binary.js +591 -0
  4. package/build-npm.js +537 -0
  5. package/build.js +230 -0
  6. package/check-binary.js +26 -0
  7. package/deploy.sh +95 -0
  8. package/index.js +5775 -0
  9. package/lib/agent-registry.js +1037 -0
  10. package/lib/args-parser.js +837 -0
  11. package/lib/blessed-widget-patched.js +93 -0
  12. package/lib/cli-markdown.js +590 -0
  13. package/lib/compaction.js +153 -0
  14. package/lib/duration.js +94 -0
  15. package/lib/hash.js +22 -0
  16. package/lib/marketplace.js +866 -0
  17. package/lib/memory-config.js +166 -0
  18. package/lib/skill-manager.js +891 -0
  19. package/lib/soul.js +31 -0
  20. package/lib/tool-output-formatter.js +180 -0
  21. package/package.json +35 -33
  22. package/start-pave.sh +149 -0
  23. package/status.js +271 -0
  24. package/test/abort-stream.test.js +445 -0
  25. package/test/agent-auto-compaction.test.js +552 -0
  26. package/test/agent-comm-abort.test.js +95 -0
  27. package/test/agent-comm.test.js +598 -0
  28. package/test/agent-inbox.test.js +576 -0
  29. package/test/agent-init.test.js +264 -0
  30. package/test/agent-interrupt.test.js +314 -0
  31. package/test/agent-lifecycle.test.js +520 -0
  32. package/test/agent-log-files.test.js +349 -0
  33. package/test/agent-mode.manual-test.js +392 -0
  34. package/test/agent-parsing.test.js +228 -0
  35. package/test/agent-post-stream-idle.test.js +762 -0
  36. package/test/agent-registry.test.js +359 -0
  37. package/test/agent-rm.test.js +442 -0
  38. package/test/agent-spawn.test.js +933 -0
  39. package/test/agent-status-api.test.js +624 -0
  40. package/test/agent-update.test.js +435 -0
  41. package/test/args-parser.test.js +391 -0
  42. package/test/auto-compaction-chat.manual-test.js +227 -0
  43. package/test/auto-compaction.test.js +941 -0
  44. package/test/build-config.test.js +120 -0
  45. package/test/build-npm.test.js +388 -0
  46. package/test/chat-command.test.js +137 -0
  47. package/test/chat-leading-lines.test.js +159 -0
  48. package/test/config-flag.test.js +272 -0
  49. package/test/cursor-drift.test.js +135 -0
  50. package/test/debug-require.js +23 -0
  51. package/test/dir-migration.test.js +323 -0
  52. package/test/duration.test.js +229 -0
  53. package/test/ghostty-term.test.js +202 -0
  54. package/test/http500-backoff.test.js +854 -0
  55. package/test/integration.test.js +86 -0
  56. package/test/memory-guard-env.test.js +220 -0
  57. package/test/pr233-fixes.test.js +259 -0
  58. package/test/run-agent-init.js +297 -0
  59. package/test/run-all.js +64 -0
  60. package/test/run-config-flag.js +159 -0
  61. package/test/run-cursor-drift.js +82 -0
  62. package/test/run-session-path.js +154 -0
  63. package/test/run-tests.js +643 -0
  64. package/test/sandbox-redirect.test.js +202 -0
  65. package/test/session-path.test.js +132 -0
  66. package/test/shebang-strip.test.js +241 -0
  67. package/test/soul-reinject.test.js +1027 -0
  68. package/test/soul-reread.test.js +281 -0
  69. package/test/tool-output-formatter.test.js +486 -0
  70. package/test/tool-output-gating.test.js +143 -0
  71. package/test/tool-states.test.js +167 -0
  72. package/test/tools-flag.test.js +65 -0
  73. package/test/tui-attach.test.js +1255 -0
  74. package/test/tui-compaction.test.js +354 -0
  75. package/test/tui-wrap.test.js +568 -0
  76. package/test-binary.js +52 -0
  77. package/test-binary2.js +36 -0
  78. package/LICENSE +0 -21
  79. package/pave.js +0 -2
  80. package/sandbox/SandboxRunner.js +0 -1
  81. package/sandbox/pave-run.js +0 -2
  82. package/sandbox/permission.js +0 -1
  83. package/sandbox/utils/yaml.js +0 -1
package/build-npm.js ADDED
@@ -0,0 +1,537 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Build script for npm-publishable PAVE package
4
+ *
5
+ * Creates an obfuscated, Node 16-compatible npm package at dist/npm/
6
+ * that can be published as @cnrai/pave.
7
+ *
8
+ * Pipeline: esbuild bundle � javascript-obfuscator � npm package
9
+ *
10
+ * Usage:
11
+ * node build-npm.js # Build only
12
+ * node build-npm.js --publish # Build and publish (requires npm login)
13
+ * node build-npm.js --dry-run # Build and show what would be published
14
+ *
15
+ * Output:
16
+ * dist/npm/
17
+ * package.json - npm package metadata
18
+ * pave.js - Obfuscated main bundle with shebang
19
+ * sandbox/ - Obfuscated sandbox runner files
20
+ * pave-run.js
21
+ * SandboxRunner.js
22
+ * permission.js
23
+ * utils/yaml.js
24
+ * README.md - Usage instructions
25
+ * LICENSE - MIT License
26
+ */
27
+
28
+ const { execSync, execFileSync } = require('child_process');
29
+ const esbuild = require('esbuild');
30
+ const fs = require('fs');
31
+ const path = require('path');
32
+
33
+ // Directories
34
+ const ROOT_DIR = __dirname;
35
+ const OPENCODE_LITE_DIR = path.join(ROOT_DIR, '..', 'opencode-lite');
36
+ const DIST_DIR = path.join(ROOT_DIR, 'dist', 'npm');
37
+ const SANDBOX_DEST_DIR = path.join(DIST_DIR, 'sandbox');
38
+
39
+ // Get version from package.json
40
+ const packageJson = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'package.json'), 'utf8'));
41
+ const VERSION = packageJson.version || '0.0.0';
42
+
43
+ // Check for flags (mutually exclusive)
44
+ const hasPublishFlag = process.argv.includes('--publish');
45
+ const hasDryRunFlag = process.argv.includes('--dry-run');
46
+
47
+ if (hasPublishFlag && hasDryRunFlag) {
48
+ console.error('Error: --publish and --dry-run cannot be used together. Please specify only one.');
49
+ process.exit(1);
50
+ }
51
+
52
+ const shouldPublish = hasPublishFlag;
53
+ const isDryRun = hasDryRunFlag;
54
+
55
+ /**
56
+ * Obfuscate a JavaScript file using javascript-obfuscator.
57
+ * Uses execFileSync with argument array to safely handle paths with spaces.
58
+ * @param {string} inputPath - Path to the source file
59
+ * @param {string} outputPath - Path for the obfuscated output
60
+ * @returns {string} Path to the obfuscated file
61
+ */
62
+ function obfuscateFile(inputPath, outputPath) {
63
+ // Resolve npx path for execFileSync
64
+ const npxPath = process.platform === 'win32' ? 'npx.cmd' : 'npx';
65
+
66
+ // Same obfuscation settings as build-binary.js for consistency
67
+ // Use --no-install to prevent npx from fetching packages from the registry
68
+ const args = [
69
+ '--no-install',
70
+ 'javascript-obfuscator',
71
+ inputPath,
72
+ '--output', outputPath,
73
+ '--compact', 'true',
74
+ '--control-flow-flattening', 'false',
75
+ '--dead-code-injection', 'false',
76
+ '--identifier-names-generator', 'mangled',
77
+ '--rename-globals', 'false',
78
+ '--self-defending', 'false',
79
+ '--string-array', 'true',
80
+ '--string-array-encoding', 'base64',
81
+ '--string-array-threshold', '1.0',
82
+ '--seed', '42',
83
+ '--transform-object-keys', 'false',
84
+ '--unicode-escape-sequence', 'false',
85
+ '--target', 'node',
86
+ '--options-preset', 'low-obfuscation',
87
+ '--reserved-names', 'authenticated,valid,token,copilot,github,OpenCode,blessed,require,module,exports,process,console,Buffer,__dirname,__filename,program,tput,screen,cursor,options,element,children,parent,box,text,list,input,key,mouse,emit,on,off,render,destroy,focus,append,remove,hide,show,toggle,scroll,setContent,getContent,pushLine,setLine,insertLine,deleteLine,width,height,left,right,top,bottom,rows,cols',
88
+ '--reserved-strings', 'authenticated,valid,token,GitHub Copilot,Saved token is valid,OpenCode',
89
+ '--split-strings', 'false',
90
+ ];
91
+
92
+ try {
93
+ execFileSync(npxPath, args, { stdio: 'pipe', cwd: ROOT_DIR });
94
+ } catch (err) {
95
+ let message = 'javascript-obfuscator failed for ' + inputPath + ':\n' + String(err && err.message ? err.message : err);
96
+ if (err && err.stdout) {
97
+ message += '\nstdout:\n' + err.stdout.toString();
98
+ }
99
+ if (err && err.stderr) {
100
+ message += '\nstderr:\n' + err.stderr.toString();
101
+ }
102
+ throw new Error(message);
103
+ }
104
+ return outputPath;
105
+ }
106
+
107
+ async function build() {
108
+ console.log('='.repeat(60));
109
+ console.log('Building PAVE npm package v' + VERSION);
110
+ console.log('='.repeat(60));
111
+
112
+ // Clean and recreate dist/npm directory
113
+ if (fs.existsSync(DIST_DIR)) {
114
+ fs.rmSync(DIST_DIR, { recursive: true, force: true });
115
+ }
116
+ fs.mkdirSync(DIST_DIR, { recursive: true });
117
+ fs.mkdirSync(SANDBOX_DEST_DIR, { recursive: true });
118
+ fs.mkdirSync(path.join(SANDBOX_DEST_DIR, 'utils'), { recursive: true });
119
+
120
+ // Ensure javascript-obfuscator is available (must be pre-installed as devDependency)
121
+ console.log('\n[0/5] Checking javascript-obfuscator...');
122
+ const npxCheck = process.platform === 'win32' ? 'npx.cmd' : 'npx';
123
+ try {
124
+ execFileSync(npxCheck, ['--no-install', 'javascript-obfuscator', '--version'], { stdio: 'pipe', cwd: ROOT_DIR });
125
+ console.log(' javascript-obfuscator is available');
126
+ } catch (e) {
127
+ console.error(' Error: javascript-obfuscator is not installed.');
128
+ console.error(' Run: npm install --save-dev javascript-obfuscator');
129
+ process.exit(1);
130
+ }
131
+
132
+ // Step 1: Bundle with esbuild
133
+ console.log('\n[1/5] Bundling with esbuild (target: node16)...');
134
+
135
+ const bundlePath = path.join(DIST_DIR, 'pave-bundle.js');
136
+
137
+ await esbuild.build({
138
+ entryPoints: [path.join(ROOT_DIR, 'index.js')],
139
+ bundle: true,
140
+ platform: 'node',
141
+ target: 'node16',
142
+ outfile: bundlePath,
143
+ define: {
144
+ PAVE_VERSION: JSON.stringify(VERSION),
145
+ },
146
+ nodePaths: [path.join(ROOT_DIR, 'node_modules')],
147
+ external: [
148
+ 'blessed',
149
+ 'abort-controller',
150
+ 'term.js',
151
+ 'pty.js',
152
+ ],
153
+ minify: true,
154
+ keepNames: true,
155
+ sourcemap: false,
156
+ format: 'cjs',
157
+ });
158
+
159
+ const bundleStats = fs.statSync(bundlePath);
160
+ console.log(' Bundle size: ' + (bundleStats.size / 1024).toFixed(1) + ' KB');
161
+
162
+ // Step 2: Obfuscate main bundle
163
+ console.log('\n[2/5] Obfuscating main bundle...');
164
+
165
+ const obfuscatedPath = path.join(DIST_DIR, 'pave.js');
166
+ obfuscateFile(bundlePath, obfuscatedPath);
167
+
168
+ // Add shebang, iSH auto-detection, and make executable
169
+ let content = fs.readFileSync(obfuscatedPath, 'utf-8');
170
+ content = content.replace(/^(#!.*\n)+/, '');
171
+
172
+ // iSH auto-detection prefix: re-execs with optimized flags if on iSH
173
+ // Runs before any heavy imports. Checks process.execArgv to avoid infinite loop.
174
+ const ishPrefix = [
175
+ '(function(){',
176
+ 'if(process.execArgv.indexOf("--jitless")!==-1)return;',
177
+ 'try{require("fs").readFileSync("/proc/ish/version","utf-8")}catch(e){return}',
178
+ 'var f=["--jitless","--max-old-space-size=128","--optimize-for-size",',
179
+ '"--no-lazy","--expose-gc","--gc-interval=25","--max-semi-space-size=8",',
180
+ '"--memory-reducer","--no-compilation-cache","--no-turbo-inlining",',
181
+ '"--no-use-osr","--no-opt","--stack-size=512"];',
182
+ 'var r=require("child_process").spawnSync(process.execPath,',
183
+ 'f.concat([__filename]).concat(process.argv.slice(2)),',
184
+ '{stdio:"inherit"});',
185
+ 'process.exit(r.status||0);',
186
+ '})();',
187
+ ].join('');
188
+
189
+ content = '#!/usr/bin/env node\n' + ishPrefix + '\n' + content;
190
+ fs.writeFileSync(obfuscatedPath, content);
191
+ fs.chmodSync(obfuscatedPath, 0o755);
192
+
193
+ // Remove intermediate bundle
194
+ fs.unlinkSync(bundlePath);
195
+
196
+ const obfStats = fs.statSync(obfuscatedPath);
197
+ console.log(' Obfuscated size: ' + (obfStats.size / 1024).toFixed(1) + ' KB');
198
+
199
+ // Verify obfuscation of main bundle
200
+ const obfContent = fs.readFileSync(obfuscatedPath, 'utf-8');
201
+ const sensitiveStrings = ['newGlobal', 'os.system', 'SandboxRunner', 'drainJobQueue'];
202
+ const found = sensitiveStrings.filter((s) => { return obfContent.includes(s); });
203
+ if (found.length > 0) {
204
+ console.error(' Error: Some sensitive strings still visible in pave.js after obfuscation: ' + found.join(', '));
205
+ process.exit(1);
206
+ } else {
207
+ console.log(' Sensitive strings successfully obfuscated');
208
+ }
209
+
210
+ // Step 3: Copy and obfuscate sandbox files
211
+ console.log('\n[3/5] Copying and obfuscating sandbox files...');
212
+
213
+ const sandboxFiles = [
214
+ { src: 'bin/pave-run.js', dest: 'pave-run.js' },
215
+ { src: 'src/sandbox/SandboxRunner.js', dest: 'SandboxRunner.js' },
216
+ { src: 'src/tools/permission.js', dest: 'permission.js' },
217
+ { src: 'src/utils/yaml.js', dest: 'utils/yaml.js' },
218
+ ];
219
+
220
+ for (let i = 0; i < sandboxFiles.length; i++) {
221
+ const file = sandboxFiles[i];
222
+ const srcPath = path.join(OPENCODE_LITE_DIR, file.src);
223
+ const destPath = path.join(SANDBOX_DEST_DIR, file.dest);
224
+
225
+ if (!fs.existsSync(srcPath)) {
226
+ console.error(' Error: Required sandbox file is missing: ' + srcPath);
227
+ process.exit(1);
228
+ }
229
+
230
+ let fileContent = fs.readFileSync(srcPath, 'utf-8');
231
+
232
+ // Fix require paths for the bundled structure (same as build.js)
233
+ if (file.dest === 'pave-run.js') {
234
+ fileContent = fileContent.replace(
235
+ /var sandboxPath = path\.join\(__dirname.*SandboxRunner\.js'\);/,
236
+ "var sandboxPath = path.join(__dirname, 'SandboxRunner.js');",
237
+ );
238
+ fileContent = fileContent.replace(
239
+ /var permissionPath = path\.join\(__dirname.*permission\.js'\);/,
240
+ "var permissionPath = path.join(__dirname, 'permission.js');",
241
+ );
242
+ }
243
+
244
+ if (file.dest === 'SandboxRunner.js') {
245
+ fileContent = fileContent.replace(
246
+ /} = require\(['"]\.\.\/tools\/permission['"]\);/,
247
+ "} = require('./permission');",
248
+ );
249
+ fileContent = fileContent.replace(
250
+ /require\(['"]\.\.\/tools\/permission['"]\)/g,
251
+ "require('./permission')",
252
+ );
253
+ }
254
+
255
+ if (file.dest === 'permission.js') {
256
+ fileContent = fileContent.replace(
257
+ /require\(['"]\.\.\/utils\/yaml['"]\)/g,
258
+ "require('./utils/yaml')",
259
+ );
260
+ // Strip developer-specific absolute paths from LEGACY_ENV_FILE_PATHS
261
+ // to avoid leaking local machine info into the npm package
262
+ fileContent = fileContent.replace(
263
+ /,\s*['"]\/Users\/[^'"]*['"]/g,
264
+ '',
265
+ );
266
+ }
267
+
268
+ // Write patched file to a temp location, then obfuscate to dest
269
+ const tempPath = destPath.replace(/\.js$/, '.input.js');
270
+ fs.writeFileSync(tempPath, fileContent);
271
+
272
+ try {
273
+ obfuscateFile(tempPath, destPath);
274
+ fs.unlinkSync(tempPath);
275
+ console.log(' Obfuscated: sandbox/' + file.dest);
276
+ } catch (err) {
277
+ // yaml.js is a generic YAML parsing utility with no PAVE-specific logic,
278
+ // so it's safe to ship unobfuscated if obfuscation fails
279
+ if (file.dest === 'utils/yaml.js') {
280
+ console.warn(' Warning: Obfuscation skipped for sandbox/utils/yaml.js; using patched source.');
281
+ fs.renameSync(tempPath, destPath);
282
+ } else {
283
+ console.error(' Error: Failed to obfuscate sandbox/' + file.dest + ': ' + err.message);
284
+ try { fs.unlinkSync(tempPath); } catch (e) { /* cleanup best-effort */ }
285
+ process.exit(1);
286
+ }
287
+ }
288
+ }
289
+
290
+ // Verify sandbox outputs for sensitive strings (except yaml.js which may be unobfuscated)
291
+ const sandboxSensitive = ['newGlobal', 'os.system', 'drainJobQueue', 'SandboxRunner'];
292
+ const whitelistedSandbox = ['utils/yaml.js'];
293
+ // pave-run.js legitimately references SandboxRunner as a require path/variable name
294
+ const perFileWhitelist = { 'pave-run.js': ['SandboxRunner'] };
295
+ for (let k = 0; k < sandboxFiles.length; k++) {
296
+ const sf = sandboxFiles[k];
297
+ if (whitelistedSandbox.indexOf(sf.dest) > -1) continue;
298
+ const sfPath = path.join(SANDBOX_DEST_DIR, sf.dest);
299
+ if (!fs.existsSync(sfPath)) continue;
300
+ const sfContent = fs.readFileSync(sfPath, 'utf-8');
301
+ const fileWhitelist = perFileWhitelist[sf.dest] || [];
302
+ const sfFound = sandboxSensitive.filter((s) => {
303
+ return fileWhitelist.indexOf(s) === -1 && sfContent.includes(s);
304
+ });
305
+ if (sfFound.length > 0) {
306
+ console.error(' Error: Sensitive strings in sandbox/' + sf.dest + ': ' + sfFound.join(', '));
307
+ process.exit(1);
308
+ }
309
+ }
310
+ console.log(' Sandbox files verified: no sensitive strings detected');
311
+
312
+ // Check for developer-specific paths that should not be in published package
313
+ const devPathPatterns = [/\/Users\/[^/\\]+\//g, /\/home\/[^/\\]+\//g, /C:\\\\Users\\\\[^\\]+\\\\/g];
314
+ for (let m = 0; m < sandboxFiles.length; m++) {
315
+ const dpFile = sandboxFiles[m];
316
+ const dpPath = path.join(SANDBOX_DEST_DIR, dpFile.dest);
317
+ if (!fs.existsSync(dpPath)) continue;
318
+ const dpContent = fs.readFileSync(dpPath, 'utf-8');
319
+ for (let n = 0; n < devPathPatterns.length; n++) {
320
+ const devMatch = dpContent.match(devPathPatterns[n]);
321
+ if (devMatch) {
322
+ console.error(' Error: Developer-specific path found in sandbox/' + dpFile.dest + ': ' + devMatch[0]);
323
+ process.exit(1);
324
+ }
325
+ }
326
+ }
327
+ console.log(' Sandbox files verified: no developer-specific paths detected');
328
+
329
+ // Step 4: Generate package.json, README, LICENSE
330
+ console.log('\n[4/5] Generating package metadata...');
331
+
332
+ const npmPackage = {
333
+ name: '@cnrai/pave',
334
+ version: VERSION,
335
+ description: 'PAVE - Personal AI Virtual Environment. AI agent framework for the terminal.',
336
+ main: 'pave.js',
337
+ bin: {
338
+ pave: './pave.js',
339
+ },
340
+ scripts: {
341
+ start: 'node pave.js',
342
+ },
343
+ engines: {
344
+ node: '>=16.0.0',
345
+ },
346
+ os: ['darwin', 'linux', 'win32'],
347
+ dependencies: {
348
+ blessed: '^0.1.81',
349
+ 'js-yaml': '^4.1.0',
350
+ },
351
+ files: [
352
+ 'pave.js',
353
+ 'sandbox/',
354
+ 'README.md',
355
+ 'LICENSE',
356
+ ],
357
+ keywords: [
358
+ 'ai', 'terminal', 'tui', 'agent', 'cli',
359
+ 'ish', 'ios', 'node16', 'memory-efficient',
360
+ ],
361
+ repository: {
362
+ type: 'git',
363
+ url: 'git+https://github.com/cnrai/openpave.git',
364
+ },
365
+ homepage: 'https://github.com/cnrai/openpave',
366
+ bugs: {
367
+ url: 'https://github.com/cnrai/openpave/issues',
368
+ },
369
+ author: 'CNRAI',
370
+ license: 'MIT',
371
+ };
372
+
373
+ fs.writeFileSync(
374
+ path.join(DIST_DIR, 'package.json'),
375
+ JSON.stringify(npmPackage, null, 2) + '\n',
376
+ );
377
+ console.log(' Created package.json');
378
+
379
+ // README
380
+ const readme = [
381
+ '# @cnrai/pave',
382
+ '',
383
+ 'PAVE - Personal AI Virtual Environment. AI agent framework for the terminal.',
384
+ '',
385
+ '## Installation',
386
+ '',
387
+ '```bash',
388
+ 'npm install -g @cnrai/pave',
389
+ '```',
390
+ '',
391
+ '## Usage',
392
+ '',
393
+ '```bash',
394
+ 'pave # Launch TUI',
395
+ 'pave --help # Show help',
396
+ 'pave --version # Show version',
397
+ 'pave chat "question" # Quick chat',
398
+ '```',
399
+ '',
400
+ '### iSH (iOS)',
401
+ '',
402
+ '```bash',
403
+ 'npm install -g @cnrai/pave',
404
+ 'pave # Auto-detects iSH and applies optimized Node flags',
405
+ '```',
406
+ '',
407
+ '## Requirements',
408
+ '',
409
+ '- Node.js >= 16.0.0',
410
+ '- A GitHub Copilot subscription (for the AI backend)',
411
+ '',
412
+ '## Links',
413
+ '',
414
+ '- Source: https://github.com/cnrai/openpave',
415
+ '- Issues: https://github.com/cnrai/openpave/issues',
416
+ '- Homebrew (native binary): `brew tap cnrai/tap && brew install pave`',
417
+ '',
418
+ '## License',
419
+ '',
420
+ 'MIT - Copyright (c) 2025 CNRAI',
421
+ '',
422
+ ].join('\n');
423
+
424
+ fs.writeFileSync(path.join(DIST_DIR, 'README.md'), readme);
425
+ console.log(' Created README.md');
426
+
427
+ // LICENSE
428
+ const license = [
429
+ 'MIT License',
430
+ '',
431
+ 'Copyright (c) 2025 CNRAI',
432
+ '',
433
+ 'Permission is hereby granted, free of charge, to any person obtaining a copy',
434
+ 'of this software and associated documentation files (the "Software"), to deal',
435
+ 'in the Software without restriction, including without limitation the rights',
436
+ 'to use, copy, modify, merge, publish, distribute, sublicense, and/or sell',
437
+ 'copies of the Software, and to permit persons to whom the Software is',
438
+ 'furnished to do so, subject to the following conditions:',
439
+ '',
440
+ 'The above copyright notice and this permission notice shall be included in all',
441
+ 'copies or substantial portions of the Software.',
442
+ '',
443
+ 'THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR',
444
+ 'IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,',
445
+ 'FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE',
446
+ 'AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER',
447
+ 'LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,',
448
+ 'OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE',
449
+ 'SOFTWARE.',
450
+ '',
451
+ ].join('\n');
452
+
453
+ fs.writeFileSync(path.join(DIST_DIR, 'LICENSE'), license);
454
+ console.log(' Created LICENSE');
455
+
456
+ // Step 5: Summary and optional publish
457
+ console.log('\n[5/5] Package summary...');
458
+
459
+ // Calculate total size
460
+ let totalSize = 0;
461
+ function addSize(filePath) {
462
+ if (fs.existsSync(filePath)) {
463
+ totalSize += fs.statSync(filePath).size;
464
+ }
465
+ }
466
+ addSize(path.join(DIST_DIR, 'pave.js'));
467
+ addSize(path.join(DIST_DIR, 'package.json'));
468
+ addSize(path.join(DIST_DIR, 'README.md'));
469
+ addSize(path.join(DIST_DIR, 'LICENSE'));
470
+
471
+ // Add sandbox files
472
+ const sandboxDir = path.join(DIST_DIR, 'sandbox');
473
+ if (fs.existsSync(sandboxDir)) {
474
+ const walkDir = function (dir) {
475
+ const files = fs.readdirSync(dir);
476
+ for (let j = 0; j < files.length; j++) {
477
+ const fp = path.join(dir, files[j]);
478
+ const stat = fs.statSync(fp);
479
+ if (stat.isDirectory()) {
480
+ walkDir(fp);
481
+ } else {
482
+ totalSize += stat.size;
483
+ }
484
+ }
485
+ };
486
+ walkDir(sandboxDir);
487
+ }
488
+
489
+ console.log('\n' + '='.repeat(60));
490
+ console.log('npm package built successfully!');
491
+ console.log('='.repeat(60));
492
+ console.log('\n Package: @cnrai/pave@' + VERSION);
493
+ console.log(' Location: ' + DIST_DIR);
494
+ console.log(' Size: ' + (totalSize / 1024).toFixed(1) + ' KB (before npm install)');
495
+ console.log('\n Contents:');
496
+ console.log(' pave.js - Obfuscated main bundle');
497
+ console.log(' sandbox/ - Obfuscated sandbox files');
498
+ console.log(' package.json - npm metadata');
499
+ console.log(' README.md - Usage instructions');
500
+ console.log(' LICENSE - MIT License');
501
+
502
+ if (shouldPublish) {
503
+ console.log('\n Publishing to npm...');
504
+ try {
505
+ execSync('npm publish --access public', { cwd: DIST_DIR, stdio: 'inherit' });
506
+ console.log('\n Published @cnrai/pave@' + VERSION + ' to npm!');
507
+ } catch (err) {
508
+ console.error('\n Publish failed: ' + err.message);
509
+ console.error(' Make sure you are logged in: npm login');
510
+ console.error(' And @cnrai org exists: npm org create cnrai');
511
+ process.exit(1);
512
+ }
513
+ } else if (isDryRun) {
514
+ console.log('\n Dry run (showing what would be published):');
515
+ try {
516
+ execSync('npm pack --dry-run', { cwd: DIST_DIR, stdio: 'inherit' });
517
+ } catch (err) {
518
+ // Only ignore the error if the npm command itself is not available.
519
+ if (err && (err.code === 'ENOENT' || err.status === 127)) {
520
+ console.log(' (npm pack not available, skipping dry run)');
521
+ } else {
522
+ console.error(' npm pack --dry-run failed:', err && err.message ? err.message : err);
523
+ process.exit(1);
524
+ }
525
+ }
526
+ } else {
527
+ console.log('\n To publish:');
528
+ console.log(' cd ' + DIST_DIR);
529
+ console.log(' npm publish --access public');
530
+ console.log('\n Or run: node build-npm.js --publish');
531
+ }
532
+ }
533
+
534
+ build().catch((err) => {
535
+ console.error('Build failed:', err);
536
+ process.exit(1);
537
+ });