@jpssff/vanor 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-cn.md +166 -0
- package/README.md +120 -0
- package/base/config.js +162 -0
- package/base/core/compaction.js +58 -0
- package/base/core/harness.js +246 -0
- package/base/core/loop.js +72 -0
- package/base/core/prompt.js +126 -0
- package/base/core/session.js +255 -0
- package/base/events.js +54 -0
- package/base/i18n/index.js +80 -0
- package/base/i18n/locales/en.js +254 -0
- package/base/i18n/locales/zh-CN.js +252 -0
- package/base/llm/index.js +119 -0
- package/base/llm/providers/anthropic.js +147 -0
- package/base/llm/providers/openai.js +155 -0
- package/base/llm/sse.js +27 -0
- package/base/llm/trace.js +64 -0
- package/base/logger.js +57 -0
- package/base/memory/index.js +139 -0
- package/base/security/index.js +77 -0
- package/base/skills/loader.js +297 -0
- package/base/test/cli.test.js +91 -0
- package/base/test/config.test.js +63 -0
- package/base/test/core.test.js +154 -0
- package/base/test/i18n.test.js +32 -0
- package/base/test/loop.test.js +97 -0
- package/base/test/memory.test.js +47 -0
- package/base/test/message.test.js +38 -0
- package/base/test/session.test.js +324 -0
- package/base/test/skills.test.js +236 -0
- package/base/test/statusbar.test.js +143 -0
- package/base/test/tools.test.js +127 -0
- package/base/test/trace.test.js +62 -0
- package/base/test/tui.test.js +242 -0
- package/base/test/utils.test.js +35 -0
- package/base/tools/builtin.js +221 -0
- package/base/tools/index.js +157 -0
- package/base/transport/cli.js +417 -0
- package/base/transport/message.js +81 -0
- package/base/transport/statusbar.js +117 -0
- package/base/transport/tui.js +397 -0
- package/base/utils.js +150 -0
- package/docs/TECH_DESIGN.md +544 -0
- package/index.js +175 -0
- package/package.json +33 -0
package/index.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Vanor 入口:解析子命令,选择 base / base-user,启动对应功能。
|
|
3
|
+
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import readline from "node:readline/promises";
|
|
8
|
+
import { stdin, stdout } from "node:process";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
loadConfig,
|
|
12
|
+
getPaths,
|
|
13
|
+
ensurePaths,
|
|
14
|
+
saveConfig,
|
|
15
|
+
validateConfig,
|
|
16
|
+
startupIssues,
|
|
17
|
+
} from "./base/config.js";
|
|
18
|
+
import { createI18n } from "./base/i18n/index.js";
|
|
19
|
+
import { Logger } from "./base/logger.js";
|
|
20
|
+
import { createHarness } from "./base/core/harness.js";
|
|
21
|
+
import { startCli, printSessions } from "./base/transport/cli.js";
|
|
22
|
+
import { loadSkills, defaultSkillDirs } from "./base/skills/loader.js";
|
|
23
|
+
import { c } from "./base/utils.js";
|
|
24
|
+
|
|
25
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
|
|
27
|
+
function pkgVersion() {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8")).version;
|
|
30
|
+
} catch {
|
|
31
|
+
return "0.0.0";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function runConfigWizard(paths, i18n) {
|
|
36
|
+
const { t } = i18n;
|
|
37
|
+
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
38
|
+
stdout.write(c.bold(`${t("index.configWizard.title")}\n`));
|
|
39
|
+
stdout.write(c.gray(`${t("index.configWizard.hint")}\n\n`));
|
|
40
|
+
|
|
41
|
+
const { raw } = loadConfig(paths.config);
|
|
42
|
+
const name = (await rl.question(t("index.configWizard.providerName"))).trim() || "default";
|
|
43
|
+
const type = (await rl.question(t("index.configWizard.type"))).trim() || "openai";
|
|
44
|
+
let baseURL = "";
|
|
45
|
+
if (type === "openai") {
|
|
46
|
+
baseURL = (await rl.question(t("index.configWizard.baseURL"))).trim() || "https://api.openai.com/v1";
|
|
47
|
+
}
|
|
48
|
+
const apiKey = (await rl.question(t("index.configWizard.apiKey"))).trim();
|
|
49
|
+
const modelId = (await rl.question(t("index.configWizard.modelId"))).trim();
|
|
50
|
+
rl.close();
|
|
51
|
+
|
|
52
|
+
raw.llm = raw.llm || {};
|
|
53
|
+
raw.llm.providers = raw.llm.providers || {};
|
|
54
|
+
raw.llm.providers[name] = type === "openai" ? { type, baseURL, apiKey } : { type, apiKey };
|
|
55
|
+
raw.llm.defaultModel = `${name}/${modelId}`;
|
|
56
|
+
|
|
57
|
+
const file = saveConfig(paths.config, raw);
|
|
58
|
+
stdout.write(c.green(`\n${t("index.configWizard.saved", { file })}\n`));
|
|
59
|
+
stdout.write(c.gray(`${t("index.configWizard.defaultModel", { model: raw.llm.defaultModel })}\n${t("index.configWizard.next")}\n`));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function runDoctor(config, paths, configExists, i18n) {
|
|
63
|
+
const { t } = i18n;
|
|
64
|
+
stdout.write(c.bold(`${t("index.doctor.title")}\n`));
|
|
65
|
+
stdout.write(`Node: ${process.version}\n`);
|
|
66
|
+
const state = configExists ? c.green(t("common.exists")) : c.yellow(t("common.missing"));
|
|
67
|
+
stdout.write(`${t("index.doctor.configFile", { path: paths.config, state })}\n`);
|
|
68
|
+
stdout.write(`${t("index.doctor.workspace", { workspace: config.security.workspaceRoot })}\n`);
|
|
69
|
+
stdout.write(`${t("index.doctor.approval", { approval: config.security.approval })}\n`);
|
|
70
|
+
|
|
71
|
+
const issues = validateConfig(config, t);
|
|
72
|
+
if (config.security.approval === "auto") {
|
|
73
|
+
issues.push(t("index.doctor.autoRisk"));
|
|
74
|
+
}
|
|
75
|
+
if (!issues.length) {
|
|
76
|
+
stdout.write(c.green(`\n${t("index.doctor.ok")}\n`));
|
|
77
|
+
} else {
|
|
78
|
+
stdout.write(c.yellow(`\n${t("index.doctor.issues")}\n`));
|
|
79
|
+
for (const i of issues) stdout.write(` - ${i}\n`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function startChat(config, paths, logger, options = {}, i18n = createI18n(config)) {
|
|
84
|
+
const { t } = i18n;
|
|
85
|
+
const issues = startupIssues(config, t);
|
|
86
|
+
if (issues.length) {
|
|
87
|
+
stdout.write(c.yellow(`${t("index.chat.setupIncomplete")}\n`));
|
|
88
|
+
for (const i of issues) stdout.write(` - ${i}\n`);
|
|
89
|
+
stdout.write(c.gray(`\n${t("index.chat.runConfig")}\n`));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (options.sessionId) {
|
|
93
|
+
const id = path.basename(options.sessionId).replace(/\.jsonl$/, "");
|
|
94
|
+
const file = path.join(paths.sessions, `${id}.jsonl`);
|
|
95
|
+
if (!fs.existsSync(file)) {
|
|
96
|
+
stdout.write(c.red(`${t("index.chat.sessionNotFound", { id: options.sessionId })}\n`));
|
|
97
|
+
stdout.write(c.gray(`${t("index.chat.sessionListHint")}\n`));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
options = { ...options, sessionId: id };
|
|
101
|
+
}
|
|
102
|
+
const restoreLatest = options.restoreLatest ?? config.session?.restore !== "none";
|
|
103
|
+
const harness = createHarness({ config, paths, logger, sessionId: options.sessionId, restoreLatest });
|
|
104
|
+
return startCli(harness, { config });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function main() {
|
|
108
|
+
const argv = process.argv.slice(2);
|
|
109
|
+
const cmd = argv[0];
|
|
110
|
+
const paths = getPaths();
|
|
111
|
+
ensurePaths(paths);
|
|
112
|
+
const { config, exists } = loadConfig(paths.config);
|
|
113
|
+
const i18n = createI18n(config);
|
|
114
|
+
const { t } = i18n;
|
|
115
|
+
const logger = new Logger({ dir: paths.logs, level: config.logging?.level || "info" });
|
|
116
|
+
|
|
117
|
+
switch (cmd) {
|
|
118
|
+
case "config":
|
|
119
|
+
await runConfigWizard(paths, i18n);
|
|
120
|
+
return;
|
|
121
|
+
case "doctor":
|
|
122
|
+
runDoctor(config, paths, exists, i18n);
|
|
123
|
+
return;
|
|
124
|
+
case "sessions":
|
|
125
|
+
printSessions(paths, i18n);
|
|
126
|
+
return;
|
|
127
|
+
case "resume":
|
|
128
|
+
await startChat(config, paths, logger, { sessionId: argv[1], restoreLatest: !argv[1] }, i18n);
|
|
129
|
+
return;
|
|
130
|
+
case "skills": {
|
|
131
|
+
const skills = loadSkills(defaultSkillDirs(paths, config.security.workspaceRoot), logger);
|
|
132
|
+
if (!skills.length) stdout.write(`${t("index.skills.none")}\n`);
|
|
133
|
+
for (const s of skills) stdout.write(`- ${s.name}: ${s.description}\n`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
case "update":
|
|
137
|
+
stdout.write(`${t("index.update")}\n`);
|
|
138
|
+
return;
|
|
139
|
+
case "version":
|
|
140
|
+
case "-v":
|
|
141
|
+
case "--version":
|
|
142
|
+
stdout.write(`vanor ${pkgVersion()}\n`);
|
|
143
|
+
return;
|
|
144
|
+
case "help":
|
|
145
|
+
case "--help":
|
|
146
|
+
stdout.write(
|
|
147
|
+
[
|
|
148
|
+
t("index.help.usage"),
|
|
149
|
+
"",
|
|
150
|
+
` ${t("index.help.chat")}`,
|
|
151
|
+
` ${t("index.help.config")}`,
|
|
152
|
+
` ${t("index.help.doctor")}`,
|
|
153
|
+
` ${t("index.help.sessions")}`,
|
|
154
|
+
` ${t("index.help.resume")}`,
|
|
155
|
+
` ${t("index.help.skills")}`,
|
|
156
|
+
` ${t("index.help.update")}`,
|
|
157
|
+
` ${t("index.help.version")}`,
|
|
158
|
+
"",
|
|
159
|
+
].join("\n"),
|
|
160
|
+
);
|
|
161
|
+
return;
|
|
162
|
+
default:
|
|
163
|
+
if (cmd && cmd.startsWith("-") === false) {
|
|
164
|
+
stdout.write(c.red(`${t("index.unknownCommand", { cmd })}\n`));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
await startChat(config, paths, logger, {}, i18n);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
main().catch((e) => {
|
|
172
|
+
const { t } = createI18n();
|
|
173
|
+
stdout.write(`${t("index.fatal", { error: e.stack || e.message })}\n`);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jpssff/vanor",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A zero-dependency LLM-powered CLI agent for developers by Wanyou Intelligence",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vanor": "index.js"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"start": "node index.js",
|
|
14
|
+
"test": "node --test \"base/test/*.test.js\""
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"index.js",
|
|
18
|
+
"base",
|
|
19
|
+
"README-cn.md",
|
|
20
|
+
"docs"
|
|
21
|
+
],
|
|
22
|
+
"keywords": [
|
|
23
|
+
"agent",
|
|
24
|
+
"llm",
|
|
25
|
+
"cli",
|
|
26
|
+
"ai",
|
|
27
|
+
"vanor",
|
|
28
|
+
"wanyou",
|
|
29
|
+
"wanyouzhisuan",
|
|
30
|
+
"Wanyou Intelligence"
|
|
31
|
+
],
|
|
32
|
+
"license": "MIT"
|
|
33
|
+
}
|