claude-flow 1.0.0
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/LICENSE +21 -0
- package/README.md +612 -0
- package/bin/claude-flow +0 -0
- package/bin/claude-flow-simple +0 -0
- package/bin/claude-flow-typecheck +0 -0
- package/deno.json +84 -0
- package/package.json +45 -0
- package/scripts/check-links.ts +274 -0
- package/scripts/check-performance-regression.ts +168 -0
- package/scripts/claude-sparc.sh +562 -0
- package/scripts/coverage-report.ts +692 -0
- package/scripts/demo-task-system.ts +224 -0
- package/scripts/install.js +72 -0
- package/scripts/test-batch-tasks.ts +29 -0
- package/scripts/test-coordination-features.ts +238 -0
- package/scripts/test-mcp.ts +251 -0
- package/scripts/test-runner.ts +571 -0
- package/scripts/validate-examples.ts +288 -0
- package/src/cli/cli-core.ts +273 -0
- package/src/cli/commands/agent.ts +83 -0
- package/src/cli/commands/config.ts +442 -0
- package/src/cli/commands/help.ts +765 -0
- package/src/cli/commands/index.ts +963 -0
- package/src/cli/commands/mcp.ts +191 -0
- package/src/cli/commands/memory.ts +74 -0
- package/src/cli/commands/monitor.ts +403 -0
- package/src/cli/commands/session.ts +595 -0
- package/src/cli/commands/start.ts +156 -0
- package/src/cli/commands/status.ts +345 -0
- package/src/cli/commands/task.ts +79 -0
- package/src/cli/commands/workflow.ts +763 -0
- package/src/cli/completion.ts +553 -0
- package/src/cli/formatter.ts +310 -0
- package/src/cli/index.ts +211 -0
- package/src/cli/main.ts +23 -0
- package/src/cli/repl.ts +1050 -0
- package/src/cli/simple-cli.js +211 -0
- package/src/cli/simple-cli.ts +211 -0
- package/src/coordination/README.md +400 -0
- package/src/coordination/advanced-scheduler.ts +487 -0
- package/src/coordination/circuit-breaker.ts +366 -0
- package/src/coordination/conflict-resolution.ts +490 -0
- package/src/coordination/dependency-graph.ts +475 -0
- package/src/coordination/index.ts +63 -0
- package/src/coordination/manager.ts +460 -0
- package/src/coordination/messaging.ts +290 -0
- package/src/coordination/metrics.ts +585 -0
- package/src/coordination/resources.ts +322 -0
- package/src/coordination/scheduler.ts +390 -0
- package/src/coordination/work-stealing.ts +224 -0
- package/src/core/config.ts +627 -0
- package/src/core/event-bus.ts +186 -0
- package/src/core/json-persistence.ts +183 -0
- package/src/core/logger.ts +262 -0
- package/src/core/orchestrator-fixed.ts +312 -0
- package/src/core/orchestrator.ts +1234 -0
- package/src/core/persistence.ts +276 -0
- package/src/mcp/auth.ts +438 -0
- package/src/mcp/claude-flow-tools.ts +1280 -0
- package/src/mcp/load-balancer.ts +510 -0
- package/src/mcp/router.ts +240 -0
- package/src/mcp/server.ts +548 -0
- package/src/mcp/session-manager.ts +418 -0
- package/src/mcp/tools.ts +180 -0
- package/src/mcp/transports/base.ts +21 -0
- package/src/mcp/transports/http.ts +457 -0
- package/src/mcp/transports/stdio.ts +254 -0
- package/src/memory/backends/base.ts +22 -0
- package/src/memory/backends/markdown.ts +283 -0
- package/src/memory/backends/sqlite.ts +329 -0
- package/src/memory/cache.ts +238 -0
- package/src/memory/indexer.ts +238 -0
- package/src/memory/manager.ts +572 -0
- package/src/terminal/adapters/base.ts +29 -0
- package/src/terminal/adapters/native.ts +504 -0
- package/src/terminal/adapters/vscode.ts +340 -0
- package/src/terminal/manager.ts +308 -0
- package/src/terminal/pool.ts +271 -0
- package/src/terminal/session.ts +250 -0
- package/src/terminal/vscode-bridge.ts +242 -0
- package/src/utils/errors.ts +231 -0
- package/src/utils/helpers.ts +476 -0
- package/src/utils/types.ts +493 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native terminal adapter implementation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ITerminalAdapter, Terminal } from './base.ts';
|
|
6
|
+
import { ILogger } from '../../core/logger.ts';
|
|
7
|
+
import { TerminalError, TerminalCommandError } from '../../utils/errors.ts';
|
|
8
|
+
import { generateId, delay, timeout, createDeferred } from '../../utils/helpers.ts';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Platform-specific shell configuration
|
|
12
|
+
*/
|
|
13
|
+
interface ShellConfig {
|
|
14
|
+
path: string;
|
|
15
|
+
args: string[];
|
|
16
|
+
env?: Record<string, string>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Native terminal implementation using Deno subprocess
|
|
21
|
+
*/
|
|
22
|
+
class NativeTerminal implements Terminal {
|
|
23
|
+
id: string;
|
|
24
|
+
pid?: number;
|
|
25
|
+
private process?: Deno.ChildProcess | undefined;
|
|
26
|
+
private encoder = new TextEncoder();
|
|
27
|
+
private decoder = new TextDecoder();
|
|
28
|
+
private shell: string;
|
|
29
|
+
private outputBuffer = '';
|
|
30
|
+
private errorBuffer = '';
|
|
31
|
+
private commandMarker: string;
|
|
32
|
+
private commandDeferred?: ReturnType<typeof createDeferred<string>> | undefined;
|
|
33
|
+
private outputListeners = new Set<(data: string) => void>();
|
|
34
|
+
private alive = true;
|
|
35
|
+
private stdoutReader?: ReadableStreamDefaultReader<Uint8Array> | undefined;
|
|
36
|
+
private stderrReader?: ReadableStreamDefaultReader<Uint8Array> | undefined;
|
|
37
|
+
|
|
38
|
+
constructor(shell: string, private logger: ILogger) {
|
|
39
|
+
this.id = generateId('native-term');
|
|
40
|
+
this.shell = shell;
|
|
41
|
+
this.commandMarker = `__CLAUDE_FLOW_${this.id}__`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async initialize(): Promise<void> {
|
|
45
|
+
try {
|
|
46
|
+
const shellConfig = this.getShellConfig();
|
|
47
|
+
|
|
48
|
+
// Start shell process
|
|
49
|
+
const command = new Deno.Command(shellConfig.path, {
|
|
50
|
+
args: shellConfig.args,
|
|
51
|
+
stdin: 'piped',
|
|
52
|
+
stdout: 'piped',
|
|
53
|
+
stderr: 'piped',
|
|
54
|
+
env: {
|
|
55
|
+
...Deno.env.toObject(),
|
|
56
|
+
...shellConfig.env,
|
|
57
|
+
CLAUDE_FLOW_TERMINAL: 'true',
|
|
58
|
+
CLAUDE_FLOW_TERMINAL_ID: this.id,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
this.process = command.spawn();
|
|
63
|
+
|
|
64
|
+
// Get PID if available
|
|
65
|
+
if ('pid' in this.process) {
|
|
66
|
+
this.pid = (this.process as any).pid;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Start output readers
|
|
70
|
+
this.startOutputReader();
|
|
71
|
+
this.startErrorReader();
|
|
72
|
+
|
|
73
|
+
// Monitor process status
|
|
74
|
+
this.monitorProcess();
|
|
75
|
+
|
|
76
|
+
// Wait for shell to be ready
|
|
77
|
+
await this.waitForReady();
|
|
78
|
+
|
|
79
|
+
this.logger.debug('Native terminal initialized', {
|
|
80
|
+
id: this.id,
|
|
81
|
+
pid: this.pid,
|
|
82
|
+
shell: this.shell,
|
|
83
|
+
});
|
|
84
|
+
} catch (error) {
|
|
85
|
+
this.alive = false;
|
|
86
|
+
throw new TerminalError('Failed to create native terminal', { error });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async executeCommand(command: string): Promise<string> {
|
|
91
|
+
if (!this.process || !this.isAlive()) {
|
|
92
|
+
throw new TerminalError('Terminal is not alive');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
// Create deferred for this command
|
|
97
|
+
this.commandDeferred = createDeferred<string>();
|
|
98
|
+
|
|
99
|
+
// Clear output buffer
|
|
100
|
+
this.outputBuffer = '';
|
|
101
|
+
|
|
102
|
+
// Send command with marker
|
|
103
|
+
const markedCommand = this.wrapCommand(command);
|
|
104
|
+
await this.write(markedCommand + '\n');
|
|
105
|
+
|
|
106
|
+
// Wait for command to complete
|
|
107
|
+
const output = await timeout(
|
|
108
|
+
this.commandDeferred.promise,
|
|
109
|
+
30000,
|
|
110
|
+
'Command execution timeout',
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
return output;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
throw new TerminalCommandError('Failed to execute command', { command, error });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async write(data: string): Promise<void> {
|
|
120
|
+
if (!this.process || !this.isAlive()) {
|
|
121
|
+
throw new TerminalError('Terminal is not alive');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const writer = this.process.stdin.getWriter();
|
|
125
|
+
try {
|
|
126
|
+
await writer.write(this.encoder.encode(data));
|
|
127
|
+
} finally {
|
|
128
|
+
writer.releaseLock();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async read(): Promise<string> {
|
|
133
|
+
if (!this.process || !this.isAlive()) {
|
|
134
|
+
throw new TerminalError('Terminal is not alive');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Return buffered output
|
|
138
|
+
const output = this.outputBuffer;
|
|
139
|
+
this.outputBuffer = '';
|
|
140
|
+
return output;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
isAlive(): boolean {
|
|
144
|
+
return this.alive && this.process !== undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async kill(): Promise<void> {
|
|
148
|
+
if (!this.process) return;
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
this.alive = false;
|
|
152
|
+
|
|
153
|
+
// Cancel readers
|
|
154
|
+
if (this.stdoutReader) {
|
|
155
|
+
await this.stdoutReader.cancel();
|
|
156
|
+
}
|
|
157
|
+
if (this.stderrReader) {
|
|
158
|
+
await this.stderrReader.cancel();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Try graceful shutdown first
|
|
162
|
+
try {
|
|
163
|
+
await this.write('exit\n');
|
|
164
|
+
await delay(500);
|
|
165
|
+
} catch {
|
|
166
|
+
// Ignore write errors during shutdown
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Force kill if still alive
|
|
170
|
+
try {
|
|
171
|
+
this.process.kill('SIGTERM');
|
|
172
|
+
await delay(500);
|
|
173
|
+
|
|
174
|
+
// Use SIGKILL if SIGTERM didn't work
|
|
175
|
+
this.process.kill('SIGKILL');
|
|
176
|
+
} catch {
|
|
177
|
+
// Process might already be dead
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Wait for process to exit
|
|
181
|
+
try {
|
|
182
|
+
await this.process.status;
|
|
183
|
+
} catch {
|
|
184
|
+
// Ignore status errors
|
|
185
|
+
}
|
|
186
|
+
} catch (error) {
|
|
187
|
+
this.logger.warn('Error killing native terminal', { id: this.id, error });
|
|
188
|
+
} finally {
|
|
189
|
+
this.process = undefined;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Add output listener
|
|
195
|
+
*/
|
|
196
|
+
addOutputListener(listener: (data: string) => void): void {
|
|
197
|
+
this.outputListeners.add(listener);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Remove output listener
|
|
202
|
+
*/
|
|
203
|
+
removeOutputListener(listener: (data: string) => void): void {
|
|
204
|
+
this.outputListeners.delete(listener);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private getShellConfig(): ShellConfig {
|
|
208
|
+
const platform = Deno.build.os;
|
|
209
|
+
|
|
210
|
+
switch (this.shell) {
|
|
211
|
+
case 'bash':
|
|
212
|
+
return {
|
|
213
|
+
path: platform === 'windows' ? 'C:\\Program Files\\Git\\bin\\bash.exe' : '/bin/bash',
|
|
214
|
+
args: ['--norc', '--noprofile'],
|
|
215
|
+
env: { PS1: '$ ' },
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
case 'zsh':
|
|
219
|
+
return {
|
|
220
|
+
path: '/bin/zsh',
|
|
221
|
+
args: ['--no-rcs'],
|
|
222
|
+
env: { PS1: '$ ' },
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
case 'powershell':
|
|
226
|
+
return {
|
|
227
|
+
path: platform === 'windows' ? 'powershell.exe' : 'pwsh',
|
|
228
|
+
args: ['-NoProfile', '-NonInteractive', '-NoLogo'],
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
case 'cmd':
|
|
232
|
+
return {
|
|
233
|
+
path: 'cmd.exe',
|
|
234
|
+
args: ['/Q', '/K', 'prompt $G'],
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
case 'sh':
|
|
238
|
+
default:
|
|
239
|
+
return {
|
|
240
|
+
path: '/bin/sh',
|
|
241
|
+
args: [],
|
|
242
|
+
env: { PS1: '$ ' },
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private wrapCommand(command: string): string {
|
|
248
|
+
const platform = Deno.build.os;
|
|
249
|
+
|
|
250
|
+
if (this.shell === 'powershell') {
|
|
251
|
+
// PowerShell command wrapping
|
|
252
|
+
return `${command}; Write-Host "${this.commandMarker}"`;
|
|
253
|
+
} else if (this.shell === 'cmd' && platform === 'windows') {
|
|
254
|
+
// Windows CMD command wrapping
|
|
255
|
+
return `${command} & echo ${this.commandMarker}`;
|
|
256
|
+
} else {
|
|
257
|
+
// Unix-like shell command wrapping
|
|
258
|
+
return `${command} && echo "${this.commandMarker}" || (echo "${this.commandMarker}"; false)`;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private async startOutputReader(): Promise<void> {
|
|
263
|
+
if (!this.process) return;
|
|
264
|
+
|
|
265
|
+
this.stdoutReader = this.process.stdout.getReader();
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
while (this.alive) {
|
|
269
|
+
const { done, value } = await this.stdoutReader.read();
|
|
270
|
+
if (done) break;
|
|
271
|
+
|
|
272
|
+
const text = this.decoder.decode(value);
|
|
273
|
+
this.processOutput(text);
|
|
274
|
+
}
|
|
275
|
+
} catch (error) {
|
|
276
|
+
if (this.alive) {
|
|
277
|
+
this.logger.error('Error reading stdout', { id: this.id, error });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private async startErrorReader(): Promise<void> {
|
|
283
|
+
if (!this.process) return;
|
|
284
|
+
|
|
285
|
+
this.stderrReader = this.process.stderr.getReader();
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
while (this.alive) {
|
|
289
|
+
const { done, value } = await this.stderrReader.read();
|
|
290
|
+
if (done) break;
|
|
291
|
+
|
|
292
|
+
const text = this.decoder.decode(value);
|
|
293
|
+
this.errorBuffer += text;
|
|
294
|
+
|
|
295
|
+
// Also send stderr to output listeners
|
|
296
|
+
this.notifyListeners(text);
|
|
297
|
+
}
|
|
298
|
+
} catch (error) {
|
|
299
|
+
if (this.alive) {
|
|
300
|
+
this.logger.error('Error reading stderr', { id: this.id, error });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private processOutput(text: string): void {
|
|
306
|
+
this.outputBuffer += text;
|
|
307
|
+
|
|
308
|
+
// Notify listeners
|
|
309
|
+
this.notifyListeners(text);
|
|
310
|
+
|
|
311
|
+
// Check for command completion marker
|
|
312
|
+
const markerIndex = this.outputBuffer.indexOf(this.commandMarker);
|
|
313
|
+
if (markerIndex !== -1 && this.commandDeferred) {
|
|
314
|
+
// Extract output before marker
|
|
315
|
+
const output = this.outputBuffer.substring(0, markerIndex).trim();
|
|
316
|
+
|
|
317
|
+
// Include any stderr output
|
|
318
|
+
const fullOutput = this.errorBuffer ? `${output}\n${this.errorBuffer}` : output;
|
|
319
|
+
this.errorBuffer = '';
|
|
320
|
+
|
|
321
|
+
// Clear buffer up to after marker
|
|
322
|
+
this.outputBuffer = this.outputBuffer.substring(
|
|
323
|
+
markerIndex + this.commandMarker.length,
|
|
324
|
+
).trim();
|
|
325
|
+
|
|
326
|
+
// Resolve pending command
|
|
327
|
+
this.commandDeferred.resolve(fullOutput);
|
|
328
|
+
this.commandDeferred = undefined;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private notifyListeners(data: string): void {
|
|
333
|
+
this.outputListeners.forEach(listener => {
|
|
334
|
+
try {
|
|
335
|
+
listener(data);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
this.logger.error('Error in output listener', { id: this.id, error });
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private async monitorProcess(): Promise<void> {
|
|
343
|
+
if (!this.process) return;
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
const status = await this.process.status;
|
|
347
|
+
this.logger.info('Terminal process exited', {
|
|
348
|
+
id: this.id,
|
|
349
|
+
code: status.code,
|
|
350
|
+
signal: status.signal,
|
|
351
|
+
});
|
|
352
|
+
} catch (error) {
|
|
353
|
+
this.logger.error('Error monitoring process', { id: this.id, error });
|
|
354
|
+
} finally {
|
|
355
|
+
this.alive = false;
|
|
356
|
+
|
|
357
|
+
// Reject any pending command
|
|
358
|
+
if (this.commandDeferred) {
|
|
359
|
+
this.commandDeferred.reject(new Error('Terminal process exited'));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private async waitForReady(): Promise<void> {
|
|
365
|
+
// Send a test command to ensure shell is ready
|
|
366
|
+
const testCommand = this.shell === 'powershell'
|
|
367
|
+
? 'Write-Host "READY"'
|
|
368
|
+
: 'echo "READY"';
|
|
369
|
+
|
|
370
|
+
await this.write(testCommand + '\n');
|
|
371
|
+
|
|
372
|
+
const startTime = Date.now();
|
|
373
|
+
while (Date.now() - startTime < 5000) {
|
|
374
|
+
if (this.outputBuffer.includes('READY')) {
|
|
375
|
+
this.outputBuffer = '';
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
await delay(100);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
throw new TerminalError('Terminal failed to become ready');
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Native terminal adapter
|
|
387
|
+
*/
|
|
388
|
+
export class NativeAdapter implements ITerminalAdapter {
|
|
389
|
+
private terminals = new Map<string, NativeTerminal>();
|
|
390
|
+
private shell: string;
|
|
391
|
+
|
|
392
|
+
constructor(private logger: ILogger) {
|
|
393
|
+
// Detect available shell
|
|
394
|
+
this.shell = this.detectShell();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async initialize(): Promise<void> {
|
|
398
|
+
this.logger.info('Initializing native terminal adapter', { shell: this.shell });
|
|
399
|
+
|
|
400
|
+
// Verify shell is available
|
|
401
|
+
try {
|
|
402
|
+
const testConfig = this.getTestCommand();
|
|
403
|
+
const command = new Deno.Command(testConfig.cmd, { args: testConfig.args });
|
|
404
|
+
const { success } = await command.output();
|
|
405
|
+
|
|
406
|
+
if (!success) {
|
|
407
|
+
throw new Error('Shell test failed');
|
|
408
|
+
}
|
|
409
|
+
} catch (error) {
|
|
410
|
+
this.logger.warn(`Shell ${this.shell} not available, falling back to sh`, { error });
|
|
411
|
+
this.shell = 'sh';
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async shutdown(): Promise<void> {
|
|
416
|
+
this.logger.info('Shutting down native terminal adapter');
|
|
417
|
+
|
|
418
|
+
// Kill all terminals
|
|
419
|
+
const terminals = Array.from(this.terminals.values());
|
|
420
|
+
await Promise.all(terminals.map(term => term.kill()));
|
|
421
|
+
|
|
422
|
+
this.terminals.clear();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async createTerminal(): Promise<Terminal> {
|
|
426
|
+
const terminal = new NativeTerminal(this.shell, this.logger);
|
|
427
|
+
|
|
428
|
+
await terminal.initialize();
|
|
429
|
+
this.terminals.set(terminal.id, terminal);
|
|
430
|
+
|
|
431
|
+
return terminal;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async destroyTerminal(terminal: Terminal): Promise<void> {
|
|
435
|
+
await terminal.kill();
|
|
436
|
+
this.terminals.delete(terminal.id);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
private detectShell(): string {
|
|
440
|
+
const platform = Deno.build.os;
|
|
441
|
+
|
|
442
|
+
if (platform === 'windows') {
|
|
443
|
+
// Windows shell detection
|
|
444
|
+
const comspec = Deno.env.get('COMSPEC');
|
|
445
|
+
if (comspec?.toLowerCase().includes('powershell')) {
|
|
446
|
+
return 'powershell';
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Check if PowerShell is available
|
|
450
|
+
try {
|
|
451
|
+
const command = new Deno.Command('powershell', { args: ['-Version'] });
|
|
452
|
+
const { success } = command.outputSync();
|
|
453
|
+
if (success) {
|
|
454
|
+
return 'powershell';
|
|
455
|
+
}
|
|
456
|
+
} catch {
|
|
457
|
+
// PowerShell not available
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return 'cmd';
|
|
461
|
+
} else {
|
|
462
|
+
// Unix-like shell detection
|
|
463
|
+
const shell = Deno.env.get('SHELL');
|
|
464
|
+
if (shell) {
|
|
465
|
+
const shellName = shell.split('/').pop();
|
|
466
|
+
if (shellName && this.isShellSupported(shellName)) {
|
|
467
|
+
return shellName;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Try common shells in order of preference
|
|
472
|
+
const shells = ['bash', 'zsh', 'sh'];
|
|
473
|
+
for (const shell of shells) {
|
|
474
|
+
try {
|
|
475
|
+
const command = new Deno.Command('which', { args: [shell] });
|
|
476
|
+
const { success } = command.outputSync();
|
|
477
|
+
if (success) {
|
|
478
|
+
return shell;
|
|
479
|
+
}
|
|
480
|
+
} catch {
|
|
481
|
+
// Continue to next shell
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Default to sh
|
|
486
|
+
return 'sh';
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private isShellSupported(shell: string): boolean {
|
|
491
|
+
return ['bash', 'zsh', 'sh', 'fish', 'dash', 'powershell', 'cmd'].includes(shell);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
private getTestCommand(): { cmd: string; args: string[] } {
|
|
495
|
+
switch (this.shell) {
|
|
496
|
+
case 'powershell':
|
|
497
|
+
return { cmd: 'powershell', args: ['-Version'] };
|
|
498
|
+
case 'cmd':
|
|
499
|
+
return { cmd: 'cmd', args: ['/C', 'echo test'] };
|
|
500
|
+
default:
|
|
501
|
+
return { cmd: this.shell, args: ['--version'] };
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|