eve-app-cli
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseEve App CLI
Eve App CLI
Build domain-specific CLIs for Eve-compatible apps so agents interact via commands instead of raw REST calls.
为兼容Eve的应用构建特定领域的CLI工具,让Agent通过命令而非原始REST调用进行交互。
Why
设计初衷
Agents waste 3-5 LLM calls per REST interaction on URL construction, JSON quoting, auth headers, and error parsing. A CLI reduces this to 1 call:
bash
undefinedAgent在每次REST交互中,会在URL构建、JSON转义、认证头和错误解析上浪费3-5次LLM调用。使用CLI可将其减少至1次调用:
bash
undefinedBefore (3-5 calls, error-prone)
之前(3-5次调用,易出错)
curl -X POST "$EVE_APP_API_URL_API/projects/$PID/changesets"
-H "Content-Type: application/json"
-H "Authorization: Bearer $EVE_JOB_TOKEN"
-d @/tmp/changeset.json
-H "Content-Type: application/json"
-H "Authorization: Bearer $EVE_JOB_TOKEN"
-d @/tmp/changeset.json
curl -X POST "$EVE_APP_API_URL_API/projects/$PID/changesets"
-H "Content-Type: application/json"
-H "Authorization: Bearer $EVE_JOB_TOKEN"
-d @/tmp/changeset.json
-H "Content-Type: application/json"
-H "Authorization: Bearer $EVE_JOB_TOKEN"
-d @/tmp/changeset.json
After (1 call, self-documenting)
之后(1次调用,自带文档)
eden changeset create --project $PID --file /tmp/changeset.json
undefinededen changeset create --project $PID --file /tmp/changeset.json
undefinedQuick Start
快速开始
1. Create the CLI Package
1. 创建CLI包
your-app/
cli/
src/
index.ts # Entry point
client.ts # API client (reads env vars)
commands/
projects.ts # Domain commands
bin/
your-app # Built artifact (single-file bundle)
package.json
tsconfig.jsonyour-app/
cli/
src/
index.ts # 入口文件
client.ts # API客户端(读取环境变量)
commands/
projects.ts # 领域命令
bin/
your-app # 构建产物(单文件打包)
package.json
tsconfig.json2. Implement the API Client
2. 实现API客户端
typescript
// cli/src/client.ts — Copy this, change SERVICE name
const SERVICE = 'API';
export function getApiUrl(): string {
const url = process.env[`EVE_APP_API_URL_${SERVICE}`];
if (!url) {
console.error(`Error: EVE_APP_API_URL_${SERVICE} not set.`);
console.error('Are you running inside an Eve job with with_apis: [api]?');
process.exit(1);
}
return url;
}
export async function api<T = unknown>(
method: string,
path: string,
body?: unknown,
): Promise<T> {
const url = getApiUrl();
const token = process.env.EVE_JOB_TOKEN;
const res = await fetch(`${url}${path}`, {
method,
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) {
const err = await res.json().catch(() => ({} as Record<string, string>));
console.error(`${method} ${path} → ${res.status}: ${err.message || res.statusText}`);
process.exit(1);
}
return res.json() as Promise<T>;
}typescript
// cli/src/client.ts — 复制此代码,修改SERVICE名称
const SERVICE = 'API';
export function getApiUrl(): string {
const url = process.env[`EVE_APP_API_URL_${SERVICE}`];
if (!url) {
console.error(`Error: EVE_APP_API_URL_${SERVICE} not set.`);
console.error('Are you running inside an Eve job with with_apis: [api]?');
process.exit(1);
}
return url;
}
export async function api<T = unknown>(
method: string,
path: string,
body?: unknown,
): Promise<T> {
const url = getApiUrl();
const token = process.env.EVE_JOB_TOKEN;
const res = await fetch(`${url}${path}`, {
method,
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) {
const err = await res.json().catch(() => ({} as Record<string, string>));
console.error(`${method} ${path} → ${res.status}: ${err.message || res.statusText}`);
process.exit(1);
}
return res.json() as Promise<T>;
}3. Define Commands
3. 定义命令
typescript
// cli/src/index.ts
import { Command } from 'commander';
import { api } from './client.js';
import { readFile } from 'node:fs/promises';
const program = new Command();
program.name('myapp').description('My App CLI').version('1.0.0');
program.command('items')
.command('list')
.option('--json', 'JSON output')
.action(async (opts) => {
const items = await api('GET', '/items');
if (opts.json) return console.log(JSON.stringify(items, null, 2));
for (const i of items) console.log(`${i.id} ${i.name}`);
});
program.command('items')
.command('create')
.requiredOption('--file <path>', 'JSON file')
.action(async (opts) => {
const body = JSON.parse(await readFile(opts.file, 'utf8'));
const result = await api('POST', '/items', body);
console.log(`Created: ${result.id}`);
});
program.parse();typescript
// cli/src/index.ts
import { Command } from 'commander';
import { api } from './client.js';
import { readFile } from 'node:fs/promises';
const program = new Command();
program.name('myapp').description('My App CLI').version('1.0.0');
program.command('items')
.command('list')
.option('--json', 'JSON输出')
.action(async (opts) => {
const items = await api('GET', '/items');
if (opts.json) return console.log(JSON.stringify(items, null, 2));
for (const i of items) console.log(`${i.id} ${i.name}`);
});
program.command('items')
.command('create')
.requiredOption('--file <path>', 'JSON文件')
.action(async (opts) => {
const body = JSON.parse(await readFile(opts.file, 'utf8'));
const result = await api('POST', '/items', body);
console.log(`Created: ${result.id}`);
});
program.parse();4. Bundle for Zero-Dependency Distribution
4. 打包为零依赖可执行文件
Create a build script ():
cli/build.mjsjavascript
import { build } from 'esbuild';
import { readFile, writeFile, chmod } from 'node:fs/promises';
await build({
entryPoints: ['cli/src/index.ts'],
bundle: true,
platform: 'node',
target: 'node20',
format: 'cjs', // CJS — commander uses require() internally
outfile: 'cli/bin/myapp',
});
// Prepend shebang (esbuild banner escapes the !)
const code = await readFile('cli/bin/myapp', 'utf8');
await writeFile('cli/bin/myapp', '#!/usr/bin/env node\n' + code);
await chmod('cli/bin/myapp', 0o755);Add to :
package.jsonjson
{
"scripts": {
"build": "node build.mjs"
}
}Important: Do NOT set in — it causes errors at runtime. Use extension for the build script instead.
"type": "module"package.jsonrequire().mjs创建构建脚本():
cli/build.mjsjavascript
import { build } from 'esbuild';
import { readFile, writeFile, chmod } from 'node:fs/promises';
await build({
entryPoints: ['cli/src/index.ts'],
bundle: true,
platform: 'node',
target: 'node20',
format: 'cjs', // CJS — commander内部使用require()
outfile: 'cli/bin/myapp',
});
// 添加shebang(esbuild的banner会转义!)
const code = await readFile('cli/bin/myapp', 'utf8');
await writeFile('cli/bin/myapp', '#!/usr/bin/env node\n' + code);
await chmod('cli/bin/myapp', 0o755);在中添加:
package.jsonjson
{
"scripts": {
"build": "node build.mjs"
}
}重要提示: 不要在中设置——这会导致运行时出现错误。请改用扩展名作为构建脚本。
package.json"type": "module"require().mjs5. Declare in Manifest
5. 在清单中声明
yaml
undefinedyaml
undefined.eve/manifest.yaml
.eve/manifest.yaml
services:
api:
build:
context: ./apps/api
ports: [3000]
x-eve:
api_spec:
type: openapi
cli:
name: myapp # Binary name on $PATH
bin: cli/bin/myapp # Path relative to repo root
The platform automatically makes the CLI available to agents that have `with_apis: [api]`.services:
api:
build:
context: ./apps/api
ports: [3000]
x-eve:
api_spec:
type: openapi
cli:
name: myapp # $PATH中的二进制文件名
bin: cli/bin/myapp # 相对于代码库根目录的路径
平台会自动向包含`with_apis: [api]`的Agent提供该CLI工具。Design Rules
设计规则
Command Structure
命令结构
Map CLI commands to your domain, not HTTP endpoints:
undefined将CLI命令映射到你的业务领域,而非HTTP端点:
undefinedGood — domain vocabulary
推荐 — 使用领域术语
eden map show
eden changeset create --file data.json
eden changeset accept CS-45
eden map show
eden changeset create --file data.json
eden changeset accept CS-45
Bad — HTTP vocabulary
不推荐 — 使用HTTP术语
eden get /projects/123/map
eden post /changesets --body data.json
undefinededen get /projects/123/map
eden post /changesets --body data.json
undefinedOutput Contract
输出约定
- Default: human-readable (tables, summaries)
- : machine-readable JSON on stdout
--json - Errors: stderr, exit code 1, actionable message
bash
eden projects list # Table: ID NAME CREATED
eden projects list --json # [{"id":"...","name":"..."}]
eden changeset accept BAD-ID # stderr: "Changeset BAD-ID not found"- 默认输出:人类可读格式(表格、摘要)
- 参数:标准输出为机器可读的JSON
--json - 错误信息:输出到标准错误,退出码为1,附带可操作提示
bash
eden projects list # 表格格式:ID NAME CREATED
eden projects list --json # [{"id":"...","name":"..."}]
eden changeset accept BAD-ID # 标准错误输出: "Changeset BAD-ID not found"Auto-Detection Pattern
自动检测模式
When only one resource exists, auto-detect instead of requiring flags:
typescript
async function autoDetectProject(): Promise<string> {
const projects = await api('GET', '/projects');
if (projects.length === 1) return projects[0].id;
if (projects.length === 0) {
console.error('No projects found.');
process.exit(1);
}
console.error('Multiple projects. Use --project <id>:');
for (const p of projects) console.error(` ${p.id} ${p.name}`);
process.exit(1);
}当仅存在一个资源时,自动检测资源,无需用户指定参数:
typescript
async function autoDetectProject(): Promise<string> {
const projects = await api('GET', '/projects');
if (projects.length === 1) return projects[0].id;
if (projects.length === 0) {
console.error('未找到任何项目。');
process.exit(1);
}
console.error('存在多个项目,请使用--project <id>参数指定:');
for (const p of projects) console.error(` ${p.id} ${p.name}`);
process.exit(1);
}Progressive Help
渐进式帮助
Every command and subcommand has :
--help$ eden --help
Eden story map CLI
Commands:
projects Manage projects
map View story map
changeset Create and review changesets
persona Manage personas
question Manage questions
search Search the map
export Export project data
$ eden changeset --help
Commands:
create Create a changeset from JSON file
accept Accept a pending changeset
reject Reject a pending changeset
list List changesets for a project每个命令和子命令都支持参数:
--help$ eden --help
Eden story map CLI
Commands:
projects 管理项目
map 查看故事地图
changeset 创建并评审变更集
persona 管理角色
question 管理问题
search 搜索地图
export 导出项目数据
$ eden changeset --help
Commands:
create 从JSON文件创建变更集
accept 接受待处理的变更集
reject 拒绝待处理的变更集
list 列出项目的变更集Environment Variables
环境变量
The CLI reads these from the environment (injected automatically by Eve):
| Variable | Purpose | Set By |
|---|---|---|
| Base URL of the app API | Platform ( |
| Bearer auth token | Platform (per job) |
| Eve platform project ID | Platform |
| Eve platform org ID | Platform |
The CLI never requires manual configuration.
CLI从环境中读取以下变量(由Eve自动注入):
| 变量名 | 用途 | 设置方 |
|---|---|---|
| 应用API的基础URL | 平台( |
| Bearer认证令牌 | 平台(每个任务) |
| Eve平台的项目ID | 平台 |
| Eve平台的组织ID | 平台 |
CLI无需手动配置。
Testing Locally
本地测试
Set env vars and run directly:
bash
export EVE_APP_API_URL_API=http://localhost:3000
export EVE_JOB_TOKEN=$(eve auth token)设置环境变量后直接运行:
bash
export EVE_APP_API_URL_API=http://localhost:3000
export EVE_JOB_TOKEN=$(eve auth token)Test individual commands
测试单个命令
./cli/bin/myapp projects list
./cli/bin/myapp items create --file test-data.json
undefined./cli/bin/myapp projects list
./cli/bin/myapp items create --file test-data.json
undefinedBundling Details
打包细节
Use esbuild to produce a single file with zero runtime dependencies:
- inlines all imports (including
--bundle)commander - targets Node.js built-ins
--platform=node - matches Eve runner environment
--target=node20 - uses CommonJS (commander uses
--format=cjsinternally)require() - Shebang prepended separately (esbuild escapes
--bannerin!)#!/usr/bin/env - Result: 50-200KB single file, no needed at runtime
node_modules
Commit to the repo so it's available immediately after clone.
cli/bin/myapp使用esbuild生成单文件、零运行时依赖的可执行文件:
- :内联所有导入(包括
--bundle)commander - :针对Node.js内置模块
--platform=node - :匹配Eve运行环境
--target=node20 - :使用CommonJS(commander内部使用
--format=cjs)require() - 单独添加shebang(esbuild的会转义
--banner中的#!/usr/bin/env)! - 结果:50-200KB的单文件,运行时无需
node_modules
将提交到代码库,以便克隆后即可直接使用。
cli/bin/myappImage-Based Distribution (Compiled CLIs)
基于镜像的分发(编译型CLI)
For Go, Rust, or other compiled CLIs:
yaml
services:
api:
x-eve:
cli:
name: myapp
image: ghcr.io/org/myapp-cli:latestBuild a Docker image with the CLI binary at :
/cli/bin/myappdockerfile
FROM rust:1.77 AS build
COPY . .
RUN cargo build --release
FROM busybox:stable
COPY /app/target/release/myapp /cli/bin/myappThe platform injects it via init container (same pattern as toolchains, ~2-5s latency).
对于Go、Rust或其他编译型语言开发的CLI:
yaml
services:
api:
x-eve:
cli:
name: myapp
image: ghcr.io/org/myapp-cli:latest构建Docker镜像,将CLI二进制文件放置在路径下:
/cli/bin/myappdockerfile
FROM rust:1.77 AS build
COPY . .
RUN cargo build --release
FROM busybox:stable
COPY /app/target/release/myapp /cli/bin/myapp平台会通过初始化容器注入该CLI(与工具链模式相同,延迟约2-5秒)。
See Also
相关链接
- in eve-read-eve-docs for the full technical reference
references/app-cli.md - for manifest schema details
references/manifest.md - for the Eve Auth SDK (server-side token verification)
references/eve-sdk.md
- eve-read-eve-docs中的:完整技术参考文档
references/app-cli.md - :清单文件Schema细节
references/manifest.md - :Eve认证SDK(服务端令牌验证)
references/eve-sdk.md