orquesta-cli 0.2.22 → 0.2.24

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.
@@ -0,0 +1,58 @@
1
+ export type UpdateStatus = {
2
+ type: 'checking';
3
+ } | {
4
+ type: 'no_update';
5
+ } | {
6
+ type: 'first_run';
7
+ step: number;
8
+ totalSteps: number;
9
+ message: string;
10
+ } | {
11
+ type: 'updating';
12
+ step: number;
13
+ totalSteps: number;
14
+ message: string;
15
+ } | {
16
+ type: 'complete';
17
+ needsRestart: boolean;
18
+ message: string;
19
+ } | {
20
+ type: 'error';
21
+ message: string;
22
+ } | {
23
+ type: 'skipped';
24
+ reason: string;
25
+ };
26
+ export type StatusCallback = (status: UpdateStatus) => void;
27
+ export declare class GitAutoUpdater {
28
+ private repoUrl;
29
+ private repoDir;
30
+ private commitFile;
31
+ private enabled;
32
+ private onStatus;
33
+ constructor(options?: {
34
+ repoUrl?: string;
35
+ enabled?: boolean;
36
+ onStatus?: StatusCallback;
37
+ });
38
+ setStatusCallback(callback: StatusCallback): void;
39
+ private emitStatus;
40
+ run(options?: {
41
+ noUpdate?: boolean;
42
+ }): Promise<boolean>;
43
+ private runBinaryMode;
44
+ private getRemoteCommit;
45
+ private getSavedCommit;
46
+ private saveCommit;
47
+ private updateBinary;
48
+ private initialSetup;
49
+ private pullAndUpdate;
50
+ private freshClone;
51
+ private rebuildAndLink;
52
+ private copyBinariesInternal;
53
+ private ensurePathConfigured;
54
+ private unlinkNpm;
55
+ private cleanupRepo;
56
+ }
57
+ export default GitAutoUpdater;
58
+ //# sourceMappingURL=git-auto-updater.d.ts.map
@@ -0,0 +1,374 @@
1
+ import { spawn } from 'child_process';
2
+ import fs, { createReadStream, createWriteStream } from 'fs';
3
+ import { rm, copyFile, chmod } from 'fs/promises';
4
+ import { pipeline } from 'stream/promises';
5
+ import path from 'path';
6
+ import os from 'os';
7
+ import zlib from 'zlib';
8
+ import { logger } from '../utils/logger.js';
9
+ function isRunningAsBinary() {
10
+ const execPath = process.execPath;
11
+ const isNodeRuntime = execPath.includes('node') || execPath.includes('nodejs');
12
+ return !isNodeRuntime;
13
+ }
14
+ function execAsync(command, options = {}) {
15
+ return new Promise((resolve, reject) => {
16
+ const [cmd, ...args] = command.split(' ');
17
+ const child = spawn(cmd, args, {
18
+ cwd: options.cwd,
19
+ shell: true,
20
+ stdio: 'pipe',
21
+ });
22
+ let stdout = '';
23
+ let stderr = '';
24
+ child.stdout?.on('data', (data) => {
25
+ stdout += data.toString();
26
+ });
27
+ child.stderr?.on('data', (data) => {
28
+ stderr += data.toString();
29
+ });
30
+ child.on('close', (code) => {
31
+ if (code === 0) {
32
+ resolve({ stdout, stderr });
33
+ }
34
+ else {
35
+ const error = new Error(`Command failed: ${command}`);
36
+ error.stdout = stdout;
37
+ error.stderr = stderr;
38
+ error.code = code;
39
+ reject(error);
40
+ }
41
+ });
42
+ child.on('error', (err) => {
43
+ reject(err);
44
+ });
45
+ });
46
+ }
47
+ export class GitAutoUpdater {
48
+ repoUrl = 'https://github.com/A2G-Dev-Space/Local-CLI.git';
49
+ repoDir;
50
+ commitFile;
51
+ enabled = true;
52
+ onStatus = null;
53
+ constructor(options) {
54
+ this.repoDir = path.join(os.homedir(), '.local-cli', 'repo');
55
+ this.commitFile = path.join(os.homedir(), '.local-cli', 'current-commit');
56
+ if (options?.repoUrl) {
57
+ this.repoUrl = options.repoUrl;
58
+ }
59
+ if (options?.enabled !== undefined) {
60
+ this.enabled = options.enabled;
61
+ }
62
+ if (options?.onStatus) {
63
+ this.onStatus = options.onStatus;
64
+ }
65
+ }
66
+ setStatusCallback(callback) {
67
+ this.onStatus = callback;
68
+ }
69
+ emitStatus(status) {
70
+ if (this.onStatus) {
71
+ this.onStatus(status);
72
+ }
73
+ }
74
+ async run(options = {}) {
75
+ logger.enter('GitAutoUpdater.run', {
76
+ noUpdate: options.noUpdate,
77
+ enabled: this.enabled,
78
+ repoDir: this.repoDir
79
+ });
80
+ if (options.noUpdate || !this.enabled) {
81
+ logger.flow('Git auto-update disabled - skipping');
82
+ this.emitStatus({ type: 'skipped', reason: 'disabled' });
83
+ return false;
84
+ }
85
+ this.emitStatus({ type: 'checking' });
86
+ try {
87
+ if (isRunningAsBinary() && fs.existsSync(this.repoDir)) {
88
+ logger.flow('Cleaning up leftover repo from previous version');
89
+ await this.cleanupRepo();
90
+ }
91
+ if (isRunningAsBinary()) {
92
+ return await this.runBinaryMode();
93
+ }
94
+ logger.flow('Checking repository directory');
95
+ if (!fs.existsSync(this.repoDir)) {
96
+ logger.flow('First run detected - need initial setup');
97
+ return await this.initialSetup();
98
+ }
99
+ else {
100
+ return await this.pullAndUpdate();
101
+ }
102
+ }
103
+ catch (error) {
104
+ logger.error('Git auto-update failed', error);
105
+ this.emitStatus({ type: 'error', message: 'Auto-update failed, continuing with current version' });
106
+ }
107
+ return false;
108
+ }
109
+ async runBinaryMode() {
110
+ logger.flow('Running in binary mode - using ls-remote for update check');
111
+ try {
112
+ const remoteCommit = await this.getRemoteCommit();
113
+ if (!remoteCommit) {
114
+ logger.error('Failed to get remote commit');
115
+ this.emitStatus({ type: 'error', message: 'Failed to check for updates' });
116
+ return false;
117
+ }
118
+ const savedCommit = this.getSavedCommit();
119
+ logger.debug('Version check', { remote: remoteCommit.slice(0, 7), saved: savedCommit?.slice(0, 7) || 'none' });
120
+ if (savedCommit === remoteCommit) {
121
+ logger.flow('Already up to date');
122
+ this.emitStatus({ type: 'no_update' });
123
+ return false;
124
+ }
125
+ logger.flow('Update available, starting update process');
126
+ return await this.updateBinary(remoteCommit);
127
+ }
128
+ catch (error) {
129
+ logger.error('Binary mode update failed', error);
130
+ this.emitStatus({ type: 'error', message: 'Update check failed' });
131
+ return false;
132
+ }
133
+ }
134
+ async getRemoteCommit() {
135
+ try {
136
+ const result = await execAsync(`git ls-remote ${this.repoUrl} refs/heads/main`);
137
+ const match = result.stdout.match(/^([a-f0-9]+)/);
138
+ return match && match[1] ? match[1] : null;
139
+ }
140
+ catch (error) {
141
+ logger.error('Failed to get remote commit via ls-remote', error);
142
+ return null;
143
+ }
144
+ }
145
+ getSavedCommit() {
146
+ try {
147
+ if (fs.existsSync(this.commitFile)) {
148
+ return fs.readFileSync(this.commitFile, 'utf-8').trim();
149
+ }
150
+ }
151
+ catch (error) {
152
+ logger.debug('Failed to read saved commit: ' + (error instanceof Error ? error.message : String(error)));
153
+ }
154
+ return null;
155
+ }
156
+ saveCommit(commit) {
157
+ try {
158
+ const dir = path.dirname(this.commitFile);
159
+ if (!fs.existsSync(dir)) {
160
+ fs.mkdirSync(dir, { recursive: true });
161
+ }
162
+ fs.writeFileSync(this.commitFile, commit);
163
+ logger.debug('Saved commit hash: ' + commit.slice(0, 7));
164
+ }
165
+ catch (error) {
166
+ logger.debug('Failed to save commit: ' + (error instanceof Error ? error.message : String(error)));
167
+ }
168
+ }
169
+ async updateBinary(remoteCommit) {
170
+ const totalSteps = 3;
171
+ const isFirstRun = !this.getSavedCommit();
172
+ try {
173
+ const statusType = isFirstRun ? 'first_run' : 'updating';
174
+ this.emitStatus({ type: statusType, step: 1, totalSteps, message: 'Downloading update...' });
175
+ const parentDir = path.dirname(this.repoDir);
176
+ if (!fs.existsSync(parentDir)) {
177
+ fs.mkdirSync(parentDir, { recursive: true });
178
+ }
179
+ await execAsync(`git clone --depth 1 ${this.repoUrl} ${this.repoDir}`);
180
+ this.emitStatus({ type: statusType, step: 2, totalSteps, message: 'Installing update...' });
181
+ const success = await this.copyBinariesInternal();
182
+ if (!success) {
183
+ return false;
184
+ }
185
+ this.emitStatus({ type: statusType, step: 3, totalSteps, message: 'Finalizing...' });
186
+ await this.cleanupRepo();
187
+ this.saveCommit(remoteCommit);
188
+ const shell = process.env['SHELL'] || '/bin/bash';
189
+ const rcFile = shell.includes('zsh') ? '~/.zshrc' : '~/.bashrc';
190
+ const restartMsg = isFirstRun
191
+ ? `Setup complete! Run: source ${rcFile} && lcli`
192
+ : 'Update complete! Please restart.';
193
+ this.emitStatus({ type: 'complete', needsRestart: true, message: restartMsg });
194
+ return true;
195
+ }
196
+ catch (error) {
197
+ logger.error('Binary update failed', error);
198
+ await this.cleanupRepo();
199
+ const message = error instanceof Error ? error.message : 'Unknown error';
200
+ this.emitStatus({ type: 'error', message: `Update failed: ${message}` });
201
+ return false;
202
+ }
203
+ }
204
+ async initialSetup() {
205
+ logger.enter('initialSetup', {
206
+ repoDir: this.repoDir,
207
+ repoUrl: this.repoUrl
208
+ });
209
+ const totalSteps = 4;
210
+ try {
211
+ this.emitStatus({ type: 'first_run', step: 1, totalSteps, message: 'Cloning repository...' });
212
+ const parentDir = path.dirname(this.repoDir);
213
+ if (!fs.existsSync(parentDir)) {
214
+ fs.mkdirSync(parentDir, { recursive: true });
215
+ }
216
+ await execAsync(`git clone ${this.repoUrl} ${this.repoDir}`);
217
+ this.emitStatus({ type: 'first_run', step: 2, totalSteps, message: 'Installing dependencies...' });
218
+ await execAsync('npm install', { cwd: this.repoDir });
219
+ this.emitStatus({ type: 'first_run', step: 3, totalSteps, message: 'Building project...' });
220
+ await execAsync('npm run build', { cwd: this.repoDir });
221
+ this.emitStatus({ type: 'first_run', step: 4, totalSteps, message: 'Creating global link...' });
222
+ await execAsync('npm link', { cwd: this.repoDir });
223
+ this.emitStatus({ type: 'complete', needsRestart: true, message: 'Setup complete! Please restart.' });
224
+ return true;
225
+ }
226
+ catch (error) {
227
+ logger.error('Initial setup failed', error);
228
+ const message = error instanceof Error ? error.message : 'Unknown error';
229
+ this.emitStatus({ type: 'error', message: `Setup failed: ${message}` });
230
+ return false;
231
+ }
232
+ }
233
+ async pullAndUpdate() {
234
+ logger.debug('Checking for updates', { repoDir: this.repoDir });
235
+ try {
236
+ await execAsync('git fetch origin main', { cwd: this.repoDir });
237
+ const currentResult = await execAsync('git rev-parse HEAD', { cwd: this.repoDir });
238
+ const latestResult = await execAsync('git rev-parse origin/main', { cwd: this.repoDir });
239
+ const currentCommit = currentResult.stdout.trim();
240
+ const latestCommit = latestResult.stdout.trim();
241
+ if (currentCommit === latestCommit) {
242
+ logger.debug('Already up to date, no rebuild needed');
243
+ this.emitStatus({ type: 'no_update' });
244
+ return false;
245
+ }
246
+ logger.debug('Resetting to latest commit...', { from: currentCommit.slice(0, 7), to: latestCommit.slice(0, 7) });
247
+ await execAsync('git reset --hard origin/main', { cwd: this.repoDir });
248
+ return await this.rebuildAndLink();
249
+ }
250
+ catch (error) {
251
+ logger.error('Pull/reset failed, attempting fresh clone', error);
252
+ return await this.freshClone();
253
+ }
254
+ }
255
+ async freshClone() {
256
+ logger.flow('Performing fresh clone');
257
+ try {
258
+ this.emitStatus({ type: 'updating', step: 1, totalSteps: 4, message: 'Removing old repository...' });
259
+ await rm(this.repoDir, { recursive: true, force: true });
260
+ return await this.initialSetup();
261
+ }
262
+ catch (error) {
263
+ logger.error('Fresh clone failed', error);
264
+ const message = error instanceof Error ? error.message : 'Unknown error';
265
+ this.emitStatus({ type: 'error', message: `Fresh clone failed: ${message}` });
266
+ return false;
267
+ }
268
+ }
269
+ async rebuildAndLink() {
270
+ const totalSteps = 3;
271
+ try {
272
+ this.emitStatus({ type: 'updating', step: 1, totalSteps, message: 'Updating dependencies...' });
273
+ await execAsync('npm install', { cwd: this.repoDir });
274
+ this.emitStatus({ type: 'updating', step: 2, totalSteps, message: 'Building project...' });
275
+ await execAsync('npm run build', { cwd: this.repoDir });
276
+ this.emitStatus({ type: 'updating', step: 3, totalSteps, message: 'Updating global link...' });
277
+ await execAsync('npm link', { cwd: this.repoDir });
278
+ this.emitStatus({ type: 'complete', needsRestart: true, message: 'Update complete! Please restart.' });
279
+ return true;
280
+ }
281
+ catch (buildError) {
282
+ logger.error('Build/link failed', buildError);
283
+ const message = buildError instanceof Error ? buildError.message : 'Unknown error';
284
+ this.emitStatus({ type: 'error', message: `Build failed: ${message}` });
285
+ return false;
286
+ }
287
+ }
288
+ async copyBinariesInternal() {
289
+ try {
290
+ const repoBinDir = path.join(this.repoDir, 'bin');
291
+ const installDir = path.join(os.homedir(), '.local', 'bin');
292
+ const lcliGzSrc = path.join(repoBinDir, 'lcli.gz');
293
+ const yogaSrc = path.join(repoBinDir, 'yoga.wasm');
294
+ const lcliDest = path.join(installDir, 'lcli');
295
+ const yogaDest = path.join(installDir, 'yoga.wasm');
296
+ if (!fs.existsSync(lcliGzSrc)) {
297
+ this.emitStatus({ type: 'error', message: 'Binary not found in repository (bin/lcli.gz)' });
298
+ return false;
299
+ }
300
+ if (!fs.existsSync(installDir)) {
301
+ fs.mkdirSync(installDir, { recursive: true });
302
+ }
303
+ await rm(lcliDest, { force: true });
304
+ await pipeline(createReadStream(lcliGzSrc), zlib.createGunzip(), createWriteStream(lcliDest));
305
+ await chmod(lcliDest, 0o755);
306
+ if (fs.existsSync(yogaSrc)) {
307
+ await copyFile(yogaSrc, yogaDest);
308
+ }
309
+ await this.ensurePathConfigured(installDir);
310
+ await this.unlinkNpm();
311
+ logger.debug('Binaries copied successfully');
312
+ return true;
313
+ }
314
+ catch (error) {
315
+ logger.error('Copy binaries internal failed', error);
316
+ return false;
317
+ }
318
+ }
319
+ async ensurePathConfigured(binDir) {
320
+ const pathExport = `export PATH="${binDir}:$PATH"`;
321
+ const marker = '# local-cli binary';
322
+ const shell = process.env['SHELL'] || '/bin/bash';
323
+ let rcFile;
324
+ if (shell.includes('zsh')) {
325
+ rcFile = path.join(os.homedir(), '.zshrc');
326
+ }
327
+ else {
328
+ rcFile = path.join(os.homedir(), '.bashrc');
329
+ }
330
+ try {
331
+ let content = '';
332
+ if (fs.existsSync(rcFile)) {
333
+ content = fs.readFileSync(rcFile, 'utf-8');
334
+ }
335
+ if (content.includes(marker)) {
336
+ logger.debug('PATH already configured (marker found)');
337
+ return;
338
+ }
339
+ const currentPath = process.env['PATH'] || '';
340
+ if (currentPath.split(':').includes(binDir)) {
341
+ logger.debug('PATH already contains binDir, adding marker for future reference');
342
+ fs.appendFileSync(rcFile, `\n${marker}\n# PATH already configured elsewhere\n`);
343
+ return;
344
+ }
345
+ const addition = `\n${marker}\n${pathExport}\n`;
346
+ fs.appendFileSync(rcFile, addition);
347
+ logger.debug('PATH configuration added to ' + rcFile);
348
+ }
349
+ catch (error) {
350
+ logger.debug('Failed to configure PATH: ' + (error instanceof Error ? error.message : String(error)));
351
+ }
352
+ }
353
+ async unlinkNpm() {
354
+ try {
355
+ await execAsync('npm unlink -g local-cli');
356
+ }
357
+ catch (error) {
358
+ }
359
+ }
360
+ async cleanupRepo() {
361
+ try {
362
+ if (fs.existsSync(this.repoDir)) {
363
+ logger.debug('Cleaning up repo directory to prevent source code exposure');
364
+ await rm(this.repoDir, { recursive: true, force: true });
365
+ logger.debug('Repo directory removed successfully');
366
+ }
367
+ }
368
+ catch (error) {
369
+ logger.debug('Failed to cleanup repo: ' + (error instanceof Error ? error.message : String(error)));
370
+ }
371
+ }
372
+ }
373
+ export default GitAutoUpdater;
374
+ //# sourceMappingURL=git-auto-updater.js.map
@@ -17,6 +17,8 @@ import { formatErrorMessage, buildTodoContext, findActiveTodo, getTodoStats } fr
17
17
  import { runParallelGraph, shouldUseParallelOrchestrator } from './parallel-orchestrator.js';
18
18
  import { memoryStore } from './memory-store.js';
19
19
  import { auditLog } from './audit-log.js';
20
+ import { reportPromptStart, reportPromptComplete, reportPromptCancelled } from '../orquesta/prompt-reporter.js';
21
+ import { usageTracker } from '../core/usage-tracker.js';
20
22
  function buildEnvironmentContext() {
21
23
  const cwd = process.cwd();
22
24
  return `\n\n## Environment\n\nWorking directory (where ALL your tools execute): \`${cwd}\`\nTools (read_file, list_files, bash, edit_file, …) run on the USER'S machine in this directory.\n- Use paths RELATIVE to this directory ("package.json", "src/foo.ts", "."). They resolve against the working directory automatically.\n- Do NOT prepend the absolute working-directory path to tool arguments, and do NOT invent absolute paths from documentation — the repo is already at the working directory above.\n- Ignore any other "current directory" that may appear elsewhere in your context (e.g. a proxy's /tmp); the directory above is authoritative.\n`;
@@ -63,6 +65,14 @@ export class PlanExecutor {
63
65
  worktreeEnabled: configManager.isWorktreeIsolationEnabled(),
64
66
  maxParallel: configManager.getMaxParallelWorkers(),
65
67
  });
68
+ const orquestaEndpoint = configManager.getCurrentEndpoint();
69
+ const orquestaTrackingId = await reportPromptStart(userMessage, {
70
+ cwd: process.cwd(),
71
+ model: configManager.getCurrentModel()?.name,
72
+ endpoint: orquestaEndpoint?.baseUrl,
73
+ });
74
+ let runError = null;
75
+ let runInterrupted = false;
66
76
  try {
67
77
  if (isInterruptedRef.current) {
68
78
  throw new Error('INTERRUPTED');
@@ -212,6 +222,7 @@ export class PlanExecutor {
212
222
  }
213
223
  catch (error) {
214
224
  if (error instanceof Error && error.message === 'INTERRUPTED') {
225
+ runInterrupted = true;
215
226
  auditLog.emit(auditSid, 'run.error', { runId, reason: 'interrupted' });
216
227
  logger.flow('Plan mode interrupted by user');
217
228
  callbacks.setMessages((prev) => {
@@ -224,6 +235,7 @@ export class PlanExecutor {
224
235
  });
225
236
  return;
226
237
  }
238
+ runError = error;
227
239
  auditLog.emit(auditSid, 'run.error', { runId, message: error?.message });
228
240
  logger.error('Plan mode execution failed', error);
229
241
  const errorMessage = formatErrorMessage(error);
@@ -237,6 +249,23 @@ export class PlanExecutor {
237
249
  });
238
250
  }
239
251
  finally {
252
+ if (orquestaTrackingId) {
253
+ try {
254
+ if (runInterrupted) {
255
+ await reportPromptCancelled(orquestaTrackingId);
256
+ }
257
+ else {
258
+ const usage = usageTracker.getSessionUsage();
259
+ await reportPromptComplete(orquestaTrackingId, {
260
+ success: !runError,
261
+ error: runError ? formatErrorMessage(runError) : undefined,
262
+ tokensUsed: usage.totalTokens || undefined,
263
+ });
264
+ }
265
+ }
266
+ catch {
267
+ }
268
+ }
240
269
  callbacks.setExecutionPhase('idle');
241
270
  clearTodoCallbacks();
242
271
  clearFinalResponseCallbacks();
@@ -65,6 +65,10 @@ export declare function updatePromptStatus(promptId: string, update: {
65
65
  success: boolean;
66
66
  error?: string;
67
67
  }>;
68
+ export declare function streamPromptLogs(promptId: string, logs: Array<Record<string, unknown>>): Promise<{
69
+ success: boolean;
70
+ error?: string;
71
+ }>;
68
72
  export declare function fullSync(): Promise<{
69
73
  pullResult: SyncResult;
70
74
  pushResult: PushResult;
@@ -264,6 +264,33 @@ export async function updatePromptStatus(promptId, update) {
264
264
  };
265
265
  }
266
266
  }
267
+ export async function streamPromptLogs(promptId, logs) {
268
+ const orquestaConfig = configManager.getOrquestaConfig();
269
+ if (!orquestaConfig?.token) {
270
+ return { success: false, error: 'Not connected to Orquesta' };
271
+ }
272
+ try {
273
+ const response = await fetch(`${ORQUESTA_API}/api/prompts/${promptId}/logs`, {
274
+ method: 'POST',
275
+ headers: {
276
+ 'X-Agent-Token': orquestaConfig.token,
277
+ 'Content-Type': 'application/json',
278
+ },
279
+ body: JSON.stringify({ logs }),
280
+ });
281
+ if (!response.ok) {
282
+ const errorData = (await response.json().catch(() => ({})));
283
+ return { success: false, error: errorData.error || `HTTP ${response.status}` };
284
+ }
285
+ return { success: true };
286
+ }
287
+ catch (error) {
288
+ logger.warn('Failed to stream prompt logs to Orquesta', {
289
+ error: error instanceof Error ? error.message : String(error),
290
+ });
291
+ return { success: false, error: error instanceof Error ? error.message : 'Network error' };
292
+ }
293
+ }
267
294
  export async function fullSync() {
268
295
  const pullResult = await syncOrquestaConfigs();
269
296
  const pushResult = await pushConfigsToOrquesta();
@@ -1,3 +1,4 @@
1
+ export declare function getCurrentPromptId(): string | null;
1
2
  export declare function reportPromptStart(content: string, context?: {
2
3
  cwd?: string;
3
4
  model?: string;
@@ -11,6 +12,11 @@ export declare function reportPromptComplete(trackingId: string, result: {
11
12
  costCents?: number;
12
13
  }): Promise<void>;
13
14
  export declare function reportPromptCancelled(trackingId: string): Promise<void>;
15
+ export declare function reportToolUse(toolName: string, args: Record<string, unknown>, result: {
16
+ success: boolean;
17
+ output?: string;
18
+ error?: string;
19
+ }): Promise<void>;
14
20
  export declare function getActivePromptCount(): number;
15
21
  export declare function cleanupOldPrompts(maxAgeMs?: number): void;
16
22
  //# sourceMappingURL=prompt-reporter.d.ts.map
@@ -1,7 +1,12 @@
1
1
  import { configManager } from '../core/config/config-manager.js';
2
- import { submitPromptToOrquesta, updatePromptStatus } from './config-sync.js';
2
+ import { submitPromptToOrquesta, updatePromptStatus, streamPromptLogs } from './config-sync.js';
3
3
  import { logger } from '../utils/logger.js';
4
4
  const activePrompts = new Map();
5
+ let currentPromptId = null;
6
+ let currentSequence = 0;
7
+ export function getCurrentPromptId() {
8
+ return currentPromptId;
9
+ }
5
10
  export async function reportPromptStart(content, context) {
6
11
  if (!configManager.hasOrquestaConnection()) {
7
12
  return null;
@@ -23,6 +28,8 @@ export async function reportPromptStart(content, context) {
23
28
  startTime: Date.now(),
24
29
  content,
25
30
  });
31
+ currentPromptId = result.promptId;
32
+ currentSequence = 0;
26
33
  await updatePromptStatus(result.promptId, { status: 'running' });
27
34
  return result.promptId;
28
35
  }
@@ -48,6 +55,9 @@ export async function reportPromptComplete(trackingId, result) {
48
55
  costCents: result.costCents,
49
56
  });
50
57
  activePrompts.delete(trackingId);
58
+ if (currentPromptId === trackingId) {
59
+ currentPromptId = null;
60
+ }
51
61
  }
52
62
  catch (error) {
53
63
  logger.warn('Failed to report prompt completion', {
@@ -64,6 +74,9 @@ export async function reportPromptCancelled(trackingId) {
64
74
  status: 'cancelled',
65
75
  });
66
76
  activePrompts.delete(trackingId);
77
+ if (currentPromptId === trackingId) {
78
+ currentPromptId = null;
79
+ }
67
80
  }
68
81
  catch (error) {
69
82
  logger.warn('Failed to report prompt cancellation', {
@@ -71,6 +84,69 @@ export async function reportPromptCancelled(trackingId) {
71
84
  });
72
85
  }
73
86
  }
87
+ function describeToolCall(toolName, args) {
88
+ const str = (v) => (typeof v === 'string' ? v : '');
89
+ const truncate = (s, max) => s.length > max ? `${s.slice(0, max)}…` : s;
90
+ const filePath = str(args['file_path'] || args['path']);
91
+ switch (toolName) {
92
+ case 'bash':
93
+ case 'background_bash':
94
+ return `$ ${truncate(str(args['command']), 500)}`;
95
+ case 'read_file':
96
+ return filePath ? `Read ${filePath}` : 'Read file';
97
+ case 'edit_file':
98
+ return filePath ? `Edit ${filePath}` : 'Edit file';
99
+ case 'write_file':
100
+ return filePath ? `Write ${filePath}` : 'Write file';
101
+ case 'list_files':
102
+ return `List ${str(args['path']) || '.'}`;
103
+ case 'grep':
104
+ case 'search_files':
105
+ return `Search "${truncate(str(args['pattern'] || args['query']), 120)}"`;
106
+ default:
107
+ return `Using ${toolName}`;
108
+ }
109
+ }
110
+ export async function reportToolUse(toolName, args, result) {
111
+ const promptId = currentPromptId;
112
+ if (!promptId || !configManager.hasOrquestaConnection()) {
113
+ return;
114
+ }
115
+ try {
116
+ const truncate = (s, max) => s.length > max ? `${s.slice(0, max)}…` : s;
117
+ const callSeq = (currentSequence += 1);
118
+ const resultSeq = (currentSequence += 1);
119
+ const logs = [
120
+ {
121
+ type: 'tool_call',
122
+ level: 'info',
123
+ sequence: callSeq,
124
+ tool_call: {
125
+ name: toolName,
126
+ parameters: args,
127
+ message: describeToolCall(toolName, args),
128
+ },
129
+ },
130
+ {
131
+ type: 'tool_result',
132
+ level: result.success ? 'success' : 'error',
133
+ sequence: resultSeq,
134
+ tool_result: {
135
+ name: toolName,
136
+ success: result.success,
137
+ output: result.output ? truncate(result.output, 10000) : undefined,
138
+ error: result.error ? truncate(result.error, 10000) : undefined,
139
+ },
140
+ },
141
+ ];
142
+ await streamPromptLogs(promptId, logs);
143
+ }
144
+ catch (error) {
145
+ logger.warn('Failed to report tool use to Orquesta', {
146
+ error: error instanceof Error ? error.message : String(error),
147
+ });
148
+ }
149
+ }
74
150
  export function getActivePromptCount() {
75
151
  return activePrompts.size;
76
152
  }
@@ -1,5 +1,6 @@
1
1
  import { isLLMSimpleTool } from '../../types.js';
2
2
  import { getStreamLogger } from '../../../utils/json-stream-logger.js';
3
+ import { reportToolUse } from '../../../orquesta/prompt-reporter.js';
3
4
  let toolExecutionCallback = null;
4
5
  let toolResponseCallback = null;
5
6
  let planCreatedCallback = null;
@@ -128,6 +129,13 @@ export async function executeSimpleTool(toolName, args) {
128
129
  : (result.error || 'Unknown error');
129
130
  toolResponseCallback(toolName, result.success, resultText);
130
131
  }
132
+ if (!isTodoTool) {
133
+ void reportToolUse(toolName, args, {
134
+ success: result.success,
135
+ output: result.success ? result.result : undefined,
136
+ error: result.success ? undefined : result.error,
137
+ });
138
+ }
131
139
  return result;
132
140
  }
133
141
  export const executeFileTool = executeSimpleTool;
@@ -37,7 +37,7 @@ import { configManager } from '../../core/config/config-manager.js';
37
37
  import { logger } from '../../utils/logger.js';
38
38
  import { usageTracker } from '../../core/usage-tracker.js';
39
39
  import { UpdateNotification } from '../UpdateNotification.js';
40
- import { checkForCliUpdate, runCliUpdate } from '../../utils/update-checker.js';
40
+ import { checkForCliUpdate, runCliUpdate, setSkippedVersion } from '../../utils/update-checker.js';
41
41
  import { setToolExecutionCallback, setTellToUserCallback, setToolResponseCallback, setPlanCreatedCallback, setTodoStartCallback, setTodoCompleteCallback, setTodoFailCallback, setCompactCallback, setAssistantResponseCallback, setToolApprovalCallback, setReasoningCallback, } from '../../tools/llm/simple/file-tools.js';
42
42
  import { createRequire } from 'module';
43
43
  const require = createRequire(import.meta.url);
@@ -512,6 +512,7 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo }) => {
512
512
  runCliUpdate(updateLatest, (line) => setUpdateProgress(line.slice(0, 100)))
513
513
  .then(res => {
514
514
  if (res.success) {
515
+ setSkippedVersion(updateLatest);
515
516
  setUpdatePhase('done');
516
517
  }
517
518
  else {
@@ -521,7 +522,12 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo }) => {
521
522
  });
522
523
  return;
523
524
  }
524
- if (key.escape || inputChar === 'n' || inputChar === 'N' || inputChar === 's' || inputChar === 'S') {
525
+ if (inputChar === 's' || inputChar === 'S') {
526
+ setSkippedVersion(updateLatest);
527
+ setUpdatePhase('hidden');
528
+ return;
529
+ }
530
+ if (key.escape || inputChar === 'n' || inputChar === 'N') {
525
531
  setUpdatePhase('hidden');
526
532
  return;
527
533
  }
@@ -1,3 +1,5 @@
1
+ export declare function getSkippedVersion(): string | null;
2
+ export declare function setSkippedVersion(version: string): void;
1
3
  export declare function checkForCliUpdate(currentVersion: string, timeoutMs?: number): Promise<{
2
4
  current: string;
3
5
  latest: string;
@@ -1,6 +1,33 @@
1
1
  import { spawn } from 'child_process';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
2
5
  import { logger } from './logger.js';
3
6
  const REGISTRY_URL = 'https://registry.npmjs.org/orquesta-cli/latest';
7
+ const STATE_DIR = path.join(process.env['HOME'] || os.homedir() || '.', '.local-cli');
8
+ const STATE_FILE = path.join(STATE_DIR, 'update-state.json');
9
+ function readUpdateState() {
10
+ try {
11
+ return JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'));
12
+ }
13
+ catch {
14
+ return {};
15
+ }
16
+ }
17
+ export function getSkippedVersion() {
18
+ return readUpdateState().skippedVersion ?? null;
19
+ }
20
+ export function setSkippedVersion(version) {
21
+ try {
22
+ if (!fs.existsSync(STATE_DIR))
23
+ fs.mkdirSync(STATE_DIR, { recursive: true });
24
+ const state = readUpdateState();
25
+ state.skippedVersion = version;
26
+ fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
27
+ }
28
+ catch {
29
+ }
30
+ }
4
31
  function isNewer(a, b) {
5
32
  const pa = a.split('.').map(n => parseInt(n, 10) || 0);
6
33
  const pb = b.split('.').map(n => parseInt(n, 10) || 0);
@@ -27,6 +54,10 @@ export async function checkForCliUpdate(currentVersion, timeoutMs = 3500) {
27
54
  const latest = json?.version;
28
55
  if (!latest || !isNewer(latest, currentVersion))
29
56
  return null;
57
+ if (getSkippedVersion() === latest) {
58
+ logger.debug?.('CLI update available but skipped by user', { latest });
59
+ return null;
60
+ }
30
61
  logger.debug?.('CLI update available', { current: currentVersion, latest });
31
62
  return { current: currentVersion, latest };
32
63
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orquesta-cli",
3
- "version": "0.2.22",
3
+ "version": "0.2.24",
4
4
  "description": "Orquesta CLI - AI-powered coding assistant with team collaboration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",