@xortex/xcode 3.0.5 → 3.0.8

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/bin/xcode CHANGED
@@ -1,164 +1,165 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * XCode CLI Entry Point
4
- *
5
- * This is the executable that gets installed when users run:
6
- * - npm install -g @xortex/xcode
7
- * - Or use the curl installer
4
+ *
5
+ * This codebase is built for Bun (bun.sh). If Bun is not installed,
6
+ * this script installs it automatically, then hands off to Bun.
7
+ *
8
+ * Installed via: npm install -g @xortex/xcode
8
9
  */
9
10
 
11
+ const { execSync, spawnSync } = require('child_process');
10
12
  const { spawn } = require('child_process');
11
13
  const path = require('path');
12
14
  const fs = require('fs');
15
+ const os = require('os');
13
16
 
14
- // Get the project root (where this script is located)
15
17
  const projectRoot = path.resolve(__dirname, '..');
16
18
  const mainScript = path.join(projectRoot, 'entrypoints', 'cli.tsx');
17
- const bunBundleHook = path.join(projectRoot, 'bun-bundle-hook.js');
18
19
 
19
- // Check if bun is available
20
- function getRuntime() {
20
+ // ── helpers ──────────────────────────────────────────────────────────────────
21
+
22
+ function tryExec(cmd) {
23
+ try { execSync(cmd, { stdio: 'ignore' }); return true; } catch { return false; }
24
+ }
25
+
26
+ function bunBinPath() {
27
+ // Common Bun install locations
28
+ const candidates = [
29
+ path.join(os.homedir(), '.bun', 'bin', 'bun'),
30
+ '/usr/local/bin/bun',
31
+ '/usr/bin/bun',
32
+ ];
33
+ for (const c of candidates) {
34
+ if (fs.existsSync(c)) return c;
35
+ }
36
+ // Try PATH
21
37
  try {
22
- require('child_process').execSync('bun --version', { stdio: 'ignore' });
23
- return 'bun';
38
+ return execSync('which bun', { encoding: 'utf8' }).trim();
24
39
  } catch {
25
- try {
26
- require('child_process').execSync('node --version', { stdio: 'ignore' });
27
- return 'node';
28
- } catch {
29
- console.error('❌ Error: Neither Bun nor Node.js is installed.');
30
- console.error('');
31
- console.error('Please install one of the following:');
32
- console.error(' Bun (recommended): curl -fsSL https://bun.sh/install | bash');
33
- console.error(' • Node.js: https://nodejs.org/');
34
- console.error('');
35
- process.exit(1);
40
+ return null;
41
+ }
42
+ }
43
+
44
+ function installBun() {
45
+ console.log('');
46
+ console.log('📦 XCode requires Bun runtime. Installing Bun now...');
47
+ console.log(' (This is a one-time setup)');
48
+ console.log('');
49
+
50
+ const platform = os.platform();
51
+
52
+ try {
53
+ if (platform === 'win32') {
54
+ execSync('powershell -c "irm bun.sh/install.ps1 | iex"', { stdio: 'inherit' });
55
+ } else {
56
+ // curl or wget
57
+ if (tryExec('which curl')) {
58
+ execSync('curl -fsSL https://bun.sh/install | bash', {
59
+ stdio: 'inherit',
60
+ shell: '/bin/bash',
61
+ });
62
+ } else if (tryExec('which wget')) {
63
+ execSync('wget -qO- https://bun.sh/install | bash', {
64
+ stdio: 'inherit',
65
+ shell: '/bin/bash',
66
+ });
67
+ } else {
68
+ throw new Error('Neither curl nor wget is available');
69
+ }
36
70
  }
71
+
72
+ console.log('');
73
+ console.log('✅ Bun installed successfully!');
74
+ console.log('');
75
+
76
+ // Re-source PATH
77
+ const bunBin = bunBinPath();
78
+ if (bunBin) return bunBin;
79
+
80
+ // Add ~/.bun/bin to PATH for this process
81
+ const bunBinDir = path.join(os.homedir(), '.bun', 'bin');
82
+ process.env.PATH = `${bunBinDir}${path.delimiter}${process.env.PATH}`;
83
+ return path.join(bunBinDir, 'bun');
84
+
85
+ } catch (err) {
86
+ console.error('');
87
+ console.error('❌ Could not auto-install Bun. Please install it manually:');
88
+ console.error('');
89
+ console.error(' curl -fsSL https://bun.sh/install | bash');
90
+ console.error('');
91
+ console.error(' Then run: xcode');
92
+ console.error('');
93
+ process.exit(1);
37
94
  }
38
95
  }
39
96
 
40
- // Set up environment
97
+ // ── CLI flags ─────────────────────────────────────────────────────────────────
98
+
41
99
  process.env.XCODE_CLI = 'true';
42
100
  process.env.NODE_ENV = process.env.NODE_ENV || 'production';
43
101
 
44
- // Handle special commands
45
102
  const args = process.argv.slice(2);
46
- const command = args[0];
47
103
 
48
- if (command === '--version' || command === '-v') {
49
- console.log('xcode v3.0.5 — AI coding assistant with XMem memory');
104
+ if (args[0] === '--version' || args[0] === '-v' || args[0] === '-V') {
105
+ console.log('xcode v3.0.8 — AI coding assistant with XMem memory');
50
106
  process.exit(0);
51
107
  }
52
108
 
53
- if (command === '--help' || command === '-h') {
109
+ if (args[0] === '--help' || args[0] === '-h') {
54
110
  console.log(`
55
111
  XCode - AI-powered coding assistant with XMem memory
56
112
 
57
- Usage: xcode [options] [command]
113
+ Usage: xcode [options]
58
114
 
59
115
  Options:
60
- -v, --version Show version number
61
- -h, --help Show help
62
- --model <model> Start with specific model (e.g., gemini-2.5-pro, kimi-k2.5)
116
+ -v, --version Show version
117
+ -h, --help Show this help
118
+ --model <name> Choose model: gemini-2.5-pro, kimi-k2.5, deepseek-v3.2
119
+ --openrouter Force OpenRouter as provider
63
120
 
64
- Commands:
65
- (none) Start interactive session
66
-
67
- Environment Variables:
68
- OPENROUTER_API_KEY OpenRouter API key (optional, has default)
69
- XMEM_API_URL XMem memory server URL (default: http://localhost:8000)
121
+ Environment:
70
122
  ANTHROPIC_API_KEY Anthropic API key (for Claude models)
71
-
72
- Examples:
73
- xcode # Start with default model
74
- xcode --model gemini-2.5-pro # Use Gemini via OpenRouter
75
- xcode --model kimi-k2.5 # Use Kimi via OpenRouter
123
+ OPENROUTER_API_KEY OpenRouter API key (optional, default provided)
124
+ XMEM_API_URL XMem server URL (default: http://localhost:8000)
76
125
  `);
77
126
  process.exit(0);
78
127
  }
79
128
 
80
- // Check for model argument
81
- const modelIndex = args.indexOf('--model');
82
- if (modelIndex !== -1 && args[modelIndex + 1]) {
83
- const model = args[modelIndex + 1];
129
+ // Model / provider flags
130
+ const modelIdx = args.indexOf('--model');
131
+ if (modelIdx !== -1 && args[modelIdx + 1]) {
132
+ const model = args[modelIdx + 1];
84
133
  process.env.ANTHROPIC_MODEL = model;
85
-
86
- // If using OpenRouter models, auto-enable OpenRouter
87
- const openRouterModels = ['kimi', 'deepseek', 'gemini', 'moonshot', 'google/gemini'];
88
- if (openRouterModels.some(m => model.toLowerCase().includes(m.toLowerCase()))) {
134
+ const orModels = ['kimi', 'deepseek', 'gemini', 'moonshot', 'google/'];
135
+ if (orModels.some(m => model.toLowerCase().includes(m.toLowerCase()))) {
89
136
  process.env.CLAUDE_CODE_USE_OPENROUTER = 'true';
90
137
  }
91
138
  }
92
-
93
- // Check for explicit OpenRouter flag
94
139
  if (args.includes('--openrouter')) {
95
140
  process.env.CLAUDE_CODE_USE_OPENROUTER = 'true';
96
141
  }
97
142
 
98
- // Run the main application
99
- const runtime = getRuntime();
100
-
101
- if (runtime === 'bun') {
102
- // Use Bun - it handles TypeScript natively
103
- const child = spawn('bun', ['run', mainScript, ...args], {
104
- cwd: projectRoot,
105
- stdio: 'inherit',
106
- env: process.env,
107
- });
108
-
109
- child.on('exit', (code) => {
110
- process.exit(code || 0);
111
- });
112
- } else {
113
- // Use Node with ts-node or tsx
114
- // First check if tsx is installed globally or locally
115
- let tsxPath;
116
- try {
117
- // Try local tsx first
118
- tsxPath = require.resolve('tsx/dist/cli.mjs', { paths: [projectRoot] });
119
- } catch {
120
- try {
121
- // Try global tsx
122
- tsxPath = require.resolve('tsx/dist/cli.mjs');
123
- } catch {
124
- // Fallback to npx tsx
125
- tsxPath = null;
126
- }
127
- }
128
-
129
- let child;
130
- const hasHook = fs.existsSync(bunBundleHook);
131
-
132
- if (tsxPath) {
133
- // Use direct tsx path with bun:bundle shim
134
- const nodeArgs = hasHook ? ['--require', bunBundleHook, tsxPath, mainScript] : [tsxPath, mainScript];
135
- child = spawn('node', [...nodeArgs, ...args], {
136
- cwd: projectRoot,
137
- stdio: 'inherit',
138
- env: process.env,
139
- });
143
+ // ── find / install Bun, then run ──────────────────────────────────────────────
144
+
145
+ let bun = bunBinPath();
146
+ if (!bun) {
147
+ bun = installBun();
148
+ }
149
+
150
+ const child = spawn(bun, ['run', mainScript, ...args], {
151
+ cwd: projectRoot,
152
+ stdio: 'inherit',
153
+ env: process.env,
154
+ });
155
+
156
+ child.on('exit', code => process.exit(code ?? 0));
157
+ child.on('error', err => {
158
+ if (err.code === 'ENOENT') {
159
+ console.error('\n❌ Bun not found at: ' + bun);
160
+ console.error('Please run: curl -fsSL https://bun.sh/install | bash && xcode');
140
161
  } else {
141
- // Fallback to npx
142
- const npxArgs = hasHook ? ['--node-options', `--require ${bunBundleHook}`, 'tsx'] : ['tsx'];
143
- child = spawn('npx', [...npxArgs, mainScript, ...args], {
144
- cwd: projectRoot,
145
- stdio: 'inherit',
146
- env: process.env,
147
- });
162
+ console.error('\n❌ Failed to start XCode:', err.message);
148
163
  }
149
-
150
- child.on('exit', (code) => {
151
- process.exit(code || 0);
152
- });
153
-
154
- child.on('error', (err) => {
155
- console.error('❌ Failed to start XCode:', err.message);
156
- console.error('');
157
- console.error('Please ensure tsx is installed:');
158
- console.error(' npm install -g tsx');
159
- console.error('');
160
- console.error('Or use Bun instead:');
161
- console.error(' curl -fsSL https://bun.sh/install | bash');
162
- process.exit(1);
163
- });
164
- }
164
+ process.exit(1);
165
+ });
@@ -1,25 +1,46 @@
1
1
  /**
2
- * Node.js require hook to shim bun:bundle module
3
- * This must be loaded before any other modules via --require flag
2
+ * Node.js require hook to shim 'bun:bundle' for tsx/Node.js compatibility.
3
+ *
4
+ * Strategy:
5
+ * 1. Intercept Module._resolveFilename so 'bun:bundle' resolves to a
6
+ * sentinel path (not a real file) instead of throwing MODULE_NOT_FOUND.
7
+ * 2. Pre-populate Module._cache with the shim exports under that sentinel
8
+ * path, so Module._load finds it in cache and never tries to read a file.
9
+ *
10
+ * This works even when tsx wraps _resolveFilename after this hook is installed,
11
+ * because tsx calls the original _resolveFilename as a fallback.
12
+ *
13
+ * Loaded via --require before tsx.
4
14
  */
15
+ if (!global.__bunBundleHookInstalled) {
16
+ global.__bunBundleHookInstalled = true;
5
17
 
6
- const Module = require('module');
7
- const path = require('path');
18
+ const Module = require('module');
8
19
 
9
- const originalResolveFilename = Module._resolveFilename;
20
+ // Use a sentinel that looks like an absolute path so tsx / Node accept it.
21
+ // It never needs to exist on disk because we pre-populate the cache.
22
+ const SENTINEL = '\0bun:bundle';
10
23
 
11
- // Store the project root
12
- const projectRoot = path.resolve(__dirname);
13
-
14
- Module._resolveFilename = function (request, parent, isMain, options) {
15
- // Intercept bun:bundle imports
16
- if (request === 'bun:bundle') {
17
- // Redirect to our compatibility module
18
- const compatPath = path.join(projectRoot, 'utils', 'bunBundleCompat.js');
19
- return compatPath;
24
+ // feature() shim: in Node.js all DCE gates are off by default.
25
+ // Override via CLAUDE_CODE_FEATURE_<NAME>=1 if needed.
26
+ function feature(name) {
27
+ const key = 'CLAUDE_CODE_FEATURE_' + String(name).replace(/[^A-Za-z0-9]/g, '_').toUpperCase();
28
+ const v = process.env[key];
29
+ return v === '1' || v === 'true';
20
30
  }
21
31
 
22
- return originalResolveFilename.call(this, request, parent, isMain, options);
23
- };
32
+ // Populate the module cache before tsx can intercept _load
33
+ const shimModule = new Module(SENTINEL, null);
34
+ shimModule.id = SENTINEL;
35
+ shimModule.filename = SENTINEL;
36
+ shimModule.loaded = true;
37
+ shimModule.exports = { feature };
38
+ Module._cache[SENTINEL] = shimModule;
24
39
 
25
- console.log('[XCode] Bun compatibility hook loaded');
40
+ // Intercept _resolveFilename so 'bun:bundle' maps to our sentinel
41
+ const originalResolve = Module._resolveFilename;
42
+ Module._resolveFilename = function (request, parent, isMain, options) {
43
+ if (request === 'bun:bundle') return SENTINEL;
44
+ return originalResolve.call(this, request, parent, isMain, options);
45
+ };
46
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Shim for Bun's `bun:bundle` module, used in Node.js via tsx path aliases.
3
+ * Provides a no-op `feature()` function since all Bun DCE gates default to false
4
+ * in Node.js (the guarded code blocks have their own Node.js fallbacks).
5
+ */
6
+
7
+ export function feature(name: string): boolean {
8
+ // Allow opt-in via env var if ever needed
9
+ const key = `CLAUDE_CODE_FEATURE_${String(name).replace(/[^A-Za-z0-9]/g, '_').toUpperCase()}`;
10
+ const v = process.env[key];
11
+ return v === '1' || v === 'true';
12
+ }
@@ -39,7 +39,7 @@ async function main(): Promise<void> {
39
39
  if (args.length === 1 && (args[0] === '--version' || args[0] === '-v' || args[0] === '-V')) {
40
40
  // MACRO.VERSION is inlined at build time
41
41
  // biome-ignore lint/suspicious/noConsole:: intentional console output
42
- console.log(`xcode v3.0.5 — AI coding assistant with XMem memory`);
42
+ console.log(`xcode v3.0.8 — AI coding assistant with XMem memory`);
43
43
  return;
44
44
  }
45
45
 
package/macro.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  // Build-time macros - stubbed for development
2
2
 
3
3
  export const MACRO = {
4
- VERSION: '3.0.5',
4
+ VERSION: '3.0.8',
5
5
  VERSION_CHANGELOG: '',
6
6
  NATIVE_PACKAGE_URL: '@anthropic-ai/claude-code-native',
7
7
  CLI_BIN_PATH: './cli.tsx',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xortex/xcode",
3
- "version": "3.0.5",
3
+ "version": "3.0.8",
4
4
  "description": "XCode - AI-powered coding assistant with XMem long-term memory. Supports Claude, Gemini, Kimi, DeepSeek via OpenRouter.",
5
5
  "main": "main.tsx",
6
6
  "bin": {
@@ -16,7 +16,8 @@
16
16
  "main.tsx",
17
17
  "macro.ts",
18
18
  "bun-bundle-hook.js",
19
- "utils/bunBundleCompat.ts",
19
+ "bun-bundle-shim.ts",
20
+ "tsconfig.json",
20
21
  "src/",
21
22
  "constants/",
22
23
  "utils/",
@@ -129,6 +130,7 @@
129
130
  "terminal-link": "^5.0.0",
130
131
  "terminal-size": "^4.0.1",
131
132
  "tree-kill": "^1.2.2",
133
+ "tsx": "^4.19.4",
132
134
  "usehooks-ts": "^3.1.1",
133
135
  "uuid": "^13.0.0",
134
136
  "widest-line": "^6.0.0",
@@ -143,6 +145,6 @@
143
145
  "@types/lodash-es": "^4.17.12",
144
146
  "@types/node": "^25.5.0",
145
147
  "@types/react": "^19.2.14",
146
- "tsx": "^4.19.0"
148
+ "esbuild": "^0.28.0"
147
149
  }
148
150
  }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "esnext",
4
+ "module": "esnext",
5
+ "moduleResolution": "bundler",
6
+ "esModuleInterop": true,
7
+ "strict": false,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "baseUrl": ".",
11
+ "paths": {
12
+ "src/*": ["./*"],
13
+ "bun:bundle": ["./bun-bundle-shim"]
14
+ }
15
+ }
16
+ }
@@ -1,44 +0,0 @@
1
- /**
2
- * Compatibility layer for bun:bundle module
3
- * Provides feature() function for both Bun and Node.js runtimes
4
- */
5
-
6
- // Detect if running in Bun
7
- // eslint-disable-next-line custom-rules/no-top-level-side-effects
8
- declare const Bun: { version: string } | undefined;
9
- // eslint-disable-next-line custom-rules/no-top-level-side-effects
10
- const isBunRuntime = typeof Bun !== 'undefined' && typeof Bun.version === 'string';
11
-
12
- /**
13
- * Check if a feature flag is enabled.
14
- * In Bun, this uses the native bun:bundle module.
15
- * In Node.js, this checks environment variables.
16
- */
17
- export function feature(name: string): boolean {
18
- if (isBunRuntime) {
19
- try {
20
- // Dynamic require for Bun
21
- // eslint-disable-next-line @typescript-eslint/no-require-imports
22
- const bunBundle = require('bun:bundle');
23
- return bunBundle.feature(name);
24
- } catch {
25
- return false;
26
- }
27
- }
28
-
29
- // In Node.js, check for equivalent environment variable
30
- // Features are typically controlled via CLAUDE_CODE_* env vars
31
- const envVarName = name.replace(/([A-Z])/g, '_$1').toUpperCase();
32
- const envValue = process.env[`CLAUDE_CODE_${envVarName}`] || process.env[envVarName];
33
-
34
- if (envValue === '1' || envValue === 'true') {
35
- return true;
36
- }
37
-
38
- // Default feature flags for Node.js
39
- const defaultEnabled: Record<string, boolean> = {
40
- // Add any features that should be enabled by default
41
- };
42
-
43
- return defaultEnabled[name] ?? false;
44
- }