orchestrix-yuri 2.0.0 → 2.0.2

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.
@@ -18,10 +18,7 @@ const DEFAULTS = {
18
18
  storage: path.join(os.homedir(), '.yuri', 'chat-history'),
19
19
  },
20
20
  engine: {
21
- cli_command: 'cc',
22
- fallback_command: 'claude',
23
21
  skill: 'yuri',
24
- allowed_tools: 'Read,Write,Edit,Bash,Glob,Grep',
25
22
  },
26
23
  };
27
24
 
@@ -47,7 +44,7 @@ function loadConfig() {
47
44
  ? parsed.chat_history.storage.replace('~', os.homedir())
48
45
  : DEFAULTS.chat_history.storage,
49
46
  },
50
- engine: { ...DEFAULTS.engine, ...parsed.engine },
47
+ engine: { ...DEFAULTS.engine, ...(parsed.engine || {}) },
51
48
  };
52
49
  }
53
50
 
@@ -1,14 +1,11 @@
1
1
  'use strict';
2
2
 
3
- const { execFile } = require('child_process');
4
- const { promisify } = require('util');
3
+ const { spawn } = require('child_process');
5
4
  const fs = require('fs');
6
5
  const path = require('path');
7
6
  const os = require('os');
8
7
  const yaml = require('js-yaml');
9
8
 
10
- const execFileAsync = promisify(execFile);
11
-
12
9
  const YURI_GLOBAL = path.join(os.homedir(), '.yuri');
13
10
 
14
11
  /**
@@ -69,31 +66,61 @@ function resolveProjectRoot() {
69
66
  }
70
67
 
71
68
  /**
72
- * Find the CLI command (cc or claude).
69
+ * Find the claude binary path.
70
+ * Shell aliases (like `cc`) are not available in child_process, so we
71
+ * resolve the actual binary via the user's login shell PATH.
73
72
  */
74
- function findCliCommand(engineConfig) {
75
- const { cli_command, fallback_command } = engineConfig;
76
-
77
- // Check if primary command exists via which
73
+ function findClaudeBinary() {
74
+ // Primary: resolve via user's login shell (handles all install methods)
78
75
  try {
79
- require('child_process').execSync(`which ${cli_command} 2>/dev/null`);
80
- return cli_command;
76
+ const resolved = require('child_process')
77
+ .execSync('zsh -lc "which claude" 2>/dev/null', { encoding: 'utf8' })
78
+ .trim();
79
+ if (resolved && fs.existsSync(resolved)) {
80
+ return resolved;
81
+ }
81
82
  } catch {
82
- // Try fallback
83
- try {
84
- require('child_process').execSync(`which ${fallback_command} 2>/dev/null`);
85
- return fallback_command;
86
- } catch {
87
- return cli_command; // Return primary anyway, let it fail with a clear error
83
+ // fall through
84
+ }
85
+
86
+ // Fallback: check common install locations
87
+ const candidates = [
88
+ '/usr/local/bin/claude',
89
+ '/opt/homebrew/bin/claude',
90
+ path.join(os.homedir(), '.npm-global', 'bin', 'claude'),
91
+ path.join(os.homedir(), '.local', 'bin', 'claude'),
92
+ path.join(os.homedir(), '.claude', 'bin', 'claude'),
93
+ ];
94
+
95
+ for (const candidate of candidates) {
96
+ if (fs.existsSync(candidate)) {
97
+ return candidate;
88
98
  }
89
99
  }
100
+
101
+ // Last resort: let the shell find it
102
+ return 'claude';
103
+ }
104
+
105
+ // Cache the binary path
106
+ let _claudeBinary = null;
107
+ function getClaudeBinary() {
108
+ if (!_claudeBinary) {
109
+ _claudeBinary = findClaudeBinary();
110
+ console.log(`[claude-cli] Using binary: ${_claudeBinary}`);
111
+ }
112
+ return _claudeBinary;
90
113
  }
91
114
 
92
115
  /**
93
116
  * Execute a Claude CLI call with the Yuri skill.
94
117
  *
118
+ * Uses `claude --dangerously-skip-permissions -p "prompt"` to match the user's
119
+ * `cc` alias behavior. The prompt is written to a temp file to avoid command-line
120
+ * length limits and shell escaping issues.
121
+ *
95
122
  * @param {object} opts
96
- * @param {string} opts.prompt - The composed prompt (L1 context + chat history + user message)
123
+ * @param {string} opts.prompt - The composed prompt
97
124
  * @param {string} opts.cwd - Working directory (project root)
98
125
  * @param {object} opts.engineConfig - Engine configuration from channels.yaml
99
126
  * @param {number} [opts.timeout=300000] - Timeout in ms (default 5 min)
@@ -102,35 +129,56 @@ function findCliCommand(engineConfig) {
102
129
  async function callClaude(opts) {
103
130
  const { prompt, cwd, engineConfig, timeout = 300000 } = opts;
104
131
 
105
- const cmd = findCliCommand(engineConfig);
106
- const args = [
107
- '-p', prompt,
108
- '--allowedTools', engineConfig.allowed_tools || 'Read,Write,Edit,Bash,Glob,Grep',
109
- ];
132
+ const binary = getClaudeBinary();
110
133
 
111
- const execOpts = {
112
- cwd: cwd || os.homedir(),
113
- timeout,
114
- maxBuffer: 10 * 1024 * 1024, // 10MB
115
- env: { ...process.env },
116
- };
134
+ // Write prompt to temp file to avoid command-line length limits
135
+ const tmpFile = path.join(os.tmpdir(), `yuri-prompt-${Date.now()}.txt`);
136
+ fs.writeFileSync(tmpFile, prompt);
117
137
 
118
- try {
119
- const { stdout, stderr } = await execFileAsync(cmd, args, execOpts);
138
+ return new Promise((resolve) => {
139
+ const args = [
140
+ '--dangerously-skip-permissions',
141
+ '-p',
142
+ `$(cat "${tmpFile}")`,
143
+ ];
120
144
 
121
- if (stderr && stderr.trim()) {
122
- console.error('[claude-cli] stderr:', stderr.trim().slice(0, 500));
123
- }
145
+ // Use shell to expand $(cat ...) and get proper PATH
146
+ const child = spawn('zsh', ['-lc', `"${binary}" --dangerously-skip-permissions -p "$(cat "${tmpFile}")"`], {
147
+ cwd: cwd || os.homedir(),
148
+ env: { ...process.env },
149
+ timeout,
150
+ stdio: ['ignore', 'pipe', 'pipe'],
151
+ });
124
152
 
125
- const reply = stdout.trim();
126
- return { reply, raw: stdout };
127
- } catch (err) {
128
- if (err.killed) {
129
- return { reply: ' Request timed out. The operation took too long.', raw: '' };
130
- }
131
- console.error('[claude-cli] error:', err.message);
132
- return { reply: `❌ Error: ${err.message.slice(0, 200)}`, raw: '' };
133
- }
153
+ let stdout = '';
154
+ let stderr = '';
155
+
156
+ child.stdout.on('data', (data) => { stdout += data.toString(); });
157
+ child.stderr.on('data', (data) => { stderr += data.toString(); });
158
+
159
+ child.on('close', (code) => {
160
+ // Clean up temp file
161
+ try { fs.unlinkSync(tmpFile); } catch {}
162
+
163
+ if (stderr.trim()) {
164
+ console.error('[claude-cli] stderr:', stderr.trim().slice(0, 500));
165
+ }
166
+
167
+ if (code !== 0 && !stdout.trim()) {
168
+ console.error(`[claude-cli] Process exited with code ${code}`);
169
+ resolve({ reply: `❌ Claude CLI error (exit ${code}). Check gateway logs.`, raw: '' });
170
+ return;
171
+ }
172
+
173
+ resolve({ reply: stdout.trim(), raw: stdout });
174
+ });
175
+
176
+ child.on('error', (err) => {
177
+ try { fs.unlinkSync(tmpFile); } catch {}
178
+ console.error('[claude-cli] spawn error:', err.message);
179
+ resolve({ reply: `❌ Failed to start Claude CLI: ${err.message}`, raw: '' });
180
+ });
181
+ });
134
182
  }
135
183
 
136
184
  /**
@@ -174,4 +222,4 @@ function composePrompt(userMessage, chatHistory) {
174
222
  return parts.join('\n\n---\n\n');
175
223
  }
176
224
 
177
- module.exports = { callClaude, composePrompt, loadL1Context, resolveProjectRoot, findCliCommand };
225
+ module.exports = { callClaude, composePrompt, loadL1Context, resolveProjectRoot, findClaudeBinary };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrix-yuri",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Yuri — Meta-Orchestrator for Orchestrix. Drive your entire project lifecycle with natural language.",
5
5
  "main": "lib/installer.js",
6
6
  "bin": {
@@ -23,7 +23,4 @@ chat_history:
23
23
  storage: ~/.yuri/chat-history/
24
24
 
25
25
  engine:
26
- cli_command: cc
27
- fallback_command: claude
28
26
  skill: yuri
29
- allowed_tools: "Read,Write,Edit,Bash,Glob,Grep"