@vib3code/sdk 2.0.3-canary.bd4d4a5 → 2.0.3-canary.c544441

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.bd4d4a5",
3
+ "version": "2.0.3-canary.c544441",
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",
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
  }
@@ -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
  /**
@@ -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")