@migor/cli 0.7.1 → 0.8.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/CHANGELOG.md +115 -1
- package/README.md +18 -12
- package/dist/AGENTS.md +27 -6
- package/dist/cli.js +39 -11
- package/dist/create.js +6 -1
- package/dist/dev.js +8 -3
- package/dist/diff.js +23 -1
- package/dist/doctor.js +33 -2
- package/dist/inspector/fromTrace.js +7 -7
- package/dist/inspector/summary.js +1 -1
- package/dist/inspector/trace-view.css +2 -0
- package/dist/inspector/view.js +3 -3
- package/dist/inspector-page.html +1 -0
- package/dist/inspector.js +1 -1
- package/dist/report.js +36 -15
- package/dist/templates/README.md +62 -0
- package/dist/templates/capabilities/prompt/asset.md +3 -0
- package/dist/templates/capabilities/prompt/index.ts +9 -0
- package/dist/templates/capabilities/skill/index.ts +19 -0
- package/dist/templates/capabilities/subagent/index.ts +17 -0
- package/dist/templates/capabilities/subagent/system.md +3 -0
- package/dist/templates/capabilities/tool/index.ts +18 -0
- package/dist/templates/env +3 -0
- package/dist/templates/env.example +6 -0
- package/dist/templates/gitignore +6 -0
- package/dist/templates/package.json +20 -0
- package/dist/templates/scripts/copy-assets.mjs +26 -0
- package/dist/templates/src/main.ts +37 -0
- package/dist/templates/src/registry.ts +10 -0
- package/dist/templates/tsconfig.json +21 -0
- package/dist/templates.js +92 -270
- package/package.json +2 -2
package/dist/report.js
CHANGED
|
@@ -31,12 +31,16 @@ export function extractTrace(v) {
|
|
|
31
31
|
}
|
|
32
32
|
/** 打印一张按总耗时降序的能力排行(跨多行记录时按能力合并) */
|
|
33
33
|
/** 用法串(cli.ts 的子命令 `--help` 也从这里取,避免两处各写一份) */
|
|
34
|
-
export const USAGE = '用法:agentia report <trace.jsonl>';
|
|
34
|
+
export const USAGE = '用法:agentia report <trace.jsonl> [--json]';
|
|
35
35
|
export async function reportCommand(args) {
|
|
36
|
-
|
|
36
|
+
// `--json`:机器可读输出(stdout 只有一个 JSON 文档,无任何人类装饰)—— 便于脚本/CI 串接。
|
|
37
|
+
// 出错仍走 stderr 的 `错误:…` + 退出码 1、stdout 为空,脚本据此区分「有结果」与「没跑成」。
|
|
38
|
+
const json = args.includes('--json');
|
|
39
|
+
const positional = args.filter((a) => a !== '--json');
|
|
40
|
+
const file = positional[0];
|
|
37
41
|
// 失败一律**抛错**(而不是就地设 process.exitCode)—— 本命令是异步的,
|
|
38
42
|
// 就地设的 exitCode 会被 cli.ts 末尾那句 `process.exitCode = main(...)` 覆盖成 0。
|
|
39
|
-
if (file === undefined ||
|
|
43
|
+
if (file === undefined || positional.length > 1 || file.startsWith('--')) {
|
|
40
44
|
throw new Error(USAGE);
|
|
41
45
|
}
|
|
42
46
|
let raw;
|
|
@@ -101,37 +105,54 @@ export async function reportCommand(args) {
|
|
|
101
105
|
}
|
|
102
106
|
}
|
|
103
107
|
const rows = [...merged.values()].sort((a, b) => b.totalMs - a.totalMs || b.calls - a.calls);
|
|
108
|
+
const failRows = traces
|
|
109
|
+
.filter((t) => t.status === 'error')
|
|
110
|
+
.map((t) => {
|
|
111
|
+
const spans = (t.spans ?? []);
|
|
112
|
+
return {
|
|
113
|
+
traceId: t.traceId ?? null,
|
|
114
|
+
message: spans.find((s) => s?.error?.message)?.error?.message ?? null,
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
const totalMs = rows.reduce((n, r) => n + r.totalMs, 0);
|
|
118
|
+
const errTotal = rows.reduce((n, r) => n + r.errors, 0);
|
|
119
|
+
if (json) {
|
|
120
|
+
const payload = {
|
|
121
|
+
file,
|
|
122
|
+
runs: traces.length,
|
|
123
|
+
failed: traces.length - okRuns,
|
|
124
|
+
skippedLines: badLines,
|
|
125
|
+
failures: failRows,
|
|
126
|
+
capabilities: rows,
|
|
127
|
+
totals: { totalMs, errors: errTotal, capabilities: rows.length },
|
|
128
|
+
};
|
|
129
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
104
132
|
const pad = (s, n) => (s.length >= n ? s : s + ' '.repeat(n - s.length));
|
|
105
133
|
const fmtMs = (ms) => (ms >= 1000 ? `${(ms / 1000).toFixed(2)}s` : `${ms}ms`);
|
|
106
134
|
console.log(`trace 文件 ${file}`);
|
|
107
135
|
console.log(`runs ${traces.length}(失败 ${traces.length - okRuns})${badLines > 0 ? ` · 跳过无法解析 ${badLines} 行` : ''}`);
|
|
108
136
|
// 失败 run 要说清原因:只报「失败 N」而不给理由,对调优没用。而且首次运行最常见的失败
|
|
109
137
|
// (没配 API key)恰恰不产出任何 capability 行 —— 会走到下面「没有可归因的能力」提前返回。
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
const spans = (t.spans ?? []);
|
|
113
|
-
const msg = spans.find((s) => s?.error?.message)?.error?.message;
|
|
114
|
-
console.log(` ✗ ${t.traceId ?? '(无 traceId)'}${msg ? ` ${msg}` : ''}`);
|
|
138
|
+
for (const f of failRows.slice(0, 3)) {
|
|
139
|
+
console.log(` ✗ ${f.traceId ?? '(无 traceId)'}${f.message ? ` ${f.message}` : ''}`);
|
|
115
140
|
}
|
|
116
|
-
if (
|
|
117
|
-
console.log(` …另有 ${
|
|
141
|
+
if (failRows.length > 3)
|
|
142
|
+
console.log(` …另有 ${failRows.length - 3} 条失败 run`);
|
|
118
143
|
console.log('');
|
|
119
144
|
if (rows.length === 0) {
|
|
120
145
|
console.log('(没有可归因的能力:这些 trace 里既没有 capability span,也没有 tool.output 事件)');
|
|
121
146
|
return 0;
|
|
122
147
|
}
|
|
123
148
|
console.log(`${pad('capability', 34)} ${pad('calls', 6)} ${pad('err', 5)} ${pad('total', 9)} ${pad('max', 9)} ${pad('tokens', 9)} ${pad('cost', 12)}`);
|
|
124
|
-
let total = 0;
|
|
125
|
-
let errs = 0;
|
|
126
149
|
for (const r of rows) {
|
|
127
|
-
total += r.totalMs;
|
|
128
|
-
errs += r.errors;
|
|
129
150
|
console.log(`${pad(r.capability, 34)} ${pad(String(r.calls), 6)} ${pad(String(r.errors), 5)} ` +
|
|
130
151
|
`${pad(fmtMs(r.totalMs), 9)} ${pad(fmtMs(r.maxMs), 9)} ` +
|
|
131
152
|
`${pad(r.tokens != null ? String(r.tokens) : '-', 9)} ` +
|
|
132
153
|
`${pad(r.costUsd != null ? r.costUsd.toFixed(6) : '-', 12)}`);
|
|
133
154
|
}
|
|
134
155
|
console.log('');
|
|
135
|
-
console.log(`合计耗时 ${fmtMs(
|
|
156
|
+
console.log(`合计耗时 ${fmtMs(totalMs)} · 失败 ${errTotal} 次 · 能力 ${rows.length} 个`);
|
|
136
157
|
return 0;
|
|
137
158
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# __PROJECT_NAME__
|
|
2
|
+
|
|
3
|
+
基于 [Agentia](https://github.com/retrychx/agentia) 框架的 agent 应用。
|
|
4
|
+
|
|
5
|
+
## 目录约定
|
|
6
|
+
|
|
7
|
+
四分类目录,一能力一文件夹,每个能力是一个 default export 的类,用装饰器声明:
|
|
8
|
+
|
|
9
|
+
- `src/tools/<name>/` —— `@Tool` 工具:主 agent 可调用(input → value)
|
|
10
|
+
- `src/skills/<name>/` —— `@Skill` 技能:方法体内通过 `ctx.llm()` 调 LLM
|
|
11
|
+
- `src/subagents/<name>/` —— `@SubAgent` 子代理:按 system 角色设定独立跑一轮
|
|
12
|
+
- `src/prompts/<name>/` —— `@Prompt` 文本资产:.md 文件,按需拉取进上下文
|
|
13
|
+
|
|
14
|
+
目录名就是类型,不用记别名。
|
|
15
|
+
|
|
16
|
+
## 两条装配路线
|
|
17
|
+
|
|
18
|
+
1. **目录扫描**:`createApp({ discover: [...] })` 启动期按给定顺序扫各目录下的 `<name>/index.ts`,default export 为类时以文件夹名为 DI token 注册(见 `src/main.ts`)。
|
|
19
|
+
2. **显式装配**:`createApp({ providers, system })`,providers 来自 `src/registry.ts` 注册表(由 `agentia g` 自动维护,也可手工编辑)。
|
|
20
|
+
|
|
21
|
+
两者二选一或混用。
|
|
22
|
+
|
|
23
|
+
## 生成能力
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
agentia g tool my-tool # → src/tools/my-tool/
|
|
27
|
+
agentia g skill my-skill # → src/skills/my-skill/
|
|
28
|
+
agentia g prompt my-prompt # → src/prompts/my-prompt/(含 asset.md)
|
|
29
|
+
agentia g subagent my-agent # → src/subagents/my-agent/(含 system.md)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
生成的能力自动登记到 `src/registry.ts`。
|
|
33
|
+
|
|
34
|
+
## 运行
|
|
35
|
+
|
|
36
|
+
需要 Anthropic API key —— 填进脚手架已生成的 `.env` 即可(本文件在 `.gitignore` 里):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# .env
|
|
40
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm run dev -- "你的问题"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 构建与生产运行
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm run build # tsc → dist/ + .md 文本资产跟随拷贝(asset() 按文件位置解析,必须跟着 .js 走)
|
|
51
|
+
npm start -- "你的问题" # 跑编译产物 dist/main.js(部署/Docker 用这条)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
也可以用环境变量(适合 CI / 容器)——**真实环境变量优先,不会被 `.env` 覆盖**:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
58
|
+
npm run dev -- "你的问题"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`.env` 由 `src/main.ts` 首行的 `loadEnvFile()` 读取。框架**不会自动读**它 ——
|
|
62
|
+
读哪个文件、什么时候读由你的启动代码决定(这样「换目录跑」不会悄悄改变行为)。
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Skill } from '@migor/agentia';
|
|
2
|
+
import type { SkillContext } from '@migor/agentia';
|
|
3
|
+
|
|
4
|
+
/** __NAME__ 技能能力 */
|
|
5
|
+
export default class __CLASS_NAME__ {
|
|
6
|
+
@Skill({
|
|
7
|
+
description: '示例技能:就给定主题调用 LLM 产出要点',
|
|
8
|
+
schema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: { topic: { type: 'string' } },
|
|
11
|
+
required: ['topic'],
|
|
12
|
+
additionalProperties: false,
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
async __METHOD_NAME__(input: { topic: string }, ctx: SkillContext): Promise<string> {
|
|
16
|
+
const r = await ctx.llm({ prompt: `就「${input.topic}」给出三个要点` });
|
|
17
|
+
return r.text;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SubAgent, asset } from '@migor/agentia';
|
|
2
|
+
|
|
3
|
+
/** __NAME__ 子代理能力 */
|
|
4
|
+
export default class __CLASS_NAME__ {
|
|
5
|
+
@SubAgent({
|
|
6
|
+
description: '示例子代理:按 system.md 的角色设定独立处理任务',
|
|
7
|
+
schema: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: { task: { type: 'string' } },
|
|
10
|
+
required: ['task'],
|
|
11
|
+
additionalProperties: false,
|
|
12
|
+
},
|
|
13
|
+
system: asset(import.meta.url, './system.md'),
|
|
14
|
+
})
|
|
15
|
+
// 方法体不会执行:@SubAgent 只读取方法名与装饰器元数据,调用时由框架按 system 另起 agent 执行
|
|
16
|
+
__METHOD_NAME__(_input: { task: string }): void {}
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Tool } from '@migor/agentia';
|
|
2
|
+
|
|
3
|
+
/** __NAME__ 工具能力 */
|
|
4
|
+
export default class __CLASS_NAME__ {
|
|
5
|
+
@Tool({
|
|
6
|
+
description: '示例工具:回显输入',
|
|
7
|
+
schema: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: { text: { type: 'string' } },
|
|
10
|
+
required: ['text'],
|
|
11
|
+
additionalProperties: false,
|
|
12
|
+
},
|
|
13
|
+
strict: true,
|
|
14
|
+
})
|
|
15
|
+
__METHOD_NAME__(input: { text: string }): string {
|
|
16
|
+
return `echo: ${input.text}`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_NAME__",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "agentia dev",
|
|
7
|
+
"build": "tsc -p tsconfig.json && node scripts/copy-assets.mjs",
|
|
8
|
+
"start": "node dist/main.js",
|
|
9
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@migor/agentia": "^0.8.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@migor/cli": "^0.8.0",
|
|
16
|
+
"tsx": "^4.19.0",
|
|
17
|
+
"typescript": "^7.0.2",
|
|
18
|
+
"@types/node": "^22.0.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// 把能力文件夹里的 .md 文本资产拷进 dist/(与编译产物同相对路径)。
|
|
2
|
+
// asset(import.meta.url, './x.md') 按**文件位置**解析,所以 .md 必须跟着 .js 走。
|
|
3
|
+
import { cpSync, readdirSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const here = fileURLToPath(new URL('.', import.meta.url));
|
|
8
|
+
const srcDir = join(here, '..', 'src');
|
|
9
|
+
const outDir = join(here, '..', 'dist');
|
|
10
|
+
|
|
11
|
+
function walk(dir) {
|
|
12
|
+
const out = [];
|
|
13
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
14
|
+
const p = join(dir, e.name);
|
|
15
|
+
if (e.isDirectory()) out.push(...walk(p));
|
|
16
|
+
else if (e.name.endsWith('.md')) out.push(p);
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let n = 0;
|
|
22
|
+
for (const f of walk(srcDir)) {
|
|
23
|
+
cpSync(f, join(outDir, f.slice(srcDir.length + 1)));
|
|
24
|
+
n++;
|
|
25
|
+
}
|
|
26
|
+
console.log(`[copy-assets] ${n} 个 .md 资产 → dist/`);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { createApp, loadEnvFile, SystemPrompt } from '@migor/agentia';
|
|
4
|
+
|
|
5
|
+
// 读同目录的 .env(key 写文件里即可,不必每次 export)。框架**不自动**读 .env ——
|
|
6
|
+
// 读哪个文件、什么时候读由这里决定;已存在的真实环境变量优先,不会被文件覆盖。
|
|
7
|
+
// 想换路径/顺序:loadEnvFile({ path: '.env.local' }) 或直接删掉这一行改用自己的加载器。
|
|
8
|
+
loadEnvFile();
|
|
9
|
+
|
|
10
|
+
// 能力目录按**本文件位置**解析,不是 cwd:开发态(src/main.ts)解析到 src/<分类>/,
|
|
11
|
+
// 构建后(dist/main.js)解析到 dist/<分类>/ —— 从任何目录启动都成立。
|
|
12
|
+
// ⚠️ 别改回 cwd 相对写法(形如 src/tools 的字符串):那样 `node dist/main.js` 会去加载 src 下的
|
|
13
|
+
// .ts 源码,而装饰器不是可擦除的类型语法,Node 直接跑不了("Invalid or unexpected token")。
|
|
14
|
+
// filter:空分类目录在构建后不存在(tsc 不为空目录产出 dist/<分类>/),而 discover 对
|
|
15
|
+
// 显式给出的不存在路径是报错的 —— 「这类暂时没有能力」不该让启动失败。
|
|
16
|
+
const CAPABILITY_DIRS = ['tools', 'skills', 'prompts', 'subagents'];
|
|
17
|
+
const app = await createApp({
|
|
18
|
+
name: '__PROJECT_NAME__',
|
|
19
|
+
discover: CAPABILITY_DIRS.map((d) => fileURLToPath(new URL(d + '/', import.meta.url))).filter(
|
|
20
|
+
(dir) => existsSync(dir),
|
|
21
|
+
),
|
|
22
|
+
system: new SystemPrompt().add('role', '你是 __PROJECT_NAME__ 的主 agent,按任务自主调度菜单里的能力。', true),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const { result } = await app.run(
|
|
26
|
+
[{ role: 'user', content: process.argv[2] ?? '介绍一下你自己' }],
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// 注意:run 失败**不会抛**(硬失败被记进 result.error 与 trace 后正常返回)—— 不显式检查就会
|
|
30
|
+
// 「打印一行空白 + 退出 0」,让首次运行(比如忘了配 ANTHROPIC_API_KEY)看起来像成功。
|
|
31
|
+
if (result.error) {
|
|
32
|
+
console.error(`run 失败(stopReason=${result.stopReason}):${result.error.message}`);
|
|
33
|
+
console.error('提示:模型调用读 ANTHROPIC_API_KEY —— 填进 .env(首行 loadEnvFile() 会读)或 export 均可;');
|
|
34
|
+
console.error(' 换端点 / 注入自定义 client 见项目内 AGENTS.md。');
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
}
|
|
37
|
+
if (result.finalText) console.log(result.finalText);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Agentia 能力注册表 —— 由 agentia CLI 维护(agentia g 自动更新,也可手工编辑)
|
|
2
|
+
// @agentia:imports
|
|
3
|
+
// @agentia:imports-end
|
|
4
|
+
import type { Provider } from '@migor/agentia';
|
|
5
|
+
|
|
6
|
+
/** 显式装配路线:createApp({ providers, system: ... })(与 discover 目录扫描二选一或混用) */
|
|
7
|
+
export const providers: Provider[] = [
|
|
8
|
+
// @agentia:entries
|
|
9
|
+
// @agentia:entries-end
|
|
10
|
+
];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": [
|
|
7
|
+
"ES2022"
|
|
8
|
+
],
|
|
9
|
+
"strict": true,
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"outDir": "dist",
|
|
12
|
+
"types": [
|
|
13
|
+
"node"
|
|
14
|
+
],
|
|
15
|
+
"esModuleInterop": true,
|
|
16
|
+
"skipLibCheck": true
|
|
17
|
+
},
|
|
18
|
+
"include": [
|
|
19
|
+
"src"
|
|
20
|
+
]
|
|
21
|
+
}
|