orchestrix-yuri 2.0.0 → 2.0.1

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,59 @@ function resolveProjectRoot() {
69
66
  }
70
67
 
71
68
  /**
72
- * Find the CLI command (cc or claude).
69
+ * Find the claude binary path.
70
+ * Checks common locations since shell aliases (like `cc`) are not available
71
+ * in child_process.execFile.
73
72
  */
74
- function findCliCommand(engineConfig) {
75
- const { cli_command, fallback_command } = engineConfig;
73
+ function findClaudeBinary() {
74
+ const candidates = [
75
+ path.join(os.homedir(), '.npm-global', 'bin', 'claude'),
76
+ path.join(os.homedir(), '.local', 'bin', 'claude'),
77
+ '/usr/local/bin/claude',
78
+ '/opt/homebrew/bin/claude',
79
+ ];
76
80
 
77
- // Check if primary command exists via which
81
+ // Also try resolving via shell (handles PATH correctly)
78
82
  try {
79
- require('child_process').execSync(`which ${cli_command} 2>/dev/null`);
80
- return cli_command;
83
+ const resolved = require('child_process')
84
+ .execSync('zsh -lc "which claude" 2>/dev/null', { encoding: 'utf8' })
85
+ .trim();
86
+ if (resolved && fs.existsSync(resolved)) {
87
+ return resolved;
88
+ }
81
89
  } 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
90
+ // fall through to candidates
91
+ }
92
+
93
+ for (const candidate of candidates) {
94
+ if (fs.existsSync(candidate)) {
95
+ return candidate;
88
96
  }
89
97
  }
98
+
99
+ // Last resort: assume it's in PATH
100
+ return 'claude';
101
+ }
102
+
103
+ // Cache the binary path
104
+ let _claudeBinary = null;
105
+ function getClaudeBinary() {
106
+ if (!_claudeBinary) {
107
+ _claudeBinary = findClaudeBinary();
108
+ console.log(`[claude-cli] Using binary: ${_claudeBinary}`);
109
+ }
110
+ return _claudeBinary;
90
111
  }
91
112
 
92
113
  /**
93
114
  * Execute a Claude CLI call with the Yuri skill.
94
115
  *
116
+ * Uses `claude --dangerously-skip-permissions -p "prompt"` to match the user's
117
+ * `cc` alias behavior. The prompt is written to a temp file to avoid command-line
118
+ * length limits and shell escaping issues.
119
+ *
95
120
  * @param {object} opts
96
- * @param {string} opts.prompt - The composed prompt (L1 context + chat history + user message)
121
+ * @param {string} opts.prompt - The composed prompt
97
122
  * @param {string} opts.cwd - Working directory (project root)
98
123
  * @param {object} opts.engineConfig - Engine configuration from channels.yaml
99
124
  * @param {number} [opts.timeout=300000] - Timeout in ms (default 5 min)
@@ -102,35 +127,56 @@ function findCliCommand(engineConfig) {
102
127
  async function callClaude(opts) {
103
128
  const { prompt, cwd, engineConfig, timeout = 300000 } = opts;
104
129
 
105
- const cmd = findCliCommand(engineConfig);
106
- const args = [
107
- '-p', prompt,
108
- '--allowedTools', engineConfig.allowed_tools || 'Read,Write,Edit,Bash,Glob,Grep',
109
- ];
130
+ const binary = getClaudeBinary();
110
131
 
111
- const execOpts = {
112
- cwd: cwd || os.homedir(),
113
- timeout,
114
- maxBuffer: 10 * 1024 * 1024, // 10MB
115
- env: { ...process.env },
116
- };
132
+ // Write prompt to temp file to avoid command-line length limits
133
+ const tmpFile = path.join(os.tmpdir(), `yuri-prompt-${Date.now()}.txt`);
134
+ fs.writeFileSync(tmpFile, prompt);
117
135
 
118
- try {
119
- const { stdout, stderr } = await execFileAsync(cmd, args, execOpts);
136
+ return new Promise((resolve) => {
137
+ const args = [
138
+ '--dangerously-skip-permissions',
139
+ '-p',
140
+ `$(cat "${tmpFile}")`,
141
+ ];
120
142
 
121
- if (stderr && stderr.trim()) {
122
- console.error('[claude-cli] stderr:', stderr.trim().slice(0, 500));
123
- }
143
+ // Use shell to expand $(cat ...) and get proper PATH
144
+ const child = spawn('zsh', ['-lc', `"${binary}" --dangerously-skip-permissions -p "$(cat "${tmpFile}")"`], {
145
+ cwd: cwd || os.homedir(),
146
+ env: { ...process.env },
147
+ timeout,
148
+ stdio: ['ignore', 'pipe', 'pipe'],
149
+ });
124
150
 
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
- }
151
+ let stdout = '';
152
+ let stderr = '';
153
+
154
+ child.stdout.on('data', (data) => { stdout += data.toString(); });
155
+ child.stderr.on('data', (data) => { stderr += data.toString(); });
156
+
157
+ child.on('close', (code) => {
158
+ // Clean up temp file
159
+ try { fs.unlinkSync(tmpFile); } catch {}
160
+
161
+ if (stderr.trim()) {
162
+ console.error('[claude-cli] stderr:', stderr.trim().slice(0, 500));
163
+ }
164
+
165
+ if (code !== 0 && !stdout.trim()) {
166
+ console.error(`[claude-cli] Process exited with code ${code}`);
167
+ resolve({ reply: `❌ Claude CLI error (exit ${code}). Check gateway logs.`, raw: '' });
168
+ return;
169
+ }
170
+
171
+ resolve({ reply: stdout.trim(), raw: stdout });
172
+ });
173
+
174
+ child.on('error', (err) => {
175
+ try { fs.unlinkSync(tmpFile); } catch {}
176
+ console.error('[claude-cli] spawn error:', err.message);
177
+ resolve({ reply: `❌ Failed to start Claude CLI: ${err.message}`, raw: '' });
178
+ });
179
+ });
134
180
  }
135
181
 
136
182
  /**
@@ -174,4 +220,4 @@ function composePrompt(userMessage, chatHistory) {
174
220
  return parts.join('\n\n---\n\n');
175
221
  }
176
222
 
177
- module.exports = { callClaude, composePrompt, loadL1Context, resolveProjectRoot, findCliCommand };
223
+ 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.1",
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"