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 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', 'Initialize Claude Code hook integration for this project (requires --token)')
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 configManager.initialize();
67
- const saved = configManager.getOrquestaConfig();
68
- const token = options.token || saved?.token;
69
- if (!token) {
70
- console.error(chalk.red('Error: not connected to Orquesta.'));
71
- console.error('Run ' + chalk.cyan('orquesta --login') + ' first, or pass ' + chalk.cyan('orquesta --init --token oat_…') + '.');
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]\n'));
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 Orquesta connection status\n'));
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 projectId;
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
- const chosen = (preferredProjectId && data.projects?.find((p) => p.id === preferredProjectId)) ||
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
- writeHookFiles({ projectId, token, apiUrl, agentBin });
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! Now run \`claude\` in this directory — all activity will
68
- be tracked in Orquesta automatically.
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/${projectId}
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 })))),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orquesta-cli",
3
- "version": "0.2.26",
3
+ "version": "0.2.28",
4
4
  "description": "Orquesta CLI - AI-powered coding assistant with team collaboration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",