ai-agent-helper 0.0.0-stage → 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ ## 0.1.1
4
+
5
+ - 定义 `~/.ai-agent-helper/config.toml` 的配置数据模型:`version`、`proxy`、`no_proxy`。
6
+ - 导出配置相关 Zod schemas 和 TypeScript 类型。
7
+ - 增加配置设计文档、带注释的 TOML 示例及模型校验测试。
8
+ - npm 包包含配置设计文档和示例。
9
+
10
+ 本版本只增加配置模型,尚未实现配置文件读写、`config` / `install` 命令或代理安装。
11
+
12
+ ## 0.1.0
13
+
14
+ - 检测 PATH 中的六种 AI agent CLI,每种 agent 使用独立检测模块。
15
+ - 提供 `list`、`--all`、`--json`、帮助和版本信息。
package/README.md CHANGED
@@ -1,3 +1,104 @@
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
+ 配置采用 `~/.ai-agent-helper/config.toml`,以 TOML 保存注释和配置分组。当前已提供 `configSchema`、`configKeySchema`、`proxyUrlSchema` 及对应 TypeScript 类型;配置文件读写、`config` 命令和代理安装功能尚未实现。
91
+
92
+ 设计及命令约定见 [配置功能设计](docs/configuration.md),带注释的文件示例见 [config.toml](examples/config.toml)。初版模型包含配置格式版本 `version`、默认安装代理 `proxy` 和绕过规则 `no_proxy`。
93
+
94
+ ## 开发与扩展
95
+
96
+ ```sh
97
+ npm run dev -- list --all
98
+ npm run check
99
+ npm pack
100
+ ```
101
+
102
+ 每种 agent 在 `src/agents/` 中独立实现 `AgentProvider.detect()`,在 `src/agents/index.ts` 注册。当前共用 `src/detection/find-executable.ts` 的 PATH 查找能力;以后可在各自模块内添加专用安装路径或其他识别逻辑,不必修改 CLI。
103
+
104
+ `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,19 @@
1
+ import { z } from "zod";
2
+ /** HTTP(S) proxy endpoint used by installation subprocesses. */
3
+ export declare const proxyUrlSchema: z.ZodString;
4
+ /** Validate the object produced by a TOML parser; never persist resolved environment values. */
5
+ export declare const configSchema: z.ZodObject<{
6
+ version: z.ZodDefault<z.ZodLiteral<1>>;
7
+ proxy: z.ZodOptional<z.ZodString>;
8
+ no_proxy: z.ZodDefault<z.ZodArray<z.ZodString>>;
9
+ }, z.core.$strict>;
10
+ /** Only these keys are writable through `config set` / `config unset`. */
11
+ export declare const configKeySchema: z.ZodEnum<{
12
+ no_proxy: "no_proxy";
13
+ proxy: "proxy";
14
+ }>;
15
+ /** User-provided fields, before defaults are applied. */
16
+ export type ConfigInput = z.input<typeof configSchema>;
17
+ /** Validated configuration with defaults, independent of runtime environment. */
18
+ export type Config = z.output<typeof configSchema>;
19
+ export type ConfigKey = z.infer<typeof configKeySchema>;
@@ -0,0 +1,30 @@
1
+ import { z } from "zod";
2
+ /** HTTP(S) proxy endpoint used by installation subprocesses. */
3
+ export const proxyUrlSchema = z.string().min(1).superRefine((value, context) => {
4
+ let url;
5
+ try {
6
+ url = new URL(value);
7
+ }
8
+ catch {
9
+ context.addIssue({ code: "custom", message: "代理必须是有效的 HTTP/HTTPS URL。" });
10
+ return;
11
+ }
12
+ if (value !== value.trim() || /\s/.test(value)) {
13
+ context.addIssue({ code: "custom", message: "代理 URL 不能包含空白字符。" });
14
+ }
15
+ if (!/^https?:\/\//i.test(value) || !["http:", "https:"].includes(url.protocol) || !url.hostname) {
16
+ context.addIssue({ code: "custom", message: "代理仅支持 http:// 或 https://。" });
17
+ }
18
+ if ((url.pathname !== "" && url.pathname !== "/") || url.search || url.hash) {
19
+ context.addIssue({ code: "custom", message: "代理 URL 不能包含路径、查询参数或片段。" });
20
+ }
21
+ });
22
+ const noProxyEntrySchema = z.string().min(1).refine((value) => !/[\s,]/.test(value), "每个 no_proxy 条目必须是单独的非空规则,不能包含空白或逗号。");
23
+ /** Validate the object produced by a TOML parser; never persist resolved environment values. */
24
+ export const configSchema = z.strictObject({
25
+ version: z.literal(1).default(1),
26
+ proxy: proxyUrlSchema.optional(),
27
+ no_proxy: z.array(noProxyEntrySchema).default([]),
28
+ });
29
+ /** Only these keys are writable through `config set` / `config unset`. */
30
+ export const configKeySchema = z.enum(["proxy", "no_proxy"]);
@@ -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,3 @@
1
+ export { listAgents, type ListAgentsOptions } from "./list-agents.js";
2
+ export { agentDefinitionSchema, agentResultSchema, agentListSchema, type AgentDefinition, type AgentResult } from "./schema.js";
3
+ export { configSchema, configKeySchema, proxyUrlSchema, type ConfigInput, type Config, type ConfigKey } from "./config/schema.js";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { listAgents } from "./list-agents.js";
2
+ export { agentDefinitionSchema, agentResultSchema, agentListSchema } from "./schema.js";
3
+ export { configSchema, configKeySchema, proxyUrlSchema } from "./config/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);
@@ -0,0 +1,88 @@
1
+ # 配置功能设计
2
+
3
+ 状态:已定义 TypeScript / Zod 数据模型和 TOML 示例。文件读写、`config` 命令、`install` 命令及代理注入尚未实现;本文件中的命令是拟实现的接口。
4
+
5
+ ## 文件位置与格式
6
+
7
+ 用户级配置固定为 `~/.ai-agent-helper/config.toml`,采用 TOML。使用 `os.homedir()` 定位用户主目录,不依赖 shell 展开 `~`。Windows 对应 `%USERPROFILE%\.ai-agent-helper\config.toml`。首版只加载用户级配置。
8
+
9
+ ```toml
10
+ # 配置格式版本
11
+ version = 1
12
+
13
+ # 所有 agent 安装操作共用的默认代理
14
+ proxy = "http://127.0.0.1:7890"
15
+
16
+ # 具体匹配行为由所使用的安装工具决定
17
+ no_proxy = ["localhost", "127.0.0.1", "::1"]
18
+ ```
19
+
20
+ 完整带注释示例见 `examples/config.toml`。
21
+
22
+ ## 持久化数据模型
23
+
24
+ 模型定义位于 `src/config/schema.ts`。字段采用 snake_case,与 TOML 示例保持一致。
25
+
26
+ | 字段 | 类型 | 省略时 | 校验及含义 |
27
+ | --- | --- | --- | --- |
28
+ | `version` | 字面量整数 `1` | 按 `1` 读取 | 配置格式版本,首次创建时写入;不允许通过 `config set` 修改 |
29
+ | `proxy` | 可选字符串 | 继承安装工具原有代理配置 | 必须是 HTTP/HTTPS 代理 URL;支持主机、端口、IPv6 及 URL 编码的认证信息;禁止空白、业务路径、查询参数和片段 |
30
+ | `no_proxy` | 字符串数组 | `[]` | 每个非空条目是一条绕过规则,不含空白或逗号;安装器负责转换和兼容性检查 |
31
+
32
+ 不允许未知字段,避免拼写错误被悄悄忽略。不支持的 `version` 应报错并停止写入,不能用当前模型覆盖未来版本的文件。TOML 没有 `null`,可选配置通过省略字段表示。
33
+
34
+ `ConfigInput` 表示 TOML 解析后的输入,允许省略默认字段;`Config` 表示 Zod 校验并补齐默认值后的数据。默认配置为:
35
+
36
+ ```ts
37
+ const defaults = configSchema.parse({});
38
+ // { version: 1, no_proxy: [] }
39
+ ```
40
+
41
+ `proxy` 不使用虚构的默认地址,也不把环境变量中的值写回文件。
42
+
43
+ ## 命令接口
44
+
45
+ ```sh
46
+ # 查看已保存的配置及内置默认值,以 TOML 展示
47
+ agent-helper config
48
+
49
+ # 设置默认安装代理
50
+ agent-helper config set proxy http://127.0.0.1:7890
51
+
52
+ # 数组通过多个参数设置,每个参数对应一条规则
53
+ agent-helper config set no_proxy localhost 127.0.0.1 ::1
54
+
55
+ # 删除代理配置,恢复继承安装工具原有配置
56
+ agent-helper config unset proxy
57
+
58
+ # 删除绕过规则,恢复内置默认空数组
59
+ agent-helper config unset no_proxy
60
+ ```
61
+
62
+ 可写配置键由 `configKeySchema` 限定为 `proxy` 和 `no_proxy`。`set proxy` 要求一个字符串值;`set no_proxy` 要求至少一个条目,需要恢复默认空数组时使用 `unset`。写入前应校验修改后的整份配置,而不只是当前字段。帮助文本应列出允许的键、值类型、默认行为、配置路径和示例。
63
+
64
+ `config` 展示配置文件中的值及内置默认值,不将安装工具或当前环境中的代理误标成已保存配置。代理认证信息必须脱敏,包括用户名与密码;`--json` 等将来新增的展示方式也遵循此规则。
65
+
66
+ ## 安装时的解析规则
67
+
68
+ 默认代理针对安装子进程。安装代理选择按以下顺序进行:
69
+
70
+ 1. 本次安装命令显式指定的 `--proxy`。
71
+ 2. 配置文件中的 `proxy`。
72
+ 3. 安装工具本来的配置和环境变量;由安装工具自行解释其优先级。
73
+
74
+ 前两项命中时,安装适配器为本次子进程设置对应代理参数及环境变量,不修改系统代理或安装工具的全局配置。此时,非空 `no_proxy` 明确替换工具继承的绕过规则;空数组不注入绕过规则,保留工具已有设置。代理都未显式指定时,不单独应用 helper 的 `no_proxy`。
75
+
76
+ `no_proxy` 不宣称统一不同工具对域名、端口、通配符和 CIDR 的匹配语义。适配器必须验证或拒绝其不支持的规则;不能静默忽略并宣称代理已正确应用。首版 URL 模型仅支持 HTTP/HTTPS;其他协议应在安装器支持后再扩展。
77
+
78
+ ## 读写要求
79
+
80
+ - 文件不存在时返回内置默认值;只查看配置不会创建目录或文件。
81
+ - 第一次 `set` 才创建目录与配置文件,写入 `version = 1`。
82
+ - 文件存在但 TOML 语法或数据校验失败时,报告文件位置和字段错误,停止修改,不覆盖原文件。
83
+ - 错误信息和展示结果不得输出包含认证信息的原始 URL。
84
+ - `set` / `unset` 需要使用能保留注释和原有顺序的编辑方式;只解析后重新 stringify 整份文件不能满足要求。
85
+ - 在同目录写入临时文件并原子替换,失败时保留原文件;写入前检查源文件是否变化,避免覆盖外部并发修改。
86
+ - 未知字段、未知版本的文件可以由用户手动维护,但当前版本拒绝修改,保留原文。
87
+
88
+ 文件格式版本与 npm 包版本分开维护。后续确有需求时,再为安装器参数或 agent 专属配置添加 TOML 分组及迁移规则。
@@ -0,0 +1,13 @@
1
+ # 保存到 ~/.ai-agent-helper/config.toml。
2
+ # 本示例属于配置模型设计;当前 CLI 尚未提供 config / install 命令。
3
+
4
+ # 配置文件格式版本,由 helper 管理。
5
+ version = 1
6
+
7
+ # 安装 agent 时的默认 HTTP/HTTPS 代理。
8
+ # 取消注释前请替换为自己的代理地址;省略时继承安装工具原有设置。
9
+ # proxy = "http://127.0.0.1:7890"
10
+
11
+ # 使用默认或临时代理时,交给各安装器处理的绕过规则。
12
+ # 不需要绕过规则时可以省略此项。
13
+ no_proxy = ["localhost", "127.0.0.1", "::1"]
package/package.json CHANGED
@@ -1,6 +1,63 @@
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.1",
4
+ "publishConfig": {
5
+ "registry": "https://registry.npmjs.org/",
6
+ "access": "public"
7
+ },
8
+ "description": "Discover locally installed AI agent CLIs.",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/tool-helper/ai-agent-helper.git"
12
+ },
13
+ "homepage": "https://github.com/tool-helper/ai-agent-helper#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/tool-helper/ai-agent-helper/issues"
16
+ },
17
+ "type": "module",
18
+ "bin": {
19
+ "agent-helper": "dist/cli.js"
20
+ },
21
+ "main": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "README.md",
32
+ "CHANGELOG.md",
33
+ "docs/configuration.md",
34
+ "examples/config.toml"
35
+ ],
36
+ "engines": {
37
+ "node": ">=22.13.0"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc -p tsconfig.json",
41
+ "dev": "tsx src/cli.ts",
42
+ "typecheck": "tsc -p tsconfig.check.json",
43
+ "test": "node --import tsx --test test/*.test.ts",
44
+ "check": "npm run typecheck && npm test && npm run build",
45
+ "prepack": "npm run build"
46
+ },
47
+ "keywords": [
48
+ "agent",
49
+ "cli",
50
+ "claude",
51
+ "codex",
52
+ "gemini",
53
+ "typescript"
54
+ ],
55
+ "dependencies": {
56
+ "zod": "^4.6.7"
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^22.20.5",
60
+ "tsx": "^4.23.15",
61
+ "typescript": "^7.0.2"
62
+ }
63
+ }