orquesta-cli 0.2.26 → 0.2.28
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/dist/cli.js +68 -15
- package/dist/orquesta/hook-init.d.ts +5 -0
- package/dist/orquesta/hook-init.js +108 -16
- package/dist/setup/first-run-setup.js +16 -1
- package/dist/ui/components/PlanExecuteApp.js +16 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -31,6 +31,23 @@ import { PROVIDERS } from './core/config/providers.js';
|
|
|
31
31
|
const require = createRequire(import.meta.url);
|
|
32
32
|
const packageJson = require('../package.json');
|
|
33
33
|
const program = new Command();
|
|
34
|
+
async function resolveHookToken(explicitToken) {
|
|
35
|
+
await configManager.initialize();
|
|
36
|
+
const saved = configManager.getOrquestaConfig();
|
|
37
|
+
const token = explicitToken || saved?.token;
|
|
38
|
+
if (!token) {
|
|
39
|
+
console.error(chalk.red('Error: not connected to Orquesta.'));
|
|
40
|
+
console.error('Run ' + chalk.cyan('orquesta --login') + ' first, or pass ' + chalk.cyan('--token oat_…') + '.');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
return { token: token, savedProjectId: saved?.projectId };
|
|
44
|
+
}
|
|
45
|
+
async function runHookEnable(opts) {
|
|
46
|
+
const { token, savedProjectId } = await resolveHookToken(opts.token);
|
|
47
|
+
const { initHooks } = await import('./orquesta/hook-init.js');
|
|
48
|
+
const preferredProjectId = opts.project || (opts.ignoreSaved || opts.token ? undefined : savedProjectId);
|
|
49
|
+
await initHooks(token, undefined, preferredProjectId);
|
|
50
|
+
}
|
|
34
51
|
program
|
|
35
52
|
.name('orquesta')
|
|
36
53
|
.description('Orquesta CLI - AI-powered coding assistant with local LLM support')
|
|
@@ -53,7 +70,8 @@ program
|
|
|
53
70
|
.option('--sync', 'Sync configurations with Orquesta and exit')
|
|
54
71
|
.option('--scan', 'Scan for available LLM providers (env vars + local ports)')
|
|
55
72
|
.option('--add-provider <providerId>', 'Add a specific provider by ID (e.g., openai, anthropic, ollama)')
|
|
56
|
-
.option('--init', '
|
|
73
|
+
.option('--init', 'Enable the Claude Code hook in this directory (reuses your login; or pass --token)')
|
|
74
|
+
.option('--disable-hook', 'Disable the Claude Code hook in this directory')
|
|
57
75
|
.action(async (options) => {
|
|
58
76
|
if (options.appendSystemPrompt) {
|
|
59
77
|
setAppendedSystemPrompt(options.appendSystemPrompt);
|
|
@@ -63,17 +81,12 @@ program
|
|
|
63
81
|
return;
|
|
64
82
|
}
|
|
65
83
|
if (options.init) {
|
|
66
|
-
await
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
const { initHooks } = await import('./orquesta/hook-init.js');
|
|
75
|
-
const preferredProjectId = options.token ? undefined : saved?.projectId;
|
|
76
|
-
await initHooks(token, undefined, preferredProjectId);
|
|
84
|
+
await runHookEnable({ token: options.token, project: options.project });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (options.disableHook) {
|
|
88
|
+
const { disableHooks } = await import('./orquesta/hook-init.js');
|
|
89
|
+
disableHooks();
|
|
77
90
|
return;
|
|
78
91
|
}
|
|
79
92
|
await configManager.initialize();
|
|
@@ -299,6 +312,42 @@ program
|
|
|
299
312
|
}
|
|
300
313
|
}
|
|
301
314
|
});
|
|
315
|
+
const hook = program
|
|
316
|
+
.command('hook')
|
|
317
|
+
.description('Manage the Claude Code hook for the current directory');
|
|
318
|
+
hook
|
|
319
|
+
.command('enable')
|
|
320
|
+
.description('Enable the hook here (asks which project if ambiguous)')
|
|
321
|
+
.option('--project <projectId>', 'Project this directory should stream into')
|
|
322
|
+
.option('--token <token>', 'Use a specific token instead of your saved login')
|
|
323
|
+
.action(async (opts) => {
|
|
324
|
+
await runHookEnable({ token: opts.token, project: opts.project });
|
|
325
|
+
process.exit(0);
|
|
326
|
+
});
|
|
327
|
+
hook
|
|
328
|
+
.command('switch')
|
|
329
|
+
.description('Change which project this directory streams into')
|
|
330
|
+
.option('--project <projectId>', 'Project to switch to (skips the prompt)')
|
|
331
|
+
.action(async (opts) => {
|
|
332
|
+
await runHookEnable({ project: opts.project, ignoreSaved: true });
|
|
333
|
+
process.exit(0);
|
|
334
|
+
});
|
|
335
|
+
hook
|
|
336
|
+
.command('disable')
|
|
337
|
+
.description('Disable the hook in this directory')
|
|
338
|
+
.action(async () => {
|
|
339
|
+
const { disableHooks } = await import('./orquesta/hook-init.js');
|
|
340
|
+
disableHooks();
|
|
341
|
+
process.exit(0);
|
|
342
|
+
});
|
|
343
|
+
hook
|
|
344
|
+
.command('status')
|
|
345
|
+
.description('Show the hook + target project for this directory')
|
|
346
|
+
.action(async () => {
|
|
347
|
+
await configManager.initialize();
|
|
348
|
+
showConnectionStatus();
|
|
349
|
+
process.exit(0);
|
|
350
|
+
});
|
|
302
351
|
program.showHelpAfterError(false);
|
|
303
352
|
program.configureOutput({
|
|
304
353
|
outputError: (str, write) => {
|
|
@@ -312,12 +361,16 @@ program.configureOutput({
|
|
|
312
361
|
});
|
|
313
362
|
program.on('command:*', () => {
|
|
314
363
|
console.error(chalk.red('⚠️ Unknown command.'));
|
|
315
|
-
console.log(chalk.white('Usage: orquesta [options]
|
|
364
|
+
console.log(chalk.white('Usage: orquesta [options] | orquesta hook <enable|switch|disable|status>\n'));
|
|
316
365
|
console.log(chalk.white(' -p, --print <prompt> Execute a prompt and exit\n'));
|
|
317
366
|
console.log(chalk.white(' --token <token> Connect to Orquesta dashboard\n'));
|
|
318
367
|
console.log(chalk.white(' --project <id> Select project when connecting\n'));
|
|
319
|
-
console.log(chalk.white(' --switch-project [id] Switch to a different project\n'));
|
|
320
|
-
console.log(chalk.white(' --status Show
|
|
368
|
+
console.log(chalk.white(' --switch-project [id] Switch the CLI to a different project\n'));
|
|
369
|
+
console.log(chalk.white(' --status Show connection + hook status\n'));
|
|
370
|
+
console.log(chalk.white(' hook enable Enable the Claude Code hook here\n'));
|
|
371
|
+
console.log(chalk.white(' hook switch Move this directory to another project\n'));
|
|
372
|
+
console.log(chalk.white(' hook disable Disable the Claude Code hook here\n'));
|
|
373
|
+
console.log(chalk.white(' hook status Show the hook + target project here\n'));
|
|
321
374
|
console.log(chalk.white(' --sync Sync configurations with Orquesta\n'));
|
|
322
375
|
console.log(chalk.white(' --disconnect Disconnect from Orquesta\n'));
|
|
323
376
|
console.log(chalk.white(' --scan Scan for available LLM providers\n'));
|
|
@@ -7,4 +7,9 @@ export declare function writeHookFiles(opts: {
|
|
|
7
7
|
agentBin?: string;
|
|
8
8
|
quiet?: boolean;
|
|
9
9
|
}): boolean;
|
|
10
|
+
export declare function readHookConfig(cwd?: string): {
|
|
11
|
+
projectId: string;
|
|
12
|
+
apiUrl?: string;
|
|
13
|
+
} | null;
|
|
14
|
+
export declare function disableHooks(cwd?: string): void;
|
|
10
15
|
//# sourceMappingURL=hook-init.d.ts.map
|
|
@@ -31,12 +31,47 @@ function resolveAgentBin() {
|
|
|
31
31
|
console.warn(' \x1b[33m npm install -g orquesta-agent\x1b[0m\n');
|
|
32
32
|
return 'orquesta-agent';
|
|
33
33
|
}
|
|
34
|
+
async function resolveTargetProject(projects, preferredProjectId) {
|
|
35
|
+
if (preferredProjectId) {
|
|
36
|
+
const match = projects.find((p) => p.id === preferredProjectId);
|
|
37
|
+
if (match) {
|
|
38
|
+
console.log(` Project: ${match.name}\n`);
|
|
39
|
+
return match;
|
|
40
|
+
}
|
|
41
|
+
console.warn(` \x1b[33m⚠ Your saved project isn't visible to this token — pick one below.\x1b[0m\n`);
|
|
42
|
+
}
|
|
43
|
+
if (projects.length === 1) {
|
|
44
|
+
console.log(` Project: ${projects[0].name}\n`);
|
|
45
|
+
return projects[0];
|
|
46
|
+
}
|
|
47
|
+
if (!process.stdin.isTTY) {
|
|
48
|
+
console.error(` Error: this token can reach ${projects.length} projects and none was specified.\n` +
|
|
49
|
+
` Re-run with: orquesta --init --project <projectId>`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
console.log(' Which project should this directory report to?\n');
|
|
53
|
+
projects.forEach((p, i) => console.log(` ${i + 1}. ${p.name}`));
|
|
54
|
+
console.log();
|
|
55
|
+
const readline = await import('readline');
|
|
56
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
57
|
+
const answer = await new Promise((resolve) => {
|
|
58
|
+
rl.question(` Enter number (1-${projects.length}): `, (a) => { rl.close(); resolve(a.trim()); });
|
|
59
|
+
});
|
|
60
|
+
const num = parseInt(answer, 10);
|
|
61
|
+
const pick = (!isNaN(num) && num >= 1 && num <= projects.length) ? projects[num - 1] : null;
|
|
62
|
+
if (!pick) {
|
|
63
|
+
console.error(' Invalid selection.');
|
|
64
|
+
process.exit(1);
|
|
65
|
+
throw new Error();
|
|
66
|
+
}
|
|
67
|
+
console.log(` Project: ${pick.name}\n`);
|
|
68
|
+
return pick;
|
|
69
|
+
}
|
|
34
70
|
export async function initHooks(token, apiUrl = 'https://getorquesta.com', preferredProjectId) {
|
|
35
71
|
console.log('\n Initializing Orquesta hook integration...\n');
|
|
36
72
|
const agentBin = resolveAgentBin();
|
|
37
73
|
console.log(' Validating token...');
|
|
38
|
-
let
|
|
39
|
-
let projectName;
|
|
74
|
+
let projects;
|
|
40
75
|
try {
|
|
41
76
|
const res = await fetch(`${apiUrl}/api/orquesta-cli/projects`, {
|
|
42
77
|
headers: { 'Authorization': `Bearer ${token}` },
|
|
@@ -46,28 +81,25 @@ export async function initHooks(token, apiUrl = 'https://getorquesta.com', prefe
|
|
|
46
81
|
console.error(` Error: Invalid token`);
|
|
47
82
|
process.exit(1);
|
|
48
83
|
}
|
|
49
|
-
|
|
50
|
-
data.projects?.[0];
|
|
51
|
-
if (!chosen) {
|
|
52
|
-
console.error(' Error: No projects found for this token');
|
|
53
|
-
process.exit(1);
|
|
54
|
-
throw new Error();
|
|
55
|
-
}
|
|
56
|
-
projectId = chosen.id;
|
|
57
|
-
projectName = chosen.name;
|
|
58
|
-
console.log(` Connected to: ${projectName}\n`);
|
|
84
|
+
projects = data.projects ?? [];
|
|
59
85
|
}
|
|
60
86
|
catch (err) {
|
|
61
87
|
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
62
88
|
console.error(` Error: Could not reach ${apiUrl} (${msg})`);
|
|
63
89
|
process.exit(1);
|
|
90
|
+
throw new Error();
|
|
64
91
|
}
|
|
65
|
-
|
|
92
|
+
if (projects.length === 0) {
|
|
93
|
+
console.error(' Error: No projects found for this token');
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
const chosen = await resolveTargetProject(projects, preferredProjectId);
|
|
97
|
+
writeHookFiles({ projectId: chosen.id, token, apiUrl, agentBin });
|
|
66
98
|
console.log(`
|
|
67
|
-
Done!
|
|
68
|
-
|
|
99
|
+
Done! "${chosen.name}" is wired to this directory.
|
|
100
|
+
Run \`claude\` here and every session streams into Orquesta automatically.
|
|
69
101
|
|
|
70
|
-
Dashboard: ${apiUrl}/dashboard/projects/${
|
|
102
|
+
Dashboard: ${apiUrl}/dashboard/projects/${chosen.id}
|
|
71
103
|
`);
|
|
72
104
|
}
|
|
73
105
|
export function writeHookFiles(opts) {
|
|
@@ -124,4 +156,64 @@ export function writeHookFiles(opts) {
|
|
|
124
156
|
return false;
|
|
125
157
|
}
|
|
126
158
|
}
|
|
159
|
+
export function readHookConfig(cwd = process.cwd()) {
|
|
160
|
+
try {
|
|
161
|
+
const p = path.join(cwd, '.orquesta.json');
|
|
162
|
+
if (!fs.existsSync(p))
|
|
163
|
+
return null;
|
|
164
|
+
const data = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
165
|
+
if (!data.projectId)
|
|
166
|
+
return null;
|
|
167
|
+
return { projectId: data.projectId, apiUrl: data.apiUrl };
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
export function disableHooks(cwd = process.cwd()) {
|
|
174
|
+
console.log('\n Disabling Orquesta hook for this directory...\n');
|
|
175
|
+
let changed = false;
|
|
176
|
+
const settingsPath = path.join(cwd, '.claude', 'settings.json');
|
|
177
|
+
if (fs.existsSync(settingsPath)) {
|
|
178
|
+
try {
|
|
179
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
180
|
+
if (settings.hooks && typeof settings.hooks === 'object') {
|
|
181
|
+
for (const event of Object.keys(settings.hooks)) {
|
|
182
|
+
const before = settings.hooks[event];
|
|
183
|
+
if (!Array.isArray(before))
|
|
184
|
+
continue;
|
|
185
|
+
const after = before.filter((e) => !e.hooks?.some((h) => h.command?.includes('orquesta-agent hook')));
|
|
186
|
+
if (after.length !== before.length)
|
|
187
|
+
changed = true;
|
|
188
|
+
if (after.length === 0)
|
|
189
|
+
delete settings.hooks[event];
|
|
190
|
+
else
|
|
191
|
+
settings.hooks[event] = after;
|
|
192
|
+
}
|
|
193
|
+
if (Object.keys(settings.hooks).length === 0)
|
|
194
|
+
delete settings.hooks;
|
|
195
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
196
|
+
if (changed)
|
|
197
|
+
console.log(' Removed Orquesta hooks from .claude/settings.json');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
console.warn(' \x1b[33m⚠ Could not parse .claude/settings.json — left untouched.\x1b[0m');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const orquestaJson = path.join(cwd, '.orquesta.json');
|
|
205
|
+
if (fs.existsSync(orquestaJson)) {
|
|
206
|
+
try {
|
|
207
|
+
fs.unlinkSync(orquestaJson);
|
|
208
|
+
changed = true;
|
|
209
|
+
console.log(' Removed .orquesta.json');
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
console.warn(' \x1b[33m⚠ Could not remove .orquesta.json.\x1b[0m');
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
console.log(changed
|
|
216
|
+
? '\n Done. Claude Code runs in this directory no longer report to Orquesta.\n'
|
|
217
|
+
: '\n Nothing to do — no Orquesta hook was configured here.\n');
|
|
218
|
+
}
|
|
127
219
|
//# sourceMappingURL=hook-init.js.map
|
|
@@ -4,7 +4,7 @@ import * as fs from 'fs';
|
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import { configManager } from '../core/config/config-manager.js';
|
|
6
6
|
import { syncOrquestaConfigs, fetchOrquestaProjects } from '../orquesta/config-sync.js';
|
|
7
|
-
import { writeHookFiles } from '../orquesta/hook-init.js';
|
|
7
|
+
import { writeHookFiles, readHookConfig } from '../orquesta/hook-init.js';
|
|
8
8
|
import { scanProviders, toEndpointConfig } from '../core/config/auto-detect.js';
|
|
9
9
|
const ORQUESTA_API_URL = process.env['ORQUESTA_API_URL'] || 'https://getorquesta.com';
|
|
10
10
|
export function needsFirstRunSetup() {
|
|
@@ -283,5 +283,20 @@ export function showConnectionStatus() {
|
|
|
283
283
|
console.log(chalk.dim(` Connected: ${orquestaConfig.connectedAt ? new Date(orquestaConfig.connectedAt).toLocaleString() : 'Unknown'}`));
|
|
284
284
|
console.log(chalk.dim(` Last sync: ${orquestaConfig.lastSyncAt ? new Date(orquestaConfig.lastSyncAt).toLocaleString() : 'Never'}`));
|
|
285
285
|
console.log(chalk.dim(` Auto-sync: ${orquestaConfig.autoSync !== false ? 'Enabled' : 'Disabled'}`));
|
|
286
|
+
const hook = readHookConfig();
|
|
287
|
+
console.log();
|
|
288
|
+
if (hook) {
|
|
289
|
+
const sameAsConnected = hook.projectId === orquestaConfig.projectId;
|
|
290
|
+
const name = sameAsConnected && orquestaConfig.projectName ? orquestaConfig.projectName : hook.projectId;
|
|
291
|
+
console.log(chalk.green('Claude Code hook: enabled in this directory'));
|
|
292
|
+
console.log(chalk.dim(` Streaming into: ${name}`));
|
|
293
|
+
console.log(chalk.dim(` Project ID: ${hook.projectId}`));
|
|
294
|
+
console.log(chalk.dim(` Change project: orquesta hook switch`));
|
|
295
|
+
console.log(chalk.dim(` Disable: orquesta hook disable`));
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
console.log(chalk.yellow('Claude Code hook: not enabled in this directory'));
|
|
299
|
+
console.log(chalk.dim(` Enable with: orquesta hook enable`));
|
|
300
|
+
}
|
|
286
301
|
}
|
|
287
302
|
//# sourceMappingURL=first-run-setup.js.map
|
|
@@ -34,6 +34,7 @@ import { processFileReferences } from '../hooks/atFileProcessor.js';
|
|
|
34
34
|
import { executeSlashCommand, isSlashCommand, } from '../../core/slash-command-handler.js';
|
|
35
35
|
import { closeJsonStreamLogger } from '../../utils/json-stream-logger.js';
|
|
36
36
|
import { configManager } from '../../core/config/config-manager.js';
|
|
37
|
+
import { readHookConfig } from '../../orquesta/hook-init.js';
|
|
37
38
|
import { logger } from '../../utils/logger.js';
|
|
38
39
|
import { usageTracker } from '../../core/usage-tracker.js';
|
|
39
40
|
import { UpdateNotification } from '../UpdateNotification.js';
|
|
@@ -88,6 +89,16 @@ function getStatusText({ phase, todos, currentToolName }) {
|
|
|
88
89
|
}
|
|
89
90
|
export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo }) => {
|
|
90
91
|
const { exit } = useApp();
|
|
92
|
+
const hookBinding = useMemo(() => {
|
|
93
|
+
const cfg = readHookConfig(process.cwd());
|
|
94
|
+
if (!cfg)
|
|
95
|
+
return null;
|
|
96
|
+
const oc = configManager.getOrquestaConfig();
|
|
97
|
+
const name = oc?.projectId === cfg.projectId && oc?.projectName
|
|
98
|
+
? oc.projectName
|
|
99
|
+
: cfg.projectId.slice(0, 8);
|
|
100
|
+
return { projectName: name };
|
|
101
|
+
}, []);
|
|
91
102
|
const [messages, setMessages] = useState([]);
|
|
92
103
|
const { input, setInput, handleHistoryPrev, handleHistoryNext, addToHistory } = useInputHistory();
|
|
93
104
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
@@ -1412,6 +1423,11 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo }) => {
|
|
|
1412
1423
|
React.createElement(Text, { color: "cyan" }, currentModelInfo.model),
|
|
1413
1424
|
React.createElement(Text, { color: "gray" }, " \u2502 "),
|
|
1414
1425
|
React.createElement(Text, { color: "gray" }, shortenPath(process.cwd())),
|
|
1426
|
+
hookBinding && (React.createElement(React.Fragment, null,
|
|
1427
|
+
React.createElement(Text, { color: "gray" }, " \u2502 "),
|
|
1428
|
+
React.createElement(Text, { color: "green" },
|
|
1429
|
+
"\u2B21 hook \u2192 ",
|
|
1430
|
+
hookBinding.projectName))),
|
|
1415
1431
|
(planExecutionState.todos.length > 0 || batutaUsage) && (React.createElement(React.Fragment, null,
|
|
1416
1432
|
React.createElement(Text, { color: "gray" }, " \u2502 "),
|
|
1417
1433
|
React.createElement(TodoStatusBar, { todos: planExecutionState.todos, projectName: configManager.getOrquestaConfig()?.projectName, batutaUsage: batutaUsage, isProcessing: isProcessing })))),
|