ai-agent-helper 0.0.0-stage → 0.1.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/README.md CHANGED
@@ -1,3 +1,98 @@
1
- # Temporary Holding Version
1
+ # ai-agent-helper
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 使用 TypeScript 和 Zod 开发的 AI agent CLI 管理工具。首版 `0.1.0` 仅实现列出本机 PATH 中可找到的 agent,不安装、更新或启动 agent。
4
+
5
+ 需要 Node.js **22.13 或更高版本**。
6
+
7
+ ## 安装与使用
8
+
9
+ ```sh
10
+ npm install -g ai-agent-helper --registry=https://registry.npmjs.org/
11
+ agent-helper --help
12
+ agent-helper list
13
+ ```
14
+
15
+ ## 从源码运行
16
+
17
+ ```sh
18
+ npm install
19
+ npm run build
20
+ node dist/cli.js --help
21
+ node dist/cli.js list
22
+ ```
23
+
24
+ 需要使用本地源码提供的 `agent-helper` 命令时,可在构建后于项目目录执行 `npm link`。
25
+
26
+ ## 命令
27
+
28
+ ```sh
29
+ agent-helper --help
30
+ agent-helper -h
31
+ agent-helper --version
32
+ agent-helper list
33
+ agent-helper list --help
34
+ agent-helper list --all
35
+ agent-helper list --json
36
+ agent-helper list --all --json
37
+ ```
38
+
39
+ - 无参数时显示帮助。
40
+ - `list` 默认只列出检测到的 agent,输出名称、命令、状态和路径。
41
+ - `--all` / `-a` 同时列出未检测到的 agent。
42
+ - `--json` 输出纯 JSON 数组;没有检测到时返回 `[]`。
43
+ - 正常查询(包括没有检测到)退出码为 `0`;无效参数或检测异常为 `1`,错误写入 stderr。
44
+
45
+ JSON 示例:
46
+
47
+ ```json
48
+ [
49
+ {
50
+ "id": "claude-code",
51
+ "name": "Claude Code",
52
+ "command": "claude",
53
+ "installed": true,
54
+ "path": "/usr/local/bin/claude"
55
+ }
56
+ ]
57
+ ```
58
+
59
+ `--all` 中未检测到的条目使用 `installed: false` 和 `path: null`。
60
+
61
+ ## 首版检测范围
62
+
63
+ | Agent | 命令 | 独立检测模块 |
64
+ | --- | --- | --- |
65
+ | Claude Code | `claude` | `src/agents/claude-code.ts` |
66
+ | Codex | `codex` | `src/agents/codex.ts` |
67
+ | Gemini CLI | `gemini` | `src/agents/gemini.ts` |
68
+ | OpenCode | `opencode` | `src/agents/opencode.ts` |
69
+ | Aider | `aider` | `src/agents/aider.ts` |
70
+ | GitHub Copilot | `copilot` | `src/agents/github-copilot.ts` |
71
+
72
+ 按当前进程的 PATH 顺序查找,只返回首个匹配的可执行入口。Windows 使用 PATHEXT(未设置时使用 `.COM;.EXE;.BAT;.CMD`),支持 npm 的 `.cmd` 入口、大小写不敏感的环境变量名称和带引号的 PATH 目录。macOS/Linux 要求文件有执行权限,支持符号链接。
73
+
74
+ `installed` 表示找到了对应名称的可执行文件,不保证文件身份、运行正常、版本或登录状态。不会执行 `--version`、读取密钥、联网或加载 shell 配置。PATH 之外的安装、桌面应用、shell alias/function、旧版 `gh copilot` 扩展和未知 agent 不在首版检测范围内。空 PATH 项会跳过;显式的相对 PATH 项相对于当前工作目录解析。
75
+
76
+ ## TypeScript API
77
+
78
+ ```ts
79
+ import { listAgents, agentListSchema, type AgentResult } from "ai-agent-helper";
80
+
81
+ const installed: AgentResult[] = await listAgents();
82
+ const all = await listAgents({ includeMissing: true });
83
+ agentListSchema.parse(all);
84
+ ```
85
+
86
+ 包提供 ESM 导出和类型声明。Zod 校验 CLI 选项及检测结果,并约束已检测到条目必须包含路径、未检测到条目的路径必须为 `null`。
87
+
88
+ ## 开发与扩展
89
+
90
+ ```sh
91
+ npm run dev -- list --all
92
+ npm run check
93
+ npm pack
94
+ ```
95
+
96
+ 每种 agent 在 `src/agents/` 中独立实现 `AgentProvider.detect()`,在 `src/agents/index.ts` 注册。当前共用 `src/detection/find-executable.ts` 的 PATH 查找能力;以后可在各自模块内添加专用安装路径或其他识别逻辑,不必修改 CLI。
97
+
98
+ `src/list-agents.ts` 负责汇总和 Zod 校验,`src/cli.ts` 负责参数解析与展示。测试使用临时目录和伪可执行入口,不启动本机 agent;包含 CLI 帮助、错误参数、JSON、PATH 优先级、Windows PATHEXT、POSIX 执行权限等用例。平台专属用例只在对应平台执行。
@@ -0,0 +1,2 @@
1
+ import type { AgentProvider } from "../detection/types.js";
2
+ export declare const aider: AgentProvider;
@@ -0,0 +1,7 @@
1
+ import { findExecutable } from "../detection/find-executable.js";
2
+ export const aider = {
3
+ definition: { id: "aider", name: "Aider", command: "aider" },
4
+ async detect(context) {
5
+ return findExecutable("aider", context);
6
+ },
7
+ };
@@ -0,0 +1,2 @@
1
+ import type { AgentProvider } from "../detection/types.js";
2
+ export declare const claudeCode: AgentProvider;
@@ -0,0 +1,7 @@
1
+ import { findExecutable } from "../detection/find-executable.js";
2
+ export const claudeCode = {
3
+ definition: { id: "claude-code", name: "Claude Code", command: "claude" },
4
+ async detect(context) {
5
+ return findExecutable("claude", context);
6
+ },
7
+ };
@@ -0,0 +1,2 @@
1
+ import type { AgentProvider } from "../detection/types.js";
2
+ export declare const codex: AgentProvider;
@@ -0,0 +1,7 @@
1
+ import { findExecutable } from "../detection/find-executable.js";
2
+ export const codex = {
3
+ definition: { id: "codex", name: "Codex", command: "codex" },
4
+ async detect(context) {
5
+ return findExecutable("codex", context);
6
+ },
7
+ };
@@ -0,0 +1,2 @@
1
+ import type { AgentProvider } from "../detection/types.js";
2
+ export declare const gemini: AgentProvider;
@@ -0,0 +1,7 @@
1
+ import { findExecutable } from "../detection/find-executable.js";
2
+ export const gemini = {
3
+ definition: { id: "gemini", name: "Gemini CLI", command: "gemini" },
4
+ async detect(context) {
5
+ return findExecutable("gemini", context);
6
+ },
7
+ };
@@ -0,0 +1,2 @@
1
+ import type { AgentProvider } from "../detection/types.js";
2
+ export declare const githubCopilot: AgentProvider;
@@ -0,0 +1,7 @@
1
+ import { findExecutable } from "../detection/find-executable.js";
2
+ export const githubCopilot = {
3
+ definition: { id: "github-copilot", name: "GitHub Copilot", command: "copilot" },
4
+ async detect(context) {
5
+ return findExecutable("copilot", context);
6
+ },
7
+ };
@@ -0,0 +1,2 @@
1
+ import type { AgentProvider } from "../detection/types.js";
2
+ export declare const builtInAgents: readonly AgentProvider[];
@@ -0,0 +1,7 @@
1
+ import { aider } from "./aider.js";
2
+ import { claudeCode } from "./claude-code.js";
3
+ import { codex } from "./codex.js";
4
+ import { gemini } from "./gemini.js";
5
+ import { githubCopilot } from "./github-copilot.js";
6
+ import { opencode } from "./opencode.js";
7
+ export const builtInAgents = [claudeCode, codex, gemini, opencode, aider, githubCopilot];
@@ -0,0 +1,2 @@
1
+ import type { AgentProvider } from "../detection/types.js";
2
+ export declare const opencode: AgentProvider;
@@ -0,0 +1,7 @@
1
+ import { findExecutable } from "../detection/find-executable.js";
2
+ export const opencode = {
3
+ definition: { id: "opencode", name: "OpenCode", command: "opencode" },
4
+ async detect(context) {
5
+ return findExecutable("opencode", context);
6
+ },
7
+ };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import { parseArgs } from "node:util";
4
+ import { z } from "zod";
5
+ import { listAgents } from "./list-agents.js";
6
+ const help = `agent-helper — 列出本地 AI agent CLI
7
+
8
+ 用法:
9
+ agent-helper list [--all] [--json]
10
+ agent-helper [--help | --version]
11
+
12
+ 命令:
13
+ list 列出 PATH 中检测到的 agent
14
+
15
+ 选项:
16
+ -a, --all 同时显示未检测到的 agent(仅用于 list)
17
+ --json 输出 JSON 数组(仅用于 list)
18
+ -h, --help 显示帮助,支持 list --help
19
+ -v, --version 显示 agent-helper 版本
20
+
21
+ 示例:
22
+ agent-helper list
23
+ agent-helper list --all
24
+ agent-helper list --json
25
+ agent-helper list --all --json
26
+
27
+ 检测范围:Claude Code、Codex、Gemini CLI、OpenCode、Aider、GitHub Copilot。
28
+ 仅查找 PATH 中的可执行入口,不启动 agent,不检查登录状态或版本。`;
29
+ const cliOptionsSchema = z.object({
30
+ all: z.boolean().default(false),
31
+ json: z.boolean().default(false),
32
+ help: z.boolean().default(false),
33
+ version: z.boolean().default(false),
34
+ });
35
+ function printTable(agents) {
36
+ if (agents.length === 0) {
37
+ console.log("未在 PATH 中检测到支持的 agent。使用 agent-helper list --all 查看检测范围。");
38
+ return;
39
+ }
40
+ const rows = [["AGENT", "COMMAND", "STATUS", "PATH"], ...agents.map((agent) => [
41
+ agent.name, agent.command, agent.installed ? "found" : "not found", agent.path ?? "-",
42
+ ])];
43
+ const widths = [0, 1, 2].map((column) => Math.max(...rows.map((row) => row[column].length)));
44
+ console.log(rows.map((row) => row.map((value, column) => column < 3 ? value.padEnd(widths[column] + 2) : value).join("")).join("\n"));
45
+ }
46
+ async function main() {
47
+ const { values, positionals } = parseArgs({
48
+ options: {
49
+ all: { type: "boolean", short: "a" },
50
+ json: { type: "boolean" },
51
+ help: { type: "boolean", short: "h" },
52
+ version: { type: "boolean", short: "v" },
53
+ },
54
+ allowPositionals: true,
55
+ strict: true,
56
+ });
57
+ const options = cliOptionsSchema.parse(values);
58
+ if (positionals.length > 1 || (positionals[0] !== undefined && positionals[0] !== "list")) {
59
+ throw new Error(`未知命令:${positionals.join(" ")}。使用 agent-helper --help 查看帮助。`);
60
+ }
61
+ if (options.help) {
62
+ console.log(help);
63
+ return;
64
+ }
65
+ if (options.version) {
66
+ const metadata = z.object({ version: z.string().min(1) }).parse(JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")));
67
+ console.log(metadata.version);
68
+ return;
69
+ }
70
+ if (positionals.length === 0) {
71
+ if (options.all || options.json)
72
+ throw new Error("--all 和 --json 需要与 list 命令一起使用。");
73
+ console.log(help);
74
+ return;
75
+ }
76
+ const agents = await listAgents({ includeMissing: options.all });
77
+ if (options.json)
78
+ console.log(JSON.stringify(agents, null, 2));
79
+ else
80
+ printTable(agents);
81
+ }
82
+ main().catch((error) => {
83
+ console.error(`agent-helper: ${error instanceof Error ? error.message : String(error)}`);
84
+ process.exitCode = 1;
85
+ });
@@ -0,0 +1,3 @@
1
+ import type { DetectionContext } from "./types.js";
2
+ /** Search PATH in order without running the executable or loading shell profiles. */
3
+ export declare function findExecutable(command: string, context: DetectionContext): Promise<string | null>;
@@ -0,0 +1,46 @@
1
+ import { constants } from "node:fs";
2
+ import { access, stat } from "node:fs/promises";
3
+ import { posix, win32 } from "node:path";
4
+ function getEnv(context, name) {
5
+ if (context.platform !== "win32")
6
+ return context.env[name];
7
+ const key = Object.keys(context.env).find((key) => key.toUpperCase() === name);
8
+ return key === undefined ? undefined : context.env[key];
9
+ }
10
+ async function isExecutable(file, windows) {
11
+ try {
12
+ if (!(await stat(file)).isFile())
13
+ return false;
14
+ await access(file, windows ? constants.F_OK : constants.X_OK);
15
+ return true;
16
+ }
17
+ catch (error) {
18
+ const code = error.code;
19
+ if (["ENOENT", "ENOTDIR", "EACCES", "EPERM", "ELOOP"].includes(code ?? ""))
20
+ return false;
21
+ throw error;
22
+ }
23
+ }
24
+ /** Search PATH in order without running the executable or loading shell profiles. */
25
+ export async function findExecutable(command, context) {
26
+ const windows = context.platform === "win32";
27
+ const paths = windows ? win32 : posix;
28
+ const extensions = windows
29
+ ? (getEnv(context, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD")
30
+ .split(";")
31
+ .filter((extension) => /^\.[a-z0-9]+$/i.test(extension))
32
+ : [""];
33
+ const directories = (getEnv(context, "PATH") ?? "").split(paths.delimiter);
34
+ for (const rawDirectory of directories) {
35
+ // Empty PATH entries do not implicitly opt into scanning the current directory.
36
+ const directory = windows ? rawDirectory.replace(/^"(.*)"$/, "$1") : rawDirectory;
37
+ if (!directory)
38
+ continue;
39
+ for (const extension of extensions) {
40
+ const candidate = paths.resolve(context.cwd, directory, `${command}${extension}`);
41
+ if (await isExecutable(candidate, windows))
42
+ return candidate;
43
+ }
44
+ }
45
+ return null;
46
+ }
@@ -0,0 +1,10 @@
1
+ import type { AgentDefinition } from "../schema.js";
2
+ export interface DetectionContext {
3
+ env: NodeJS.ProcessEnv;
4
+ platform: NodeJS.Platform;
5
+ cwd: string;
6
+ }
7
+ export interface AgentProvider {
8
+ definition: AgentDefinition;
9
+ detect(context: DetectionContext): Promise<string | null>;
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export { listAgents, type ListAgentsOptions } from "./list-agents.js";
2
+ export { agentDefinitionSchema, agentResultSchema, agentListSchema, type AgentDefinition, type AgentResult } from "./schema.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { listAgents } from "./list-agents.js";
2
+ export { agentDefinitionSchema, agentResultSchema, agentListSchema } from "./schema.js";
@@ -0,0 +1,6 @@
1
+ import type { DetectionContext } from "./detection/types.js";
2
+ import { type AgentResult } from "./schema.js";
3
+ export interface ListAgentsOptions extends Partial<DetectionContext> {
4
+ includeMissing?: boolean;
5
+ }
6
+ export declare function listAgents(options?: ListAgentsOptions): Promise<AgentResult[]>;
@@ -0,0 +1,14 @@
1
+ import { builtInAgents } from "./agents/index.js";
2
+ import { agentListSchema } from "./schema.js";
3
+ export async function listAgents(options = {}) {
4
+ const context = {
5
+ env: options.env ?? process.env,
6
+ platform: options.platform ?? process.platform,
7
+ cwd: options.cwd ?? process.cwd(),
8
+ };
9
+ const results = await Promise.all(builtInAgents.map(async (provider) => {
10
+ const path = await provider.detect(context);
11
+ return { ...provider.definition, installed: path !== null, path };
12
+ }));
13
+ return agentListSchema.parse(results).filter((agent) => options.includeMissing || agent.installed);
14
+ }
@@ -0,0 +1,34 @@
1
+ import { z } from "zod";
2
+ export declare const agentDefinitionSchema: z.ZodObject<{
3
+ id: z.ZodString;
4
+ name: z.ZodString;
5
+ command: z.ZodString;
6
+ }, z.core.$strip>;
7
+ export declare const agentResultSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
8
+ id: z.ZodString;
9
+ name: z.ZodString;
10
+ command: z.ZodString;
11
+ installed: z.ZodLiteral<true>;
12
+ path: z.ZodString;
13
+ }, z.core.$strip>, z.ZodObject<{
14
+ id: z.ZodString;
15
+ name: z.ZodString;
16
+ command: z.ZodString;
17
+ installed: z.ZodLiteral<false>;
18
+ path: z.ZodNull;
19
+ }, z.core.$strip>], "installed">;
20
+ export declare const agentListSchema: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
21
+ id: z.ZodString;
22
+ name: z.ZodString;
23
+ command: z.ZodString;
24
+ installed: z.ZodLiteral<true>;
25
+ path: z.ZodString;
26
+ }, z.core.$strip>, z.ZodObject<{
27
+ id: z.ZodString;
28
+ name: z.ZodString;
29
+ command: z.ZodString;
30
+ installed: z.ZodLiteral<false>;
31
+ path: z.ZodNull;
32
+ }, z.core.$strip>], "installed">>;
33
+ export type AgentDefinition = z.infer<typeof agentDefinitionSchema>;
34
+ export type AgentResult = z.infer<typeof agentResultSchema>;
package/dist/schema.js ADDED
@@ -0,0 +1,11 @@
1
+ import { z } from "zod";
2
+ export const agentDefinitionSchema = z.object({
3
+ id: z.string().min(1),
4
+ name: z.string().min(1),
5
+ command: z.string().min(1),
6
+ });
7
+ export const agentResultSchema = z.discriminatedUnion("installed", [
8
+ agentDefinitionSchema.extend({ installed: z.literal(true), path: z.string().min(1) }),
9
+ agentDefinitionSchema.extend({ installed: z.literal(false), path: z.null() }),
10
+ ]);
11
+ export const agentListSchema = z.array(agentResultSchema);
package/package.json CHANGED
@@ -1,6 +1,52 @@
1
1
  {
2
2
  "name": "ai-agent-helper",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "publishConfig": {
5
+ "registry": "https://registry.npmjs.org/",
6
+ "access": "public"
7
+ },
8
+ "description": "Discover locally installed AI agent CLIs.",
9
+ "type": "module",
10
+ "bin": {
11
+ "agent-helper": "dist/cli.js"
12
+ },
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md"
24
+ ],
25
+ "engines": {
26
+ "node": ">=22.13.0"
27
+ },
28
+ "scripts": {
29
+ "build": "tsc -p tsconfig.json",
30
+ "dev": "tsx src/cli.ts",
31
+ "typecheck": "tsc -p tsconfig.check.json",
32
+ "test": "node --import tsx --test test/*.test.ts",
33
+ "check": "npm run typecheck && npm test && npm run build",
34
+ "prepack": "npm run build"
35
+ },
36
+ "keywords": [
37
+ "agent",
38
+ "cli",
39
+ "claude",
40
+ "codex",
41
+ "gemini",
42
+ "typescript"
43
+ ],
44
+ "dependencies": {
45
+ "zod": "^4.6.7"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^22.20.5",
49
+ "tsx": "^4.23.15",
50
+ "typescript": "^7.0.2"
51
+ }
52
+ }