@gobing-ai/ts-ai-runner 0.2.1
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/README.md +3 -0
- package/dist/agent-detector.d.ts +34 -0
- package/dist/agent-detector.d.ts.map +1 -0
- package/dist/agent-detector.js +84 -0
- package/dist/agents/shims.d.ts +52 -0
- package/dist/agents/shims.d.ts.map +1 -0
- package/dist/agents/shims.js +141 -0
- package/dist/ai-runner.d.ts +48 -0
- package/dist/ai-runner.d.ts.map +1 -0
- package/dist/ai-runner.js +48 -0
- package/dist/doctor-runner.d.ts +48 -0
- package/dist/doctor-runner.d.ts.map +1 -0
- package/dist/doctor-runner.js +58 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/package.json +58 -0
- package/src/agent-detector.ts +111 -0
- package/src/agents/shims.ts +185 -0
- package/src/ai-runner.ts +97 -0
- package/src/doctor-runner.ts +97 -0
- package/src/index.ts +4 -0
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type AgentName } from './agents/shims';
|
|
2
|
+
import { AiRunner } from './ai-runner';
|
|
3
|
+
/** Installation/version probe result for one coding agent. */
|
|
4
|
+
export interface DetectedAgent {
|
|
5
|
+
/** Agent identifier. */
|
|
6
|
+
name: AgentName | string;
|
|
7
|
+
/** Whether the agent CLI was found and returned a parseable version. */
|
|
8
|
+
installed: boolean;
|
|
9
|
+
/** First version-output line when installed. */
|
|
10
|
+
version: string | null;
|
|
11
|
+
/** Agent-specific channels or models when available. */
|
|
12
|
+
channels: string[];
|
|
13
|
+
/** Detection error when unavailable. */
|
|
14
|
+
error: string | null;
|
|
15
|
+
}
|
|
16
|
+
/** Constructor options for AgentDetector. */
|
|
17
|
+
export interface AgentDetectorOptions {
|
|
18
|
+
/** Shared AiRunner instance. */
|
|
19
|
+
runner?: AiRunner;
|
|
20
|
+
/** Per-agent version probe timeout in milliseconds. */
|
|
21
|
+
timeout?: number;
|
|
22
|
+
}
|
|
23
|
+
/** Probes supported coding-agent CLIs for installation and version state. */
|
|
24
|
+
export declare class AgentDetector {
|
|
25
|
+
private readonly runner;
|
|
26
|
+
private readonly timeout;
|
|
27
|
+
constructor(options?: AgentDetectorOptions);
|
|
28
|
+
/** Probe all bundled agents in display order. */
|
|
29
|
+
detectAll(): Promise<DetectedAgent[]>;
|
|
30
|
+
/** Probe one agent by name. */
|
|
31
|
+
detectOne(agent: string): Promise<DetectedAgent>;
|
|
32
|
+
private parseResult;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=agent-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-detector.d.ts","sourceRoot":"","sources":["../src/agent-detector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAA4C,MAAM,gBAAgB,CAAC;AAC1F,OAAO,EAAuB,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5D,8DAA8D;AAC9D,MAAM,WAAW,aAAa;IAC1B,wBAAwB;IACxB,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;IACzB,wEAAwE;IACxE,SAAS,EAAE,OAAO,CAAC;IACnB,gDAAgD;IAChD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,wDAAwD;IACxD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,wCAAwC;IACxC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,6CAA6C;AAC7C,MAAM,WAAW,oBAAoB;IACjC,gCAAgC;IAChC,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAKD,6EAA6E;AAC7E,qBAAa,aAAa;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,OAAO,GAAE,oBAAyB;IAK9C,iDAAiD;IAC3C,SAAS,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;IAI3C,+BAA+B;IACzB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAiBtD,OAAO,CAAC,WAAW;CAiDtB"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { DISPLAY_ORDER, getAgentShim, isAgentName } from './agents/shims.js';
|
|
2
|
+
import { AiRunner } from './ai-runner.js';
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
4
|
+
const VERSION_PATTERN = /(?<version>\d+\.\d+(?:\.\d+)?)/;
|
|
5
|
+
/** Probes supported coding-agent CLIs for installation and version state. */
|
|
6
|
+
export class AgentDetector {
|
|
7
|
+
runner;
|
|
8
|
+
timeout;
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.runner = options.runner ?? new AiRunner();
|
|
11
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
12
|
+
}
|
|
13
|
+
/** Probe all bundled agents in display order. */
|
|
14
|
+
async detectAll() {
|
|
15
|
+
return await Promise.all(DISPLAY_ORDER.map((agent) => this.detectOne(agent)));
|
|
16
|
+
}
|
|
17
|
+
/** Probe one agent by name. */
|
|
18
|
+
async detectOne(agent) {
|
|
19
|
+
if (!isAgentName(agent)) {
|
|
20
|
+
return { name: agent, installed: false, version: null, channels: [], error: `Unknown agent: ${agent}` };
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
return this.parseResult(agent, await this.runner.runVersionCommand(agent, { timeout: this.timeout }));
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return {
|
|
27
|
+
name: agent,
|
|
28
|
+
installed: false,
|
|
29
|
+
version: null,
|
|
30
|
+
channels: [],
|
|
31
|
+
error: error instanceof Error ? error.message : String(error),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
parseResult(agent, result) {
|
|
36
|
+
const command = getAgentShim(agent).command;
|
|
37
|
+
const output = `${result.stdout}\n${result.stderr}`.trim();
|
|
38
|
+
const lower = output.toLowerCase();
|
|
39
|
+
if (lower.includes('command not found') || lower.includes('enoent') || lower.includes('not recognized')) {
|
|
40
|
+
return {
|
|
41
|
+
name: agent,
|
|
42
|
+
installed: false,
|
|
43
|
+
version: null,
|
|
44
|
+
channels: [],
|
|
45
|
+
error: `${command}: command not found`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (result.signal !== undefined || result.exitCode === null) {
|
|
49
|
+
return {
|
|
50
|
+
name: agent,
|
|
51
|
+
installed: false,
|
|
52
|
+
version: null,
|
|
53
|
+
channels: [],
|
|
54
|
+
error: result.signal ?? 'Process timed out',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (result.exitCode !== 0) {
|
|
58
|
+
return {
|
|
59
|
+
name: agent,
|
|
60
|
+
installed: false,
|
|
61
|
+
version: null,
|
|
62
|
+
channels: [],
|
|
63
|
+
error: `Non-zero exit code ${result.exitCode}: ${result.stderr.slice(0, 200)}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const match = VERSION_PATTERN.exec(output);
|
|
67
|
+
if (match?.groups?.version === undefined) {
|
|
68
|
+
return {
|
|
69
|
+
name: agent,
|
|
70
|
+
installed: false,
|
|
71
|
+
version: null,
|
|
72
|
+
channels: [],
|
|
73
|
+
error: 'Could not parse version output',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
name: agent,
|
|
78
|
+
installed: true,
|
|
79
|
+
version: output.split('\n')[0] ?? match.groups.version,
|
|
80
|
+
channels: [],
|
|
81
|
+
error: null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Identifier for one supported coding agent. */
|
|
2
|
+
export type AgentName = 'claude' | 'codex' | 'gemini' | 'pi' | 'opencode' | 'antigravity' | 'openclaw';
|
|
3
|
+
/** Output mode for prompt invocations. */
|
|
4
|
+
export type OutputMode = 'text' | 'json';
|
|
5
|
+
/** Concrete executable and argv pair returned by an agent shim. */
|
|
6
|
+
export interface ShimCommand {
|
|
7
|
+
/** Executable to invoke. */
|
|
8
|
+
command: string;
|
|
9
|
+
/** Arguments to pass to the executable. */
|
|
10
|
+
args: string[];
|
|
11
|
+
}
|
|
12
|
+
/** Options for prompt-style invocations. */
|
|
13
|
+
export interface PromptOptions {
|
|
14
|
+
/** Prompt text or slash command to send to the agent. */
|
|
15
|
+
input?: string;
|
|
16
|
+
/** Continue the previous session if the agent supports it. */
|
|
17
|
+
continue?: boolean;
|
|
18
|
+
/** Model identifier passed through to the agent CLI. */
|
|
19
|
+
model?: string;
|
|
20
|
+
/** Output mode passed through to the agent CLI. */
|
|
21
|
+
mode?: OutputMode;
|
|
22
|
+
}
|
|
23
|
+
/** Pure command builder for one coding-agent CLI. */
|
|
24
|
+
export interface AgentShim {
|
|
25
|
+
/** Stable agent identifier. */
|
|
26
|
+
readonly name: AgentName;
|
|
27
|
+
/** Executable command name. */
|
|
28
|
+
readonly command: string;
|
|
29
|
+
/** Capability tier: 1 = direct CLI, 2 = gateway/TUI constrained. */
|
|
30
|
+
readonly tier: 1 | 2;
|
|
31
|
+
/** Build a help-display command. */
|
|
32
|
+
getHelpCommand(): ShimCommand;
|
|
33
|
+
/** Build a version-detection command. */
|
|
34
|
+
getVersionCommand(): ShimCommand;
|
|
35
|
+
/** Build a prompt invocation command. */
|
|
36
|
+
getPromptCommand(options: PromptOptions): ShimCommand;
|
|
37
|
+
/** Build an auth-status command, or null when unsupported. */
|
|
38
|
+
getAuthCommand(): ShimCommand | null;
|
|
39
|
+
}
|
|
40
|
+
/** All bundled agent shims keyed by agent name. */
|
|
41
|
+
export declare const AGENT_SHIMS: Readonly<Record<AgentName, AgentShim>>;
|
|
42
|
+
/** Tier-1 auto-selection priority. */
|
|
43
|
+
export declare const TIER1_PRIORITY: readonly AgentName[];
|
|
44
|
+
/** Display order for doctor and list commands. */
|
|
45
|
+
export declare const DISPLAY_ORDER: readonly AgentName[];
|
|
46
|
+
/** Set of gateway/TUI-constrained agents. */
|
|
47
|
+
export declare const TIER2_AGENTS: ReadonlySet<AgentName>;
|
|
48
|
+
/** Return true when a value is a supported agent identifier. */
|
|
49
|
+
export declare function isAgentName(value: string): value is AgentName;
|
|
50
|
+
/** Look up a bundled agent shim. */
|
|
51
|
+
export declare function getAgentShim(agent: AgentName): AgentShim;
|
|
52
|
+
//# sourceMappingURL=shims.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shims.d.ts","sourceRoot":"","sources":["../../src/agents/shims.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,IAAI,GAAG,UAAU,GAAG,aAAa,GAAG,UAAU,CAAC;AAEvG,0CAA0C;AAC1C,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAEzC,mEAAmE;AACnE,MAAM,WAAW,WAAW;IACxB,4BAA4B;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC1B,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,IAAI,CAAC,EAAE,UAAU,CAAC;CACrB;AAED,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACtB,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,+BAA+B;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;IACrB,oCAAoC;IACpC,cAAc,IAAI,WAAW,CAAC;IAC9B,yCAAyC;IACzC,iBAAiB,IAAI,WAAW,CAAC;IACjC,yCAAyC;IACzC,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,WAAW,CAAC;IACtD,8DAA8D;IAC9D,cAAc,IAAI,WAAW,GAAG,IAAI,CAAC;CACxC;AA0GD,mDAAmD;AACnD,eAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,CAQ9D,CAAC;AAEF,sCAAsC;AACtC,eAAO,MAAM,cAAc,EAAE,SAAS,SAAS,EAAoD,CAAC;AAEpG,kDAAkD;AAClD,eAAO,MAAM,aAAa,EAAE,SAAS,SAAS,EAQ7C,CAAC;AAEF,6CAA6C;AAC7C,eAAO,MAAM,YAAY,EAAE,WAAW,CAAC,SAAS,CAAwC,CAAC;AAEzF,gEAAgE;AAChE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,SAAS,CAE7D;AAED,oCAAoC;AACpC,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAExD"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const claudeShim = {
|
|
2
|
+
name: 'claude',
|
|
3
|
+
command: 'claude',
|
|
4
|
+
tier: 1,
|
|
5
|
+
getHelpCommand: () => ({ command: 'claude', args: ['--help'] }),
|
|
6
|
+
getVersionCommand: () => ({ command: 'claude', args: ['--version'] }),
|
|
7
|
+
getPromptCommand: (options) => {
|
|
8
|
+
const args = ['-p', options.input ?? ''];
|
|
9
|
+
if (options.continue === true)
|
|
10
|
+
args.push('--continue');
|
|
11
|
+
if (options.model !== undefined)
|
|
12
|
+
args.push('--model', options.model);
|
|
13
|
+
args.push('--output-format', options.mode ?? 'text');
|
|
14
|
+
return { command: 'claude', args };
|
|
15
|
+
},
|
|
16
|
+
getAuthCommand: () => ({ command: 'claude', args: ['auth', 'status'] }),
|
|
17
|
+
};
|
|
18
|
+
const codexShim = {
|
|
19
|
+
name: 'codex',
|
|
20
|
+
command: 'codex',
|
|
21
|
+
tier: 1,
|
|
22
|
+
getHelpCommand: () => ({ command: 'codex', args: ['--help'] }),
|
|
23
|
+
getVersionCommand: () => ({ command: 'codex', args: ['--version'] }),
|
|
24
|
+
getPromptCommand: (options) => {
|
|
25
|
+
if (options.continue === true && options.input !== undefined) {
|
|
26
|
+
throw new Error('Codex resume mode does not accept a new prompt');
|
|
27
|
+
}
|
|
28
|
+
const args = options.continue === true ? ['exec', 'resume', '--last'] : ['exec', options.input ?? ''];
|
|
29
|
+
if (options.model !== undefined)
|
|
30
|
+
args.push('-m', options.model);
|
|
31
|
+
if ((options.mode ?? 'text') === 'json')
|
|
32
|
+
args.push('--json');
|
|
33
|
+
return { command: 'codex', args };
|
|
34
|
+
},
|
|
35
|
+
getAuthCommand: () => ({ command: 'codex', args: ['login', 'status'] }),
|
|
36
|
+
};
|
|
37
|
+
const geminiShim = {
|
|
38
|
+
name: 'gemini',
|
|
39
|
+
command: 'gemini',
|
|
40
|
+
tier: 1,
|
|
41
|
+
getHelpCommand: () => ({ command: 'gemini', args: ['--help'] }),
|
|
42
|
+
getVersionCommand: () => ({ command: 'gemini', args: ['--version'] }),
|
|
43
|
+
getPromptCommand: (options) => {
|
|
44
|
+
const args = ['-p', options.input ?? ''];
|
|
45
|
+
if (options.continue === true)
|
|
46
|
+
args.push('-r', 'latest');
|
|
47
|
+
if (options.model !== undefined)
|
|
48
|
+
args.push('-m', options.model);
|
|
49
|
+
args.push('-o', options.mode ?? 'text');
|
|
50
|
+
return { command: 'gemini', args };
|
|
51
|
+
},
|
|
52
|
+
getAuthCommand: () => null,
|
|
53
|
+
};
|
|
54
|
+
const piShim = {
|
|
55
|
+
name: 'pi',
|
|
56
|
+
command: 'pi',
|
|
57
|
+
tier: 1,
|
|
58
|
+
getHelpCommand: () => ({ command: 'pi', args: ['--help'] }),
|
|
59
|
+
getVersionCommand: () => ({ command: 'pi', args: ['--version'] }),
|
|
60
|
+
getPromptCommand: (options) => {
|
|
61
|
+
const args = [];
|
|
62
|
+
if (options.continue !== true)
|
|
63
|
+
args.push('--no-session');
|
|
64
|
+
args.push('-p', options.input ?? '');
|
|
65
|
+
if (options.continue === true)
|
|
66
|
+
args.push('-c');
|
|
67
|
+
if (options.model !== undefined)
|
|
68
|
+
args.push('--model', options.model);
|
|
69
|
+
args.push('--mode', options.mode ?? 'text');
|
|
70
|
+
return { command: 'pi', args };
|
|
71
|
+
},
|
|
72
|
+
getAuthCommand: () => ({ command: 'pi', args: ['--list-models'] }),
|
|
73
|
+
};
|
|
74
|
+
const opencodeShim = {
|
|
75
|
+
name: 'opencode',
|
|
76
|
+
command: 'opencode',
|
|
77
|
+
tier: 1,
|
|
78
|
+
getHelpCommand: () => ({ command: 'opencode', args: ['--help'] }),
|
|
79
|
+
getVersionCommand: () => ({ command: 'opencode', args: ['--version'] }),
|
|
80
|
+
getPromptCommand: (options) => {
|
|
81
|
+
const args = ['run', options.input ?? ''];
|
|
82
|
+
if (options.continue === true)
|
|
83
|
+
args.push('-c');
|
|
84
|
+
if (options.model !== undefined)
|
|
85
|
+
args.push('-m', options.model);
|
|
86
|
+
if ((options.mode ?? 'text') === 'json')
|
|
87
|
+
args.push('--format', 'json');
|
|
88
|
+
return { command: 'opencode', args };
|
|
89
|
+
},
|
|
90
|
+
getAuthCommand: () => ({ command: 'opencode', args: ['providers'] }),
|
|
91
|
+
};
|
|
92
|
+
const antigravityShim = {
|
|
93
|
+
name: 'antigravity',
|
|
94
|
+
command: 'agy',
|
|
95
|
+
tier: 2,
|
|
96
|
+
getHelpCommand: () => ({ command: 'agy', args: ['--help'] }),
|
|
97
|
+
getVersionCommand: () => ({ command: 'agy', args: ['--version'] }),
|
|
98
|
+
getPromptCommand: (options) => ({ command: 'agy', args: ['chat', options.input ?? ''] }),
|
|
99
|
+
getAuthCommand: () => null,
|
|
100
|
+
};
|
|
101
|
+
const openclawShim = {
|
|
102
|
+
name: 'openclaw',
|
|
103
|
+
command: 'openclaw',
|
|
104
|
+
tier: 2,
|
|
105
|
+
getHelpCommand: () => ({ command: 'openclaw', args: ['--help'] }),
|
|
106
|
+
getVersionCommand: () => ({ command: 'openclaw', args: ['--version'] }),
|
|
107
|
+
getPromptCommand: (options) => ({ command: 'openclaw', args: ['agent', '--local', '-m', options.input ?? ''] }),
|
|
108
|
+
getAuthCommand: () => ({ command: 'openclaw', args: ['health'] }),
|
|
109
|
+
};
|
|
110
|
+
/** All bundled agent shims keyed by agent name. */
|
|
111
|
+
export const AGENT_SHIMS = {
|
|
112
|
+
claude: claudeShim,
|
|
113
|
+
codex: codexShim,
|
|
114
|
+
gemini: geminiShim,
|
|
115
|
+
pi: piShim,
|
|
116
|
+
opencode: opencodeShim,
|
|
117
|
+
antigravity: antigravityShim,
|
|
118
|
+
openclaw: openclawShim,
|
|
119
|
+
};
|
|
120
|
+
/** Tier-1 auto-selection priority. */
|
|
121
|
+
export const TIER1_PRIORITY = ['pi', 'codex', 'gemini', 'claude', 'opencode'];
|
|
122
|
+
/** Display order for doctor and list commands. */
|
|
123
|
+
export const DISPLAY_ORDER = [
|
|
124
|
+
'claude',
|
|
125
|
+
'codex',
|
|
126
|
+
'gemini',
|
|
127
|
+
'pi',
|
|
128
|
+
'opencode',
|
|
129
|
+
'antigravity',
|
|
130
|
+
'openclaw',
|
|
131
|
+
];
|
|
132
|
+
/** Set of gateway/TUI-constrained agents. */
|
|
133
|
+
export const TIER2_AGENTS = new Set(['antigravity', 'openclaw']);
|
|
134
|
+
/** Return true when a value is a supported agent identifier. */
|
|
135
|
+
export function isAgentName(value) {
|
|
136
|
+
return Object.hasOwn(AGENT_SHIMS, value);
|
|
137
|
+
}
|
|
138
|
+
/** Look up a bundled agent shim. */
|
|
139
|
+
export function getAgentShim(agent) {
|
|
140
|
+
return AGENT_SHIMS[agent];
|
|
141
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type ProcessExecutor } from '@gobing-ai/ts-runtime';
|
|
2
|
+
import { type AgentName, type PromptOptions } from './agents/shims';
|
|
3
|
+
/** Result returned by every AI runner dispatch method. */
|
|
4
|
+
export interface AgentRunResult {
|
|
5
|
+
/** Process exit code; null indicates signal or timeout termination. */
|
|
6
|
+
exitCode: number | null;
|
|
7
|
+
/** Captured stdout. */
|
|
8
|
+
stdout: string;
|
|
9
|
+
/** Captured stderr. */
|
|
10
|
+
stderr: string;
|
|
11
|
+
/** Signal description when the process was terminated by signal. */
|
|
12
|
+
signal?: string;
|
|
13
|
+
/** Wall-clock duration in milliseconds. */
|
|
14
|
+
durationMs: number;
|
|
15
|
+
}
|
|
16
|
+
/** Per-invocation runtime options. */
|
|
17
|
+
export interface AgentRunOptions {
|
|
18
|
+
/** Working directory for the invocation. */
|
|
19
|
+
cwd?: string;
|
|
20
|
+
/** Timeout in milliseconds. */
|
|
21
|
+
timeout?: number;
|
|
22
|
+
}
|
|
23
|
+
/** Constructor options for AiRunner. */
|
|
24
|
+
export interface AiRunnerOptions {
|
|
25
|
+
/** Process executor used for all subprocess invocations. */
|
|
26
|
+
processExecutor?: ProcessExecutor;
|
|
27
|
+
/** Default working directory for invocations. */
|
|
28
|
+
defaultCwd?: string;
|
|
29
|
+
/** Default timeout in milliseconds. */
|
|
30
|
+
defaultTimeout?: number;
|
|
31
|
+
}
|
|
32
|
+
/** Dispatches coding-agent CLI commands through pure command shims. */
|
|
33
|
+
export declare class AiRunner {
|
|
34
|
+
private readonly processExecutor;
|
|
35
|
+
private readonly defaultCwd;
|
|
36
|
+
private readonly defaultTimeout;
|
|
37
|
+
constructor(options?: AiRunnerOptions);
|
|
38
|
+
/** Run an agent help command. */
|
|
39
|
+
runHelpCommand(agent: AgentName, options?: AgentRunOptions): Promise<AgentRunResult>;
|
|
40
|
+
/** Run an agent version command. */
|
|
41
|
+
runVersionCommand(agent: AgentName, options?: AgentRunOptions): Promise<AgentRunResult>;
|
|
42
|
+
/** Run an agent prompt command. */
|
|
43
|
+
runPromptCommand(agent: AgentName, promptOptions: PromptOptions, options?: AgentRunOptions): Promise<AgentRunResult>;
|
|
44
|
+
/** Run an agent authentication command, or return null when unsupported. */
|
|
45
|
+
runAuthCommand(agent: AgentName, options?: AgentRunOptions): Promise<AgentRunResult> | null;
|
|
46
|
+
private invoke;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=ai-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-runner.d.ts","sourceRoot":"","sources":["../src/ai-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,eAAe,EAAsB,MAAM,uBAAuB,CAAC;AACtG,OAAO,EAAE,KAAK,SAAS,EAAgB,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAElF,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC3B,uEAAuE;IACvE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,sCAAsC;AACtC,MAAM,WAAW,eAAe;IAC5B,4CAA4C;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wCAAwC;AACxC,MAAM,WAAW,eAAe;IAC5B,4DAA4D;IAC5D,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,uEAAuE;AACvE,qBAAa,QAAQ;IACjB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;gBAExC,OAAO,GAAE,eAAoB;IAMzC,iCAAiC;IACjC,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC;IAIxF,oCAAoC;IACpC,iBAAiB,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC;IAI3F,mCAAmC;IACnC,gBAAgB,CACZ,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,aAAa,EAC5B,OAAO,GAAE,eAAoB,GAC9B,OAAO,CAAC,cAAc,CAAC;IAI1B,4EAA4E;IAC5E,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI;YAKjF,MAAM;CAwBvB"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { NodeProcessExecutor } from '@gobing-ai/ts-runtime';
|
|
2
|
+
import { getAgentShim } from './agents/shims.js';
|
|
3
|
+
/** Dispatches coding-agent CLI commands through pure command shims. */
|
|
4
|
+
export class AiRunner {
|
|
5
|
+
processExecutor;
|
|
6
|
+
defaultCwd;
|
|
7
|
+
defaultTimeout;
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.processExecutor = options.processExecutor ?? new NodeProcessExecutor();
|
|
10
|
+
this.defaultCwd = options.defaultCwd;
|
|
11
|
+
this.defaultTimeout = options.defaultTimeout;
|
|
12
|
+
}
|
|
13
|
+
/** Run an agent help command. */
|
|
14
|
+
runHelpCommand(agent, options = {}) {
|
|
15
|
+
return this.invoke(agent, 'help', getAgentShim(agent).getHelpCommand(), options, true);
|
|
16
|
+
}
|
|
17
|
+
/** Run an agent version command. */
|
|
18
|
+
runVersionCommand(agent, options = {}) {
|
|
19
|
+
return this.invoke(agent, 'version', getAgentShim(agent).getVersionCommand(), options, true);
|
|
20
|
+
}
|
|
21
|
+
/** Run an agent prompt command. */
|
|
22
|
+
runPromptCommand(agent, promptOptions, options = {}) {
|
|
23
|
+
return this.invoke(agent, 'prompt', getAgentShim(agent).getPromptCommand(promptOptions), options, false);
|
|
24
|
+
}
|
|
25
|
+
/** Run an agent authentication command, or return null when unsupported. */
|
|
26
|
+
runAuthCommand(agent, options = {}) {
|
|
27
|
+
const command = getAgentShim(agent).getAuthCommand();
|
|
28
|
+
return command === null ? null : this.invoke(agent, 'auth', command, options, true);
|
|
29
|
+
}
|
|
30
|
+
async invoke(agent, operation, command, options, forceBuffered) {
|
|
31
|
+
const result = await this.processExecutor.run({
|
|
32
|
+
command: command.command,
|
|
33
|
+
args: command.args,
|
|
34
|
+
label: `ai-runner.${agent}.${operation}`,
|
|
35
|
+
rejectOnError: false,
|
|
36
|
+
forceBuffered,
|
|
37
|
+
cwd: options.cwd ?? this.defaultCwd,
|
|
38
|
+
timeout: options.timeout ?? this.defaultTimeout,
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
exitCode: result.exitCode,
|
|
42
|
+
stdout: result.stdout,
|
|
43
|
+
stderr: result.stderr,
|
|
44
|
+
...(result.signal !== undefined ? { signal: result.signal } : {}),
|
|
45
|
+
durationMs: result.durationMs,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { AgentDetector } from './agent-detector';
|
|
2
|
+
import { AiRunner } from './ai-runner';
|
|
3
|
+
/** Health-check result for one coding agent. */
|
|
4
|
+
export interface DoctorResult {
|
|
5
|
+
/** Agent identifier. */
|
|
6
|
+
agent: string;
|
|
7
|
+
/** Whether the CLI is installed. */
|
|
8
|
+
installed: boolean;
|
|
9
|
+
/** Human-readable version when installed. */
|
|
10
|
+
version: string | null;
|
|
11
|
+
/** Whether the agent appears authenticated. */
|
|
12
|
+
authenticated: boolean;
|
|
13
|
+
/** Whether the agent is ready for direct use. */
|
|
14
|
+
usable: boolean;
|
|
15
|
+
/** Capability tier: 1 = direct CLI, 2 = gateway/TUI constrained. */
|
|
16
|
+
tier: 1 | 2;
|
|
17
|
+
/** Agent-specific channels or models when available. */
|
|
18
|
+
channels: string[];
|
|
19
|
+
/** Probe error when unavailable. */
|
|
20
|
+
error: string | null;
|
|
21
|
+
}
|
|
22
|
+
/** Constructor options for DoctorRunner. */
|
|
23
|
+
export interface DoctorRunnerOptions {
|
|
24
|
+
/** Shared detector. */
|
|
25
|
+
agentDetector?: AgentDetector;
|
|
26
|
+
/** Shared runner. */
|
|
27
|
+
runner?: AiRunner;
|
|
28
|
+
/** Auth probe timeout in milliseconds. */
|
|
29
|
+
timeout?: number;
|
|
30
|
+
/** Environment map for file/env auth checks. */
|
|
31
|
+
env?: Record<string, string | undefined>;
|
|
32
|
+
}
|
|
33
|
+
/** Runs installation and auth health checks for supported coding agents. */
|
|
34
|
+
export declare class DoctorRunner {
|
|
35
|
+
private readonly detector;
|
|
36
|
+
private readonly runner;
|
|
37
|
+
private readonly timeout;
|
|
38
|
+
private readonly env;
|
|
39
|
+
private readonly fs;
|
|
40
|
+
constructor(options?: DoctorRunnerOptions);
|
|
41
|
+
/** Run a health check on all supported agents. */
|
|
42
|
+
runAll(): Promise<DoctorResult[]>;
|
|
43
|
+
/** Run a health check on one agent. */
|
|
44
|
+
runOne(agent: string): Promise<DoctorResult>;
|
|
45
|
+
private buildResult;
|
|
46
|
+
private checkAuth;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=doctor-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-runner.d.ts","sourceRoot":"","sources":["../src/doctor-runner.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAsB,MAAM,kBAAkB,CAAC;AAErE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,gDAAgD;AAChD,MAAM,WAAW,YAAY;IACzB,wBAAwB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,+CAA+C;IAC/C,aAAa,EAAE,OAAO,CAAC;IACvB,iDAAiD;IACjD,MAAM,EAAE,OAAO,CAAC;IAChB,oEAAoE;IACpE,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;IACZ,wDAAwD;IACxD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,oCAAoC;IACpC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,4CAA4C;AAC5C,MAAM,WAAW,mBAAmB;IAChC,uBAAuB;IACvB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,qBAAqB;IACrB,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC5C;AAID,4EAA4E;AAC5E,qBAAa,YAAY;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqC;IACzD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAwB;gBAE/B,OAAO,GAAE,mBAAwB;IAO7C,kDAAkD;IAC5C,MAAM,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAKvC,uCAAuC;IACjC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;YAIpC,WAAW;YAgBX,SAAS;CAa1B"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { getProcessEnv, NodeFileSystem } from '@gobing-ai/ts-runtime';
|
|
4
|
+
import { AgentDetector } from './agent-detector.js';
|
|
5
|
+
import { isAgentName, TIER2_AGENTS } from './agents/shims.js';
|
|
6
|
+
import { AiRunner } from './ai-runner.js';
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
8
|
+
/** Runs installation and auth health checks for supported coding agents. */
|
|
9
|
+
export class DoctorRunner {
|
|
10
|
+
detector;
|
|
11
|
+
runner;
|
|
12
|
+
timeout;
|
|
13
|
+
env;
|
|
14
|
+
fs = new NodeFileSystem();
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.runner = options.runner ?? new AiRunner();
|
|
17
|
+
this.detector = options.agentDetector ?? new AgentDetector({ runner: this.runner });
|
|
18
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
19
|
+
this.env = options.env ?? getProcessEnv();
|
|
20
|
+
}
|
|
21
|
+
/** Run a health check on all supported agents. */
|
|
22
|
+
async runAll() {
|
|
23
|
+
const detected = await this.detector.detectAll();
|
|
24
|
+
return await Promise.all(detected.map((agent) => this.buildResult(agent)));
|
|
25
|
+
}
|
|
26
|
+
/** Run a health check on one agent. */
|
|
27
|
+
async runOne(agent) {
|
|
28
|
+
return this.buildResult(await this.detector.detectOne(agent));
|
|
29
|
+
}
|
|
30
|
+
async buildResult(detected) {
|
|
31
|
+
const tier = TIER2_AGENTS.has(detected.name) ? 2 : 1;
|
|
32
|
+
const authenticated = detected.installed && isAgentName(detected.name) ? await this.checkAuth(detected.name) : false;
|
|
33
|
+
return {
|
|
34
|
+
agent: detected.name,
|
|
35
|
+
installed: detected.installed,
|
|
36
|
+
version: detected.version,
|
|
37
|
+
authenticated,
|
|
38
|
+
usable: detected.installed && detected.version !== null && authenticated,
|
|
39
|
+
tier,
|
|
40
|
+
channels: detected.channels,
|
|
41
|
+
error: detected.error,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async checkAuth(agent) {
|
|
45
|
+
if (agent === 'gemini')
|
|
46
|
+
return this.fs.exists(join(homedir(), '.gemini', 'settings.json'));
|
|
47
|
+
if (agent === 'codex' && (await this.fs.exists(join(homedir(), '.codex', 'auth.json'))))
|
|
48
|
+
return true;
|
|
49
|
+
if (agent === 'pi' && (this.env.GOOGLE_API_KEY !== undefined || this.env.ANTHROPIC_API_KEY !== undefined))
|
|
50
|
+
return true;
|
|
51
|
+
const command = this.runner.runAuthCommand(agent, { timeout: this.timeout });
|
|
52
|
+
if (command === null)
|
|
53
|
+
return false;
|
|
54
|
+
const result = await command;
|
|
55
|
+
return (result.exitCode === 0 &&
|
|
56
|
+
!/not authenticated|not logged|unauthenticated/i.test(`${result.stdout}\n${result.stderr}`));
|
|
57
|
+
}
|
|
58
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gobing-ai/ts-ai-runner",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "@gobing-ai/ts-ai-runner — Coding-agent shims, detection, doctor checks, and prompt execution.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"typescript",
|
|
7
|
+
"ai",
|
|
8
|
+
"agent",
|
|
9
|
+
"cli",
|
|
10
|
+
"runner"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/gobing-ai/ts-libs.git",
|
|
15
|
+
"directory": "packages/ai-runner"
|
|
16
|
+
},
|
|
17
|
+
"author": "Robin Min <minlongbing@gmail.com>",
|
|
18
|
+
"contributors": [
|
|
19
|
+
"Robin Min <minlongbing@gmail.com>"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"private": false,
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"license": "Apache-2.0",
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"src",
|
|
36
|
+
"README.md"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.build.json && bun ../../scripts/builder.ts fix-dist-esm-extensions dist",
|
|
40
|
+
"test": "NODE_ENV=test bun test --coverage --coverage-dir=.coverage --reporter=dots",
|
|
41
|
+
"test:full": "NODE_ENV=test bun test --update-snapshots --coverage --coverage-dir=.coverage",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"lint": "biome check . && bun run typecheck",
|
|
44
|
+
"format": "biome check . --write",
|
|
45
|
+
"check": "bun run lint && bun run test",
|
|
46
|
+
"prepublishOnly": "bun run build",
|
|
47
|
+
"release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-ai-runner-v<version> && git push --tags' && exit 1"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@gobing-ai/ts-runtime": "workspace:*"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/bun": "1.3.14"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { type AgentName, DISPLAY_ORDER, getAgentShim, isAgentName } from './agents/shims';
|
|
2
|
+
import { type AgentRunResult, AiRunner } from './ai-runner';
|
|
3
|
+
|
|
4
|
+
/** Installation/version probe result for one coding agent. */
|
|
5
|
+
export interface DetectedAgent {
|
|
6
|
+
/** Agent identifier. */
|
|
7
|
+
name: AgentName | string;
|
|
8
|
+
/** Whether the agent CLI was found and returned a parseable version. */
|
|
9
|
+
installed: boolean;
|
|
10
|
+
/** First version-output line when installed. */
|
|
11
|
+
version: string | null;
|
|
12
|
+
/** Agent-specific channels or models when available. */
|
|
13
|
+
channels: string[];
|
|
14
|
+
/** Detection error when unavailable. */
|
|
15
|
+
error: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Constructor options for AgentDetector. */
|
|
19
|
+
export interface AgentDetectorOptions {
|
|
20
|
+
/** Shared AiRunner instance. */
|
|
21
|
+
runner?: AiRunner;
|
|
22
|
+
/** Per-agent version probe timeout in milliseconds. */
|
|
23
|
+
timeout?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
27
|
+
const VERSION_PATTERN = /(?<version>\d+\.\d+(?:\.\d+)?)/;
|
|
28
|
+
|
|
29
|
+
/** Probes supported coding-agent CLIs for installation and version state. */
|
|
30
|
+
export class AgentDetector {
|
|
31
|
+
private readonly runner: AiRunner;
|
|
32
|
+
private readonly timeout: number;
|
|
33
|
+
|
|
34
|
+
constructor(options: AgentDetectorOptions = {}) {
|
|
35
|
+
this.runner = options.runner ?? new AiRunner();
|
|
36
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Probe all bundled agents in display order. */
|
|
40
|
+
async detectAll(): Promise<DetectedAgent[]> {
|
|
41
|
+
return await Promise.all(DISPLAY_ORDER.map((agent) => this.detectOne(agent)));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Probe one agent by name. */
|
|
45
|
+
async detectOne(agent: string): Promise<DetectedAgent> {
|
|
46
|
+
if (!isAgentName(agent)) {
|
|
47
|
+
return { name: agent, installed: false, version: null, channels: [], error: `Unknown agent: ${agent}` };
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return this.parseResult(agent, await this.runner.runVersionCommand(agent, { timeout: this.timeout }));
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return {
|
|
53
|
+
name: agent,
|
|
54
|
+
installed: false,
|
|
55
|
+
version: null,
|
|
56
|
+
channels: [],
|
|
57
|
+
error: error instanceof Error ? error.message : String(error),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private parseResult(agent: AgentName, result: AgentRunResult): DetectedAgent {
|
|
63
|
+
const command = getAgentShim(agent).command;
|
|
64
|
+
const output = `${result.stdout}\n${result.stderr}`.trim();
|
|
65
|
+
const lower = output.toLowerCase();
|
|
66
|
+
if (lower.includes('command not found') || lower.includes('enoent') || lower.includes('not recognized')) {
|
|
67
|
+
return {
|
|
68
|
+
name: agent,
|
|
69
|
+
installed: false,
|
|
70
|
+
version: null,
|
|
71
|
+
channels: [],
|
|
72
|
+
error: `${command}: command not found`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (result.signal !== undefined || result.exitCode === null) {
|
|
76
|
+
return {
|
|
77
|
+
name: agent,
|
|
78
|
+
installed: false,
|
|
79
|
+
version: null,
|
|
80
|
+
channels: [],
|
|
81
|
+
error: result.signal ?? 'Process timed out',
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (result.exitCode !== 0) {
|
|
85
|
+
return {
|
|
86
|
+
name: agent,
|
|
87
|
+
installed: false,
|
|
88
|
+
version: null,
|
|
89
|
+
channels: [],
|
|
90
|
+
error: `Non-zero exit code ${result.exitCode}: ${result.stderr.slice(0, 200)}`,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const match = VERSION_PATTERN.exec(output);
|
|
94
|
+
if (match?.groups?.version === undefined) {
|
|
95
|
+
return {
|
|
96
|
+
name: agent,
|
|
97
|
+
installed: false,
|
|
98
|
+
version: null,
|
|
99
|
+
channels: [],
|
|
100
|
+
error: 'Could not parse version output',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
name: agent,
|
|
105
|
+
installed: true,
|
|
106
|
+
version: output.split('\n')[0] ?? match.groups.version,
|
|
107
|
+
channels: [],
|
|
108
|
+
error: null,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/** Identifier for one supported coding agent. */
|
|
2
|
+
export type AgentName = 'claude' | 'codex' | 'gemini' | 'pi' | 'opencode' | 'antigravity' | 'openclaw';
|
|
3
|
+
|
|
4
|
+
/** Output mode for prompt invocations. */
|
|
5
|
+
export type OutputMode = 'text' | 'json';
|
|
6
|
+
|
|
7
|
+
/** Concrete executable and argv pair returned by an agent shim. */
|
|
8
|
+
export interface ShimCommand {
|
|
9
|
+
/** Executable to invoke. */
|
|
10
|
+
command: string;
|
|
11
|
+
/** Arguments to pass to the executable. */
|
|
12
|
+
args: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Options for prompt-style invocations. */
|
|
16
|
+
export interface PromptOptions {
|
|
17
|
+
/** Prompt text or slash command to send to the agent. */
|
|
18
|
+
input?: string;
|
|
19
|
+
/** Continue the previous session if the agent supports it. */
|
|
20
|
+
continue?: boolean;
|
|
21
|
+
/** Model identifier passed through to the agent CLI. */
|
|
22
|
+
model?: string;
|
|
23
|
+
/** Output mode passed through to the agent CLI. */
|
|
24
|
+
mode?: OutputMode;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Pure command builder for one coding-agent CLI. */
|
|
28
|
+
export interface AgentShim {
|
|
29
|
+
/** Stable agent identifier. */
|
|
30
|
+
readonly name: AgentName;
|
|
31
|
+
/** Executable command name. */
|
|
32
|
+
readonly command: string;
|
|
33
|
+
/** Capability tier: 1 = direct CLI, 2 = gateway/TUI constrained. */
|
|
34
|
+
readonly tier: 1 | 2;
|
|
35
|
+
/** Build a help-display command. */
|
|
36
|
+
getHelpCommand(): ShimCommand;
|
|
37
|
+
/** Build a version-detection command. */
|
|
38
|
+
getVersionCommand(): ShimCommand;
|
|
39
|
+
/** Build a prompt invocation command. */
|
|
40
|
+
getPromptCommand(options: PromptOptions): ShimCommand;
|
|
41
|
+
/** Build an auth-status command, or null when unsupported. */
|
|
42
|
+
getAuthCommand(): ShimCommand | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const claudeShim: AgentShim = {
|
|
46
|
+
name: 'claude',
|
|
47
|
+
command: 'claude',
|
|
48
|
+
tier: 1,
|
|
49
|
+
getHelpCommand: () => ({ command: 'claude', args: ['--help'] }),
|
|
50
|
+
getVersionCommand: () => ({ command: 'claude', args: ['--version'] }),
|
|
51
|
+
getPromptCommand: (options) => {
|
|
52
|
+
const args = ['-p', options.input ?? ''];
|
|
53
|
+
if (options.continue === true) args.push('--continue');
|
|
54
|
+
if (options.model !== undefined) args.push('--model', options.model);
|
|
55
|
+
args.push('--output-format', options.mode ?? 'text');
|
|
56
|
+
return { command: 'claude', args };
|
|
57
|
+
},
|
|
58
|
+
getAuthCommand: () => ({ command: 'claude', args: ['auth', 'status'] }),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const codexShim: AgentShim = {
|
|
62
|
+
name: 'codex',
|
|
63
|
+
command: 'codex',
|
|
64
|
+
tier: 1,
|
|
65
|
+
getHelpCommand: () => ({ command: 'codex', args: ['--help'] }),
|
|
66
|
+
getVersionCommand: () => ({ command: 'codex', args: ['--version'] }),
|
|
67
|
+
getPromptCommand: (options) => {
|
|
68
|
+
if (options.continue === true && options.input !== undefined) {
|
|
69
|
+
throw new Error('Codex resume mode does not accept a new prompt');
|
|
70
|
+
}
|
|
71
|
+
const args = options.continue === true ? ['exec', 'resume', '--last'] : ['exec', options.input ?? ''];
|
|
72
|
+
if (options.model !== undefined) args.push('-m', options.model);
|
|
73
|
+
if ((options.mode ?? 'text') === 'json') args.push('--json');
|
|
74
|
+
return { command: 'codex', args };
|
|
75
|
+
},
|
|
76
|
+
getAuthCommand: () => ({ command: 'codex', args: ['login', 'status'] }),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const geminiShim: AgentShim = {
|
|
80
|
+
name: 'gemini',
|
|
81
|
+
command: 'gemini',
|
|
82
|
+
tier: 1,
|
|
83
|
+
getHelpCommand: () => ({ command: 'gemini', args: ['--help'] }),
|
|
84
|
+
getVersionCommand: () => ({ command: 'gemini', args: ['--version'] }),
|
|
85
|
+
getPromptCommand: (options) => {
|
|
86
|
+
const args = ['-p', options.input ?? ''];
|
|
87
|
+
if (options.continue === true) args.push('-r', 'latest');
|
|
88
|
+
if (options.model !== undefined) args.push('-m', options.model);
|
|
89
|
+
args.push('-o', options.mode ?? 'text');
|
|
90
|
+
return { command: 'gemini', args };
|
|
91
|
+
},
|
|
92
|
+
getAuthCommand: () => null,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const piShim: AgentShim = {
|
|
96
|
+
name: 'pi',
|
|
97
|
+
command: 'pi',
|
|
98
|
+
tier: 1,
|
|
99
|
+
getHelpCommand: () => ({ command: 'pi', args: ['--help'] }),
|
|
100
|
+
getVersionCommand: () => ({ command: 'pi', args: ['--version'] }),
|
|
101
|
+
getPromptCommand: (options) => {
|
|
102
|
+
const args: string[] = [];
|
|
103
|
+
if (options.continue !== true) args.push('--no-session');
|
|
104
|
+
args.push('-p', options.input ?? '');
|
|
105
|
+
if (options.continue === true) args.push('-c');
|
|
106
|
+
if (options.model !== undefined) args.push('--model', options.model);
|
|
107
|
+
args.push('--mode', options.mode ?? 'text');
|
|
108
|
+
return { command: 'pi', args };
|
|
109
|
+
},
|
|
110
|
+
getAuthCommand: () => ({ command: 'pi', args: ['--list-models'] }),
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const opencodeShim: AgentShim = {
|
|
114
|
+
name: 'opencode',
|
|
115
|
+
command: 'opencode',
|
|
116
|
+
tier: 1,
|
|
117
|
+
getHelpCommand: () => ({ command: 'opencode', args: ['--help'] }),
|
|
118
|
+
getVersionCommand: () => ({ command: 'opencode', args: ['--version'] }),
|
|
119
|
+
getPromptCommand: (options) => {
|
|
120
|
+
const args = ['run', options.input ?? ''];
|
|
121
|
+
if (options.continue === true) args.push('-c');
|
|
122
|
+
if (options.model !== undefined) args.push('-m', options.model);
|
|
123
|
+
if ((options.mode ?? 'text') === 'json') args.push('--format', 'json');
|
|
124
|
+
return { command: 'opencode', args };
|
|
125
|
+
},
|
|
126
|
+
getAuthCommand: () => ({ command: 'opencode', args: ['providers'] }),
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const antigravityShim: AgentShim = {
|
|
130
|
+
name: 'antigravity',
|
|
131
|
+
command: 'agy',
|
|
132
|
+
tier: 2,
|
|
133
|
+
getHelpCommand: () => ({ command: 'agy', args: ['--help'] }),
|
|
134
|
+
getVersionCommand: () => ({ command: 'agy', args: ['--version'] }),
|
|
135
|
+
getPromptCommand: (options) => ({ command: 'agy', args: ['chat', options.input ?? ''] }),
|
|
136
|
+
getAuthCommand: () => null,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const openclawShim: AgentShim = {
|
|
140
|
+
name: 'openclaw',
|
|
141
|
+
command: 'openclaw',
|
|
142
|
+
tier: 2,
|
|
143
|
+
getHelpCommand: () => ({ command: 'openclaw', args: ['--help'] }),
|
|
144
|
+
getVersionCommand: () => ({ command: 'openclaw', args: ['--version'] }),
|
|
145
|
+
getPromptCommand: (options) => ({ command: 'openclaw', args: ['agent', '--local', '-m', options.input ?? ''] }),
|
|
146
|
+
getAuthCommand: () => ({ command: 'openclaw', args: ['health'] }),
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/** All bundled agent shims keyed by agent name. */
|
|
150
|
+
export const AGENT_SHIMS: Readonly<Record<AgentName, AgentShim>> = {
|
|
151
|
+
claude: claudeShim,
|
|
152
|
+
codex: codexShim,
|
|
153
|
+
gemini: geminiShim,
|
|
154
|
+
pi: piShim,
|
|
155
|
+
opencode: opencodeShim,
|
|
156
|
+
antigravity: antigravityShim,
|
|
157
|
+
openclaw: openclawShim,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/** Tier-1 auto-selection priority. */
|
|
161
|
+
export const TIER1_PRIORITY: readonly AgentName[] = ['pi', 'codex', 'gemini', 'claude', 'opencode'];
|
|
162
|
+
|
|
163
|
+
/** Display order for doctor and list commands. */
|
|
164
|
+
export const DISPLAY_ORDER: readonly AgentName[] = [
|
|
165
|
+
'claude',
|
|
166
|
+
'codex',
|
|
167
|
+
'gemini',
|
|
168
|
+
'pi',
|
|
169
|
+
'opencode',
|
|
170
|
+
'antigravity',
|
|
171
|
+
'openclaw',
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
/** Set of gateway/TUI-constrained agents. */
|
|
175
|
+
export const TIER2_AGENTS: ReadonlySet<AgentName> = new Set(['antigravity', 'openclaw']);
|
|
176
|
+
|
|
177
|
+
/** Return true when a value is a supported agent identifier. */
|
|
178
|
+
export function isAgentName(value: string): value is AgentName {
|
|
179
|
+
return Object.hasOwn(AGENT_SHIMS, value);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Look up a bundled agent shim. */
|
|
183
|
+
export function getAgentShim(agent: AgentName): AgentShim {
|
|
184
|
+
return AGENT_SHIMS[agent];
|
|
185
|
+
}
|
package/src/ai-runner.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { NodeProcessExecutor, type ProcessExecutor, type ProcessResult } from '@gobing-ai/ts-runtime';
|
|
2
|
+
import { type AgentName, getAgentShim, type PromptOptions } from './agents/shims';
|
|
3
|
+
|
|
4
|
+
/** Result returned by every AI runner dispatch method. */
|
|
5
|
+
export interface AgentRunResult {
|
|
6
|
+
/** Process exit code; null indicates signal or timeout termination. */
|
|
7
|
+
exitCode: number | null;
|
|
8
|
+
/** Captured stdout. */
|
|
9
|
+
stdout: string;
|
|
10
|
+
/** Captured stderr. */
|
|
11
|
+
stderr: string;
|
|
12
|
+
/** Signal description when the process was terminated by signal. */
|
|
13
|
+
signal?: string;
|
|
14
|
+
/** Wall-clock duration in milliseconds. */
|
|
15
|
+
durationMs: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Per-invocation runtime options. */
|
|
19
|
+
export interface AgentRunOptions {
|
|
20
|
+
/** Working directory for the invocation. */
|
|
21
|
+
cwd?: string;
|
|
22
|
+
/** Timeout in milliseconds. */
|
|
23
|
+
timeout?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Constructor options for AiRunner. */
|
|
27
|
+
export interface AiRunnerOptions {
|
|
28
|
+
/** Process executor used for all subprocess invocations. */
|
|
29
|
+
processExecutor?: ProcessExecutor;
|
|
30
|
+
/** Default working directory for invocations. */
|
|
31
|
+
defaultCwd?: string;
|
|
32
|
+
/** Default timeout in milliseconds. */
|
|
33
|
+
defaultTimeout?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Dispatches coding-agent CLI commands through pure command shims. */
|
|
37
|
+
export class AiRunner {
|
|
38
|
+
private readonly processExecutor: ProcessExecutor;
|
|
39
|
+
private readonly defaultCwd: string | undefined;
|
|
40
|
+
private readonly defaultTimeout: number | undefined;
|
|
41
|
+
|
|
42
|
+
constructor(options: AiRunnerOptions = {}) {
|
|
43
|
+
this.processExecutor = options.processExecutor ?? new NodeProcessExecutor();
|
|
44
|
+
this.defaultCwd = options.defaultCwd;
|
|
45
|
+
this.defaultTimeout = options.defaultTimeout;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Run an agent help command. */
|
|
49
|
+
runHelpCommand(agent: AgentName, options: AgentRunOptions = {}): Promise<AgentRunResult> {
|
|
50
|
+
return this.invoke(agent, 'help', getAgentShim(agent).getHelpCommand(), options, true);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Run an agent version command. */
|
|
54
|
+
runVersionCommand(agent: AgentName, options: AgentRunOptions = {}): Promise<AgentRunResult> {
|
|
55
|
+
return this.invoke(agent, 'version', getAgentShim(agent).getVersionCommand(), options, true);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Run an agent prompt command. */
|
|
59
|
+
runPromptCommand(
|
|
60
|
+
agent: AgentName,
|
|
61
|
+
promptOptions: PromptOptions,
|
|
62
|
+
options: AgentRunOptions = {},
|
|
63
|
+
): Promise<AgentRunResult> {
|
|
64
|
+
return this.invoke(agent, 'prompt', getAgentShim(agent).getPromptCommand(promptOptions), options, false);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Run an agent authentication command, or return null when unsupported. */
|
|
68
|
+
runAuthCommand(agent: AgentName, options: AgentRunOptions = {}): Promise<AgentRunResult> | null {
|
|
69
|
+
const command = getAgentShim(agent).getAuthCommand();
|
|
70
|
+
return command === null ? null : this.invoke(agent, 'auth', command, options, true);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private async invoke(
|
|
74
|
+
agent: AgentName,
|
|
75
|
+
operation: string,
|
|
76
|
+
command: { command: string; args: string[] },
|
|
77
|
+
options: AgentRunOptions,
|
|
78
|
+
forceBuffered: boolean,
|
|
79
|
+
): Promise<AgentRunResult> {
|
|
80
|
+
const result: ProcessResult = await this.processExecutor.run({
|
|
81
|
+
command: command.command,
|
|
82
|
+
args: command.args,
|
|
83
|
+
label: `ai-runner.${agent}.${operation}`,
|
|
84
|
+
rejectOnError: false,
|
|
85
|
+
forceBuffered,
|
|
86
|
+
cwd: options.cwd ?? this.defaultCwd,
|
|
87
|
+
timeout: options.timeout ?? this.defaultTimeout,
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
exitCode: result.exitCode,
|
|
91
|
+
stdout: result.stdout,
|
|
92
|
+
stderr: result.stderr,
|
|
93
|
+
...(result.signal !== undefined ? { signal: result.signal } : {}),
|
|
94
|
+
durationMs: result.durationMs,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { getProcessEnv, NodeFileSystem } from '@gobing-ai/ts-runtime';
|
|
4
|
+
import { AgentDetector, type DetectedAgent } from './agent-detector';
|
|
5
|
+
import { type AgentName, isAgentName, TIER2_AGENTS } from './agents/shims';
|
|
6
|
+
import { AiRunner } from './ai-runner';
|
|
7
|
+
|
|
8
|
+
/** Health-check result for one coding agent. */
|
|
9
|
+
export interface DoctorResult {
|
|
10
|
+
/** Agent identifier. */
|
|
11
|
+
agent: string;
|
|
12
|
+
/** Whether the CLI is installed. */
|
|
13
|
+
installed: boolean;
|
|
14
|
+
/** Human-readable version when installed. */
|
|
15
|
+
version: string | null;
|
|
16
|
+
/** Whether the agent appears authenticated. */
|
|
17
|
+
authenticated: boolean;
|
|
18
|
+
/** Whether the agent is ready for direct use. */
|
|
19
|
+
usable: boolean;
|
|
20
|
+
/** Capability tier: 1 = direct CLI, 2 = gateway/TUI constrained. */
|
|
21
|
+
tier: 1 | 2;
|
|
22
|
+
/** Agent-specific channels or models when available. */
|
|
23
|
+
channels: string[];
|
|
24
|
+
/** Probe error when unavailable. */
|
|
25
|
+
error: string | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Constructor options for DoctorRunner. */
|
|
29
|
+
export interface DoctorRunnerOptions {
|
|
30
|
+
/** Shared detector. */
|
|
31
|
+
agentDetector?: AgentDetector;
|
|
32
|
+
/** Shared runner. */
|
|
33
|
+
runner?: AiRunner;
|
|
34
|
+
/** Auth probe timeout in milliseconds. */
|
|
35
|
+
timeout?: number;
|
|
36
|
+
/** Environment map for file/env auth checks. */
|
|
37
|
+
env?: Record<string, string | undefined>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
41
|
+
|
|
42
|
+
/** Runs installation and auth health checks for supported coding agents. */
|
|
43
|
+
export class DoctorRunner {
|
|
44
|
+
private readonly detector: AgentDetector;
|
|
45
|
+
private readonly runner: AiRunner;
|
|
46
|
+
private readonly timeout: number;
|
|
47
|
+
private readonly env: Record<string, string | undefined>;
|
|
48
|
+
private readonly fs = new NodeFileSystem();
|
|
49
|
+
|
|
50
|
+
constructor(options: DoctorRunnerOptions = {}) {
|
|
51
|
+
this.runner = options.runner ?? new AiRunner();
|
|
52
|
+
this.detector = options.agentDetector ?? new AgentDetector({ runner: this.runner });
|
|
53
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
54
|
+
this.env = options.env ?? getProcessEnv();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Run a health check on all supported agents. */
|
|
58
|
+
async runAll(): Promise<DoctorResult[]> {
|
|
59
|
+
const detected = await this.detector.detectAll();
|
|
60
|
+
return await Promise.all(detected.map((agent) => this.buildResult(agent)));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Run a health check on one agent. */
|
|
64
|
+
async runOne(agent: string): Promise<DoctorResult> {
|
|
65
|
+
return this.buildResult(await this.detector.detectOne(agent));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private async buildResult(detected: DetectedAgent): Promise<DoctorResult> {
|
|
69
|
+
const tier = TIER2_AGENTS.has(detected.name as AgentName) ? 2 : 1;
|
|
70
|
+
const authenticated =
|
|
71
|
+
detected.installed && isAgentName(detected.name) ? await this.checkAuth(detected.name) : false;
|
|
72
|
+
return {
|
|
73
|
+
agent: detected.name,
|
|
74
|
+
installed: detected.installed,
|
|
75
|
+
version: detected.version,
|
|
76
|
+
authenticated,
|
|
77
|
+
usable: detected.installed && detected.version !== null && authenticated,
|
|
78
|
+
tier,
|
|
79
|
+
channels: detected.channels,
|
|
80
|
+
error: detected.error,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private async checkAuth(agent: AgentName): Promise<boolean> {
|
|
85
|
+
if (agent === 'gemini') return this.fs.exists(join(homedir(), '.gemini', 'settings.json'));
|
|
86
|
+
if (agent === 'codex' && (await this.fs.exists(join(homedir(), '.codex', 'auth.json')))) return true;
|
|
87
|
+
if (agent === 'pi' && (this.env.GOOGLE_API_KEY !== undefined || this.env.ANTHROPIC_API_KEY !== undefined))
|
|
88
|
+
return true;
|
|
89
|
+
const command = this.runner.runAuthCommand(agent, { timeout: this.timeout });
|
|
90
|
+
if (command === null) return false;
|
|
91
|
+
const result = await command;
|
|
92
|
+
return (
|
|
93
|
+
result.exitCode === 0 &&
|
|
94
|
+
!/not authenticated|not logged|unauthenticated/i.test(`${result.stdout}\n${result.stderr}`)
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
package/src/index.ts
ADDED