orquesta-cli 0.2.106 → 0.2.108

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
@@ -28,7 +28,7 @@ import { sessionManager } from './core/session/session-manager.js';
28
28
  import { connectWithToken, showConnectionStatus, disconnectFromOrquesta, switchProject } from './setup/first-run-setup.js';
29
29
  import { syncOrquestaConfigs } from './orquesta/config-sync.js';
30
30
  import { scanProviders, scanProvider, toEndpointConfig } from './core/config/auto-detect.js';
31
- import { PROVIDERS } from './core/config/providers.js';
31
+ import { getProviders, refreshCatalogFromServer } from './core/config/providers.js';
32
32
  import { shouldShowOnboarding, runOnboarding } from './core/onboarding.js';
33
33
  const require = createRequire(import.meta.url);
34
34
  const packageJson = require('../package.json');
@@ -224,7 +224,7 @@ program
224
224
  console.log(chalk.yellow('\nNo LLM providers detected.'));
225
225
  console.log(chalk.dim('Set environment variables (e.g., OPENAI_API_KEY) or start a local provider (Ollama, LM Studio).'));
226
226
  console.log(chalk.dim('\nSupported providers:'));
227
- for (const p of PROVIDERS) {
227
+ for (const p of getProviders()) {
228
228
  const envHint = p.envVars.length > 0 ? chalk.dim(` (${p.envVars[0]})`) : p.isLocal ? chalk.dim(` (port ${p.localPort})`) : '';
229
229
  console.log(chalk.white(` ${p.name}${envHint}`));
230
230
  }
@@ -265,7 +265,7 @@ program
265
265
  const detected = await scanProvider(options.addProvider);
266
266
  if (!detected) {
267
267
  spinner.fail(chalk.red(`Provider '${options.addProvider}' not found or not available`));
268
- const provider = PROVIDERS.find(p => p.id === options.addProvider);
268
+ const provider = getProviders().find(p => p.id === options.addProvider);
269
269
  if (provider && provider.envVars.length > 0) {
270
270
  console.log(chalk.dim(`Set ${provider.envVars[0]} environment variable and try again.`));
271
271
  }
@@ -495,5 +495,6 @@ program.on('command:*', () => {
495
495
  console.log(chalk.white('\nUse /help in interactive mode for more help.\n'));
496
496
  process.exit(1);
497
497
  });
498
+ void refreshCatalogFromServer();
498
499
  program.parse(process.argv);
499
500
  //# sourceMappingURL=cli.js.map
@@ -1,9 +1,9 @@
1
1
  import axios from 'axios';
2
- import { PROVIDERS, buildAuthHeaders } from './providers.js';
2
+ import { getProviders, buildAuthHeaders } from './providers.js';
3
3
  export async function scanProviders() {
4
4
  const detected = [];
5
5
  const notFound = [];
6
- for (const provider of PROVIDERS.filter((p) => !p.isLocal)) {
6
+ for (const provider of getProviders().filter((p) => !p.isLocal)) {
7
7
  const apiKey = findEnvVar(provider.envVars);
8
8
  if (apiKey) {
9
9
  const models = await fetchModels(provider, apiKey).catch(() => []);
@@ -18,7 +18,7 @@ export async function scanProviders() {
18
18
  notFound.push(provider.id);
19
19
  }
20
20
  }
21
- const localProbes = PROVIDERS.filter((p) => p.isLocal).map(async (provider) => {
21
+ const localProbes = getProviders().filter((p) => p.isLocal).map(async (provider) => {
22
22
  const running = await probeLocal(provider);
23
23
  if (running) {
24
24
  const models = await fetchModels(provider, '').catch(() => []);
@@ -36,7 +36,7 @@ export async function scanProviders() {
36
36
  return { detected, notFound };
37
37
  }
38
38
  export async function scanProvider(providerId, apiKey) {
39
- const provider = PROVIDERS.find((p) => p.id === providerId);
39
+ const provider = getProviders().find((p) => p.id === providerId);
40
40
  if (!provider)
41
41
  return null;
42
42
  const key = apiKey || findEnvVar(provider.envVars) || '';
@@ -22,6 +22,9 @@ export interface ProviderDefinition {
22
22
  healthCheckPath?: string;
23
23
  }
24
24
  export declare const PROVIDERS: ProviderDefinition[];
25
+ export declare function refreshCatalogFromServer(timeoutMs?: number): Promise<boolean>;
26
+ export declare function getCatalogVersion(): number;
27
+ export declare function getProviders(): ProviderDefinition[];
25
28
  export declare function getProvider(id: string): ProviderDefinition | undefined;
26
29
  export declare function detectProviderFromUrl(baseUrl: string): ProviderDefinition | undefined;
27
30
  export declare function modelHasCapability(providerId: string, modelId: string, capability: ModelCapability): boolean;
@@ -1,3 +1,6 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
1
4
  export const PROVIDERS = [
2
5
  {
3
6
  id: 'openai',
@@ -302,17 +305,98 @@ export const PROVIDERS = [
302
305
  knownModels: [],
303
306
  },
304
307
  ];
308
+ const CATALOG_CACHE_DIR = path.join(os.homedir(), '.orquesta-cli');
309
+ const CATALOG_CACHE_FILE = path.join(CATALOG_CACHE_DIR, 'catalog.json');
310
+ const CATALOG_ENDPOINT_PATH = '/api/orquesta-cli/catalog';
311
+ let effectiveProviders = PROVIDERS;
312
+ let effectiveVersion = 0;
313
+ function overlayProviders(incoming) {
314
+ const byId = new Map();
315
+ for (const p of PROVIDERS)
316
+ byId.set(p.id, p);
317
+ for (const p of incoming) {
318
+ if (p && typeof p.id === 'string' && Array.isArray(p.knownModels)) {
319
+ byId.set(p.id, p);
320
+ }
321
+ }
322
+ return Array.from(byId.values());
323
+ }
324
+ function isValidCatalogProviders(value) {
325
+ return (Array.isArray(value) &&
326
+ value.length > 0 &&
327
+ value.every((p) => p &&
328
+ typeof p.id === 'string' &&
329
+ typeof p.baseUrl === 'string' &&
330
+ Array.isArray(p.knownModels)));
331
+ }
332
+ function seedCatalogFromDisk() {
333
+ try {
334
+ const raw = fs.readFileSync(CATALOG_CACHE_FILE, 'utf-8');
335
+ const cache = JSON.parse(raw);
336
+ if (isValidCatalogProviders(cache.providers)) {
337
+ effectiveProviders = overlayProviders(cache.providers);
338
+ effectiveVersion = typeof cache.version === 'number' ? cache.version : 0;
339
+ }
340
+ }
341
+ catch {
342
+ }
343
+ }
344
+ seedCatalogFromDisk();
345
+ function catalogUrl() {
346
+ const base = process.env['ORQUESTA_API_URL'] || 'https://getorquesta.com';
347
+ return base.replace(/\/$/, '') + CATALOG_ENDPOINT_PATH;
348
+ }
349
+ export async function refreshCatalogFromServer(timeoutMs = 4000) {
350
+ try {
351
+ const controller = new AbortController();
352
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
353
+ const res = await fetch(catalogUrl(), {
354
+ signal: controller.signal,
355
+ headers: { Accept: 'application/json' },
356
+ });
357
+ clearTimeout(timer);
358
+ if (!res.ok)
359
+ return false;
360
+ const json = (await res.json());
361
+ if (!isValidCatalogProviders(json.providers))
362
+ return false;
363
+ effectiveProviders = overlayProviders(json.providers);
364
+ effectiveVersion = typeof json.version === 'number' ? json.version : effectiveVersion;
365
+ try {
366
+ if (!fs.existsSync(CATALOG_CACHE_DIR))
367
+ fs.mkdirSync(CATALOG_CACHE_DIR, { recursive: true });
368
+ const cache = {
369
+ version: effectiveVersion,
370
+ providers: json.providers,
371
+ fetchedAt: new Date().toISOString(),
372
+ };
373
+ fs.writeFileSync(CATALOG_CACHE_FILE, JSON.stringify(cache, null, 2));
374
+ }
375
+ catch {
376
+ }
377
+ return true;
378
+ }
379
+ catch {
380
+ return false;
381
+ }
382
+ }
383
+ export function getCatalogVersion() {
384
+ return effectiveVersion;
385
+ }
386
+ export function getProviders() {
387
+ return effectiveProviders;
388
+ }
305
389
  export function getProvider(id) {
306
- return PROVIDERS.find((p) => p.id === id);
390
+ return effectiveProviders.find((p) => p.id === id);
307
391
  }
308
392
  export function detectProviderFromUrl(baseUrl) {
309
393
  const url = baseUrl.toLowerCase();
310
- for (const provider of PROVIDERS) {
394
+ for (const provider of effectiveProviders) {
311
395
  const providerHost = new URL(provider.baseUrl).hostname;
312
396
  if (url.includes(providerHost))
313
397
  return provider;
314
398
  }
315
- for (const provider of PROVIDERS.filter((p) => p.isLocal && p.localPort)) {
399
+ for (const provider of effectiveProviders.filter((p) => p.isLocal && p.localPort)) {
316
400
  if (url.includes(`:${provider.localPort}`))
317
401
  return provider;
318
402
  }
@@ -51,16 +51,33 @@ export const BASH_DANGEROUS_PATTERNS = [
51
51
  /\brm\s+-rf\s+[\/~]/i,
52
52
  /\brm\s+-rf\s+\*/i,
53
53
  /\bdd\s+if=/i,
54
+ /\bdd\b[^\n]*\bof=\/dev\/sd/i,
54
55
  /\bmkfs\b/i,
55
56
  /\b:(){ :|:& };:/,
56
- /\bchmod\s+-R\s+777\s+\//i,
57
+ /\bchmod\s+(-R\s+)?777\s+[\/~]/i,
57
58
  /\bsudo\s+rm/i,
58
59
  />\s*\/dev\/sd[a-z]/i,
60
+ /\b(curl|wget)\b[^|]*\|\s*(sudo\s+)?(ba|z)?sh\b/i,
61
+ /\bgit\s+push\b[^\n]*(?:^|\s)(?:--force|-f)\b[^\n]*\b(?:main|master|prod)\b/i,
62
+ /\bgit\s+push\b[^\n]*\b(?:main|master|prod)\b[^\n]*(?:^|\s)(?:--force|-f)\b/i,
59
63
  /\bshutdown\b/i,
60
64
  /\breboot\b/i,
61
65
  /\bhalt\b/i,
62
66
  /\bpoweroff\b/i,
63
67
  ];
68
+ const CATASTROPHIC_RM_TARGET = /^(\/|~\/?|\*|\.\/?|\.\.\/?|\/\*|~\/\*)$/;
69
+ function isCatastrophicRm(command) {
70
+ const m = command.match(/\brm\b((?:\s+-{1,2}[A-Za-z-]+)*)\s+(\S+)/i);
71
+ if (!m)
72
+ return false;
73
+ const flags = (m[1] ?? '').toLowerCase();
74
+ const target = m[2] ?? '';
75
+ const hasRecursive = /-{1,2}\w*r/.test(flags) || flags.includes('recursive');
76
+ const hasForce = /-{1,2}\w*f/.test(flags) || flags.includes('force');
77
+ if (!hasRecursive || !hasForce)
78
+ return false;
79
+ return CATASTROPHIC_RM_TARGET.test(target);
80
+ }
64
81
  export const POWERSHELL_DANGEROUS_PATTERNS = [
65
82
  /Remove-Item\s+.*-Recurse\s+.*-Force\s+[A-Z]:\\/i,
66
83
  /ri\s+.*-r\s+.*-fo\s+[A-Z]:\\/i,
@@ -79,6 +96,8 @@ export const POWERSHELL_DANGEROUS_PATTERNS = [
79
96
  /for\s*\(\s*;\s*;\s*\)\s*\{.*Start-Process/i,
80
97
  ];
81
98
  export function isDangerousBashCommand(command) {
99
+ if (isCatastrophicRm(command))
100
+ return true;
82
101
  return BASH_DANGEROUS_PATTERNS.some(pattern => pattern.test(command));
83
102
  }
84
103
  export function isDangerousPowerShellCommand(command) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orquesta-cli",
3
- "version": "0.2.106",
3
+ "version": "0.2.108",
4
4
  "description": "Orquesta CLI - AI-powered coding assistant with team collaboration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,58 +0,0 @@
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
@@ -1,374 +0,0 @@
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