@vib3code/sdk 2.0.3-canary.19bcbe1 → 2.0.3-canary.279c4cb

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vib3code/sdk",
3
- "version": "2.0.3-canary.19bcbe1",
3
+ "version": "2.0.3-canary.279c4cb",
4
4
  "description": "VIB3+ 4D Visualization SDK - Unified engine with 6D rotation, MCP agentic integration, and cross-platform support",
5
5
  "type": "module",
6
6
  "main": "src/core/VIB3Engine.js",
@@ -9,9 +9,7 @@
9
9
  "vib3": "./src/cli/index.js",
10
10
  "vib3-mcp": "./src/agent/mcp/stdio-server.js"
11
11
  },
12
- "sideEffects": [
13
- "./src/geometry/builders/*.js"
14
- ],
12
+ "sideEffects": false,
15
13
  "exports": {
16
14
  ".": {
17
15
  "types": "./types/adaptive-sdk.d.ts",
package/src/cli/index.js CHANGED
@@ -5,9 +5,34 @@
5
5
  */
6
6
 
7
7
  import { performance } from 'node:perf_hooks';
8
+ import path from 'node:path';
8
9
  import { mcpServer, toolDefinitions } from '../agent/index.js';
9
10
  import { schemaRegistry } from '../schemas/index.js';
10
11
 
12
+ /**
13
+ * Resolves a file path and ensures it is within the current working directory.
14
+ * @param {string} filePath The path to validate
15
+ * @returns {string} The resolved absolute path
16
+ * @throws {Error} If the path is outside the allowed directory
17
+ */
18
+ function getSafePath(filePath) {
19
+ if (!filePath) return filePath;
20
+
21
+ const cwd = process.cwd();
22
+ const resolvedPath = path.resolve(cwd, filePath);
23
+ const relative = path.relative(cwd, resolvedPath);
24
+
25
+ const isOutside = relative.startsWith('..') || path.isAbsolute(relative);
26
+
27
+ if (isOutside) {
28
+ const error = new Error('Security Error: Path traversal detected. Access denied.');
29
+ error.code = 'EACCES';
30
+ throw error;
31
+ }
32
+
33
+ return resolvedPath;
34
+ }
35
+
11
36
  /**
12
37
  * CLI Configuration
13
38
  */
@@ -446,7 +471,7 @@ async function handleTools(parsed, startTime) {
446
471
  */
447
472
  async function handleValidate(parsed, startTime) {
448
473
  const subcommand = parsed.subcommand || 'pack';
449
- const filePath = parsed.options.file || parsed.options.f || parsed.positional[0];
474
+ let filePath = parsed.options.file || parsed.options.f || parsed.positional[0];
450
475
 
451
476
  if (!filePath && subcommand !== 'schema') {
452
477
  return wrapResponse('validate', {
@@ -460,6 +485,10 @@ async function handleValidate(parsed, startTime) {
460
485
  }
461
486
 
462
487
  try {
488
+ if (filePath) {
489
+ filePath = getSafePath(filePath);
490
+ }
491
+
463
492
  switch (subcommand) {
464
493
  case 'pack': {
465
494
  // Validate a .vib3 scene pack file
@@ -533,6 +562,17 @@ async function handleValidate(parsed, startTime) {
533
562
  }, false, performance.now() - startTime);
534
563
  }
535
564
  } catch (error) {
565
+ if (error.code === 'EACCES') {
566
+ return wrapResponse('validate', {
567
+ error: {
568
+ type: 'SecurityError',
569
+ code: 'ACCESS_DENIED',
570
+ message: error.message,
571
+ suggestion: 'Ensure the file path is within the project directory'
572
+ }
573
+ }, false, performance.now() - startTime);
574
+ }
575
+
536
576
  if (error.code === 'ENOENT') {
537
577
  return wrapResponse('validate', {
538
578
  error: {
@@ -659,7 +699,6 @@ async function main() {
659
699
  */
660
700
  async function handleInit(parsed, startTime) {
661
701
  const { writeFileSync, mkdirSync, existsSync } = await import('node:fs');
662
- const { join } = await import('node:path');
663
702
 
664
703
  const projectName = parsed.subcommand || parsed.positional[0] || 'my-vib3-app';
665
704
  const template = parsed.options.template || parsed.options.t || 'vanilla';
@@ -677,7 +716,22 @@ async function handleInit(parsed, startTime) {
677
716
  }, false, performance.now() - startTime);
678
717
  }
679
718
 
680
- const projectDir = join(process.cwd(), projectName);
719
+ let projectDir;
720
+ try {
721
+ projectDir = getSafePath(projectName);
722
+ } catch (error) {
723
+ if (error.code === 'EACCES') {
724
+ return wrapResponse('init', {
725
+ error: {
726
+ type: 'SecurityError',
727
+ code: 'ACCESS_DENIED',
728
+ message: error.message,
729
+ suggestion: 'Choose a project name that results in a valid subdirectory'
730
+ }
731
+ }, false, performance.now() - startTime);
732
+ }
733
+ throw error;
734
+ }
681
735
 
682
736
  if (existsSync(projectDir)) {
683
737
  return wrapResponse('init', {
@@ -695,8 +749,8 @@ async function handleInit(parsed, startTime) {
695
749
  const files = getTemplateFiles(template, projectName);
696
750
 
697
751
  for (const [filename, content] of Object.entries(files)) {
698
- const filepath = join(projectDir, filename);
699
- const dir = join(projectDir, filename.split('/').slice(0, -1).join('/'));
752
+ const filepath = path.join(projectDir, filename);
753
+ const dir = path.join(projectDir, filename.split('/').slice(0, -1).join('/'));
700
754
  if (dir !== projectDir) {
701
755
  mkdirSync(dir, { recursive: true });
702
756
  }
@@ -355,17 +355,21 @@ function projectPoints(points, rotor, dimension, width, height) {
355
355
  const centerY = height / 2;
356
356
  const scale = Math.min(width, height) * 0.4;
357
357
 
358
+ // Reuse vectors to minimize allocation
359
+ const rotatedBuffer = new Vec4();
360
+ const projectedBuffer = new Vec4();
361
+
358
362
  for (const point of points) {
359
363
  // Apply 4D rotation
360
- const rotated = rotor.rotate(point);
364
+ rotor.rotate(point, rotatedBuffer);
361
365
 
362
366
  // Project to 3D (perspective from W)
363
- const proj3d = rotated.projectPerspective(dimension);
367
+ rotatedBuffer.projectPerspective(dimension, projectedBuffer);
364
368
 
365
369
  // Project to 2D (simple orthographic for clean SVG)
366
- const x = centerX + proj3d.x * scale;
367
- const y = centerY - proj3d.y * scale; // Flip Y for SVG coordinates
368
- const depth = proj3d.z; // Keep depth for styling
370
+ const x = centerX + projectedBuffer.x * scale;
371
+ const y = centerY - projectedBuffer.y * scale; // Flip Y for SVG coordinates
372
+ const depth = projectedBuffer.z; // Keep depth for styling
369
373
 
370
374
  projected.push({ x, y, depth, original: point });
371
375
  }
@@ -36,16 +36,28 @@ export class Projection {
36
36
  *
37
37
  * @param {Vec4} v - 4D point
38
38
  * @param {number} d - Distance parameter (typically 1.5-5)
39
+ * @param {object} [options] - Projection options
40
+ * @param {Vec4} [target] - Optional target vector to write result to
39
41
  * @returns {Vec4} Projected point (w=0)
40
42
  */
41
- static perspective(v, d = 2, options = {}) {
43
+ static perspective(v, d = 2, options = {}, target = null) {
42
44
  if (typeof d === 'object') {
43
45
  options = d;
44
46
  d = options.d ?? 2;
45
47
  }
46
- const epsilon = options.epsilon ?? DEFAULT_EPSILON;
48
+
49
+ // Handle options overload or direct target argument
50
+ if (!target && options && options.target) {
51
+ target = options.target;
52
+ }
53
+
54
+ const epsilon = (options && options.epsilon) ?? DEFAULT_EPSILON;
47
55
  const denom = clampDenominator(d - v.w, epsilon);
48
56
  const scale = 1 / denom;
57
+
58
+ if (target) {
59
+ return target.set(v.x * scale, v.y * scale, v.z * scale, 0);
60
+ }
49
61
  return new Vec4(v.x * scale, v.y * scale, v.z * scale, 0);
50
62
  }
51
63
 
@@ -126,10 +138,33 @@ export class Projection {
126
138
  * Project array of Vec4s using perspective projection
127
139
  * @param {Vec4[]} vectors
128
140
  * @param {number} d
141
+ * @param {object} [options]
142
+ * @param {Vec4[]} [target] - Optional target array to write results to
129
143
  * @returns {Vec4[]}
130
144
  */
131
- static perspectiveArray(vectors, d = 2, options = {}) {
132
- return vectors.map(v => Projection.perspective(v, d, options));
145
+ static perspectiveArray(vectors, d = 2, options = {}, target = null) {
146
+ // Handle options overload for 'd'
147
+ if (typeof d === 'object') {
148
+ options = d;
149
+ d = options.d ?? 2;
150
+ }
151
+
152
+ if (!target) {
153
+ return vectors.map(v => Projection.perspective(v, d, options));
154
+ }
155
+
156
+ const count = vectors.length;
157
+ // Iterate and reuse
158
+ for (let i = 0; i < count; i++) {
159
+ const out = target[i];
160
+ if (out) {
161
+ Projection.perspective(vectors[i], d, options, out);
162
+ } else {
163
+ target[i] = Projection.perspective(vectors[i], d, options);
164
+ }
165
+ }
166
+
167
+ return target;
133
168
  }
134
169
 
135
170
  /**
package/src/math/Vec4.js CHANGED
@@ -408,42 +408,91 @@ export class Vec4 {
408
408
  /**
409
409
  * Project 4D point to 3D using perspective projection
410
410
  * Projects from 4D to 3D by dividing by (d - w)
411
- * @param {number} d - Distance parameter (usually 2-5)
412
- * @param {object} [options] - Projection options (epsilon, distance)
411
+ * @param {number|object} d - Distance parameter (usually 2-5) or options object
412
+ * @param {object|Vec4} [options] - Projection options or target vector
413
+ * @param {Vec4} [target] - Target vector to store result
413
414
  * @returns {Vec4} Projected point (w component is 0)
414
415
  */
415
- projectPerspective(d = 2, options = {}) {
416
+ projectPerspective(d = 2, options = {}, target = null) {
416
417
  if (typeof d === 'object') {
418
+ // usage: projectPerspective({ distance: 2, ... }, target?)
419
+ if (options instanceof Vec4) {
420
+ target = options;
421
+ }
417
422
  options = d;
418
423
  d = options.distance ?? options.d ?? 2;
424
+ } else {
425
+ // usage: projectPerspective(d, options?, target?)
426
+ // usage: projectPerspective(d, target?)
427
+ if (options instanceof Vec4) {
428
+ target = options;
429
+ options = {};
430
+ }
419
431
  }
432
+
433
+ options = options || {};
434
+
420
435
  const epsilon = options.epsilon ?? 1e-5;
421
436
  const denom = d - this._w;
422
437
  const clamped = Math.abs(denom) < epsilon ? (denom >= 0 ? epsilon : -epsilon) : denom;
423
438
  const scale = 1 / clamped;
439
+
440
+ if (target) {
441
+ target._x = this._x * scale;
442
+ target._y = this._y * scale;
443
+ target._z = this._z * scale;
444
+ target._w = 0;
445
+ return target;
446
+ }
447
+
424
448
  return new Vec4(this._x * scale, this._y * scale, this._z * scale, 0);
425
449
  }
426
450
 
427
451
  /**
428
452
  * Project 4D point to 3D using stereographic projection
429
453
  * Maps 4D hypersphere to 3D space
430
- * @param {object} [options] - Projection options (epsilon)
454
+ * @param {object|Vec4} [options] - Projection options or target vector
455
+ * @param {Vec4} [target] - Target vector to store result
431
456
  * @returns {Vec4} Projected point (w component is 0)
432
457
  */
433
- projectStereographic(options = {}) {
458
+ projectStereographic(options = {}, target = null) {
459
+ if (options instanceof Vec4) {
460
+ target = options;
461
+ options = {};
462
+ }
463
+
464
+ options = options || {};
465
+
434
466
  const epsilon = options.epsilon ?? 1e-5;
435
467
  const denom = 1 - this._w;
436
468
  const clamped = Math.abs(denom) < epsilon ? (denom >= 0 ? epsilon : -epsilon) : denom;
437
469
  const scale = 1 / clamped;
470
+
471
+ if (target) {
472
+ target._x = this._x * scale;
473
+ target._y = this._y * scale;
474
+ target._z = this._z * scale;
475
+ target._w = 0;
476
+ return target;
477
+ }
478
+
438
479
  return new Vec4(this._x * scale, this._y * scale, this._z * scale, 0);
439
480
  }
440
481
 
441
482
  /**
442
483
  * Project 4D point to 3D using orthographic projection
443
484
  * Simply drops the W component
444
- * @returns {Vec4} Projected point (w component is 0)
485
+ * @param {Vec4} [target=null] - Optional target vector
486
+ * @returns {Vec4} Projected point (w component is 0) or target
445
487
  */
446
- projectOrthographic() {
488
+ projectOrthographic(target = null) {
489
+ if (target) {
490
+ target._x = this._x;
491
+ target._y = this._y;
492
+ target._z = this._z;
493
+ target._w = 0;
494
+ return target;
495
+ }
447
496
  return new Vec4(this._x, this._y, this._z, 0);
448
497
  }
449
498
 
@@ -0,0 +1,38 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import Projection from '../math/Projection.js';
3
+ import Vec4 from '../math/Vec4.js';
4
+
5
+ describe('Projection Class', () => {
6
+ it('should project using perspective', () => {
7
+ const v = new Vec4(1, 1, 1, 0);
8
+ const p = Projection.perspective(v, 2);
9
+ expect(p.x).toBeCloseTo(0.5);
10
+ expect(p.y).toBeCloseTo(0.5);
11
+ expect(p.z).toBeCloseTo(0.5);
12
+ expect(p.w).toBe(0);
13
+ });
14
+
15
+ it('should support target vector in perspective', () => {
16
+ const v = new Vec4(1, 1, 1, 0);
17
+ const target = new Vec4();
18
+ const result = Projection.perspective(v, 2, {}, target);
19
+ expect(result).toBe(target);
20
+ expect(target.x).toBeCloseTo(0.5);
21
+ });
22
+
23
+ it('should project array using perspectiveArray', () => {
24
+ const vectors = [new Vec4(1, 1, 1, 0), new Vec4(2, 2, 2, 0)];
25
+ const result = Projection.perspectiveArray(vectors, 2);
26
+ expect(result.length).toBe(2);
27
+ expect(result[0].x).toBeCloseTo(0.5);
28
+ expect(result[1].x).toBeCloseTo(1.0);
29
+ });
30
+
31
+ it('should reuse target array in perspectiveArray', () => {
32
+ const vectors = [new Vec4(1, 1, 1, 0)];
33
+ const targetArray = [new Vec4()];
34
+ const result = Projection.perspectiveArray(vectors, 2, {}, targetArray);
35
+ expect(result).toBe(targetArray);
36
+ expect(targetArray[0].x).toBeCloseTo(0.5);
37
+ });
38
+ });
@@ -0,0 +1,109 @@
1
+ import re
2
+
3
+ file_path = 'src/math/Projection.js'
4
+
5
+ with open(file_path, 'r') as f:
6
+ content = f.read()
7
+
8
+ # Replace perspective JSDoc and Implementation
9
+ perspective_old = r""" * @param {Vec4} v - 4D point
10
+ * @param {number} d - Distance parameter (typically 1.5-5)
11
+ * @returns {Vec4} Projected point (w=0)
12
+ */
13
+ static perspective(v, d = 2, options = {}) {
14
+ if (typeof d === 'object') {
15
+ options = d;
16
+ d = options.d ?? 2;
17
+ }
18
+ const epsilon = options.epsilon ?? DEFAULT_EPSILON;
19
+ const denom = clampDenominator(d - v.w, epsilon);
20
+ const scale = 1 / denom;
21
+ return new Vec4(v.x * scale, v.y * scale, v.z * scale, 0);
22
+ }"""
23
+
24
+ perspective_new = r""" * @param {Vec4} v - 4D point
25
+ * @param {number} d - Distance parameter (typically 1.5-5)
26
+ * @param {object} [options] - Projection options
27
+ * @param {Vec4} [target] - Optional target vector to write result to
28
+ * @returns {Vec4} Projected point (w=0)
29
+ */
30
+ static perspective(v, d = 2, options = {}, target = null) {
31
+ if (typeof d === 'object') {
32
+ options = d;
33
+ d = options.d ?? 2;
34
+ }
35
+
36
+ // Handle options overload or direct target argument
37
+ if (!target && options && options.target) {
38
+ target = options.target;
39
+ }
40
+
41
+ const epsilon = (options && options.epsilon) ?? DEFAULT_EPSILON;
42
+ const denom = clampDenominator(d - v.w, epsilon);
43
+ const scale = 1 / denom;
44
+
45
+ if (target) {
46
+ return target.set(v.x * scale, v.y * scale, v.z * scale, 0);
47
+ }
48
+ return new Vec4(v.x * scale, v.y * scale, v.z * scale, 0);
49
+ }"""
50
+
51
+ if perspective_old in content:
52
+ content = content.replace(perspective_old, perspective_new)
53
+ else:
54
+ print("Could not find perspective implementation to replace.")
55
+ # Attempt more robust search if exact match fails? No, simpler is safer for now.
56
+
57
+ # Replace perspectiveArray JSDoc and Implementation
58
+ perspective_array_old = r""" /**
59
+ * Project array of Vec4s using perspective projection
60
+ * @param {Vec4[]} vectors
61
+ * @param {number} d
62
+ * @returns {Vec4[]}
63
+ */
64
+ static perspectiveArray(vectors, d = 2, options = {}) {
65
+ return vectors.map(v => Projection.perspective(v, d, options));
66
+ }"""
67
+
68
+ perspective_array_new = r""" /**
69
+ * Project array of Vec4s using perspective projection
70
+ * @param {Vec4[]} vectors
71
+ * @param {number} d
72
+ * @param {object} [options]
73
+ * @param {Vec4[]} [target] - Optional target array to write results to
74
+ * @returns {Vec4[]}
75
+ */
76
+ static perspectiveArray(vectors, d = 2, options = {}, target = null) {
77
+ // Handle options overload for 'd'
78
+ if (typeof d === 'object') {
79
+ options = d;
80
+ d = options.d ?? 2;
81
+ }
82
+
83
+ if (!target) {
84
+ return vectors.map(v => Projection.perspective(v, d, options));
85
+ }
86
+
87
+ const count = vectors.length;
88
+ // Iterate and reuse
89
+ for (let i = 0; i < count; i++) {
90
+ const out = target[i];
91
+ if (out) {
92
+ Projection.perspective(vectors[i], d, options, out);
93
+ } else {
94
+ target[i] = Projection.perspective(vectors[i], d, options);
95
+ }
96
+ }
97
+
98
+ return target;
99
+ }"""
100
+
101
+ if perspective_array_old in content:
102
+ content = content.replace(perspective_array_old, perspective_array_new)
103
+ else:
104
+ print("Could not find perspectiveArray implementation to replace.")
105
+
106
+ with open(file_path, 'w') as f:
107
+ f.write(content)
108
+
109
+ print("Updated Projection.js")