@vib3code/sdk 2.0.3-canary.183c93e → 2.0.3-canary.1f10a9c

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.183c93e",
3
+ "version": "2.0.3-canary.1f10a9c",
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
  }
@@ -16,13 +16,14 @@ import { Vec4 } from '../../math/Vec4.js';
16
16
  * @param {number} radius - Hypersphere radius
17
17
  * @returns {Vec4} Point on hypersphere
18
18
  */
19
- export function projectToHypersphere(point, radius = 1) {
19
+ export function projectToHypersphere(point, radius = 1, target = null) {
20
20
  const len = point.length();
21
21
  if (len < 0.0001) {
22
22
  // Handle origin - project to north pole
23
+ if (target) return target.set(0, 0, 0, radius);
23
24
  return new Vec4(0, 0, 0, radius);
24
25
  }
25
- return point.scale(radius / len);
26
+ return point.scale(radius / len, target);
26
27
  }
27
28
 
28
29
  /**
@@ -30,9 +31,10 @@ export function projectToHypersphere(point, radius = 1) {
30
31
  * Maps all of 3D space onto the 4D hypersphere
31
32
  * @param {Vec4} point - Input point (uses x, y, z)
32
33
  * @param {number} radius - Hypersphere radius
34
+ * @param {Vec4} [target=null] - Optional target vector
33
35
  * @returns {Vec4} Point on hypersphere
34
36
  */
35
- export function stereographicToHypersphere(point, radius = 1) {
37
+ export function stereographicToHypersphere(point, radius = 1, target = null) {
36
38
  const x = point.x;
37
39
  const y = point.y;
38
40
  const z = point.z;
@@ -40,6 +42,15 @@ export function stereographicToHypersphere(point, radius = 1) {
40
42
  const sumSq = x * x + y * y + z * z;
41
43
  const denom = sumSq + 1;
42
44
 
45
+ if (target) {
46
+ return target.set(
47
+ (2 * x) / denom * radius,
48
+ (2 * y) / denom * radius,
49
+ (2 * z) / denom * radius,
50
+ (sumSq - 1) / denom * radius
51
+ );
52
+ }
53
+
43
54
  return new Vec4(
44
55
  (2 * x) / denom * radius,
45
56
  (2 * y) / denom * radius,
@@ -56,12 +67,22 @@ export function stereographicToHypersphere(point, radius = 1) {
56
67
  * @param {number} phi - Azimuth on base S² (0 to 2π)
57
68
  * @param {number} psi - Fiber angle (0 to 2π)
58
69
  * @param {number} radius - Hypersphere radius
70
+ * @param {Vec4} [target=null] - Optional target vector
59
71
  * @returns {Vec4} Point on hypersphere
60
72
  */
61
- export function hopfFibration(theta, phi, psi, radius = 1) {
73
+ export function hopfFibration(theta, phi, psi, radius = 1, target = null) {
62
74
  const cosTheta2 = Math.cos(theta / 2);
63
75
  const sinTheta2 = Math.sin(theta / 2);
64
76
 
77
+ if (target) {
78
+ return target.set(
79
+ cosTheta2 * Math.cos((phi + psi) / 2) * radius,
80
+ cosTheta2 * Math.sin((phi + psi) / 2) * radius,
81
+ sinTheta2 * Math.cos((phi - psi) / 2) * radius,
82
+ sinTheta2 * Math.sin((phi - psi) / 2) * radius
83
+ );
84
+ }
85
+
65
86
  return new Vec4(
66
87
  cosTheta2 * Math.cos((phi + psi) / 2) * radius,
67
88
  cosTheta2 * Math.sin((phi + psi) / 2) * radius,
@@ -78,8 +99,9 @@ export function hopfFibration(theta, phi, psi, radius = 1) {
78
99
  * @returns {Vec4[]} Warped vertices
79
100
  */
80
101
  export function warpRadial(vertices, radius = 1, blendFactor = 1) {
102
+ const onSphere = new Vec4();
81
103
  return vertices.map(v => {
82
- const onSphere = projectToHypersphere(v, radius);
104
+ projectToHypersphere(v, radius, onSphere);
83
105
  return v.lerp(onSphere, blendFactor);
84
106
  });
85
107
  }
@@ -93,8 +115,9 @@ export function warpRadial(vertices, radius = 1, blendFactor = 1) {
93
115
  * @returns {Vec4[]} Warped vertices
94
116
  */
95
117
  export function warpStereographic(vertices, radius = 1, scale = 1) {
118
+ const scaled = new Vec4();
96
119
  return vertices.map(v => {
97
- const scaled = v.scale(scale);
120
+ v.scale(scale, scaled);
98
121
  return stereographicToHypersphere(scaled, radius);
99
122
  });
100
123
  }
@@ -146,25 +169,31 @@ export function warpHypersphereCore(geometry, options = {}) {
146
169
  twist = 1
147
170
  } = options;
148
171
 
149
- let warpedVertices;
150
-
151
- // Pre-scale vertices
152
- const scaledVertices = geometry.vertices.map(v => v.scale(scale));
153
-
154
- switch (method) {
155
- case 'stereographic':
156
- warpedVertices = warpStereographic(scaledVertices, radius, 1);
157
- break;
158
-
159
- case 'hopf':
160
- warpedVertices = warpHopf(scaledVertices, radius, twist);
161
- break;
172
+ const temp = new Vec4();
173
+ const warpedVertices = geometry.vertices.map(v => {
174
+ // Combined scaling and warping to minimize allocations
175
+ const result = v.scale(scale);
176
+
177
+ if (method === 'stereographic') {
178
+ stereographicToHypersphere(result, radius, result);
179
+ } else if (method === 'hopf') {
180
+ const r = result.length();
181
+ if (r < 0.0001) {
182
+ result.set(0, 0, 0, radius);
183
+ } else {
184
+ const theta = Math.acos(result.z / r);
185
+ const phi = Math.atan2(result.y, result.x);
186
+ const psi = result.w * twist + phi * 0.5;
187
+ hopfFibration(theta, phi, psi, radius, result);
188
+ }
189
+ } else {
190
+ // Radial (default)
191
+ projectToHypersphere(result, radius, temp);
192
+ result.lerp(temp, blend, result);
193
+ }
162
194
 
163
- case 'radial':
164
- default:
165
- warpedVertices = warpRadial(scaledVertices, radius, blend);
166
- break;
167
- }
195
+ return result;
196
+ });
168
197
 
169
198
  return {
170
199
  ...geometry,