@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.
Files changed (45) hide show
  1. package/README-cn.md +166 -0
  2. package/README.md +120 -0
  3. package/base/config.js +162 -0
  4. package/base/core/compaction.js +58 -0
  5. package/base/core/harness.js +246 -0
  6. package/base/core/loop.js +72 -0
  7. package/base/core/prompt.js +126 -0
  8. package/base/core/session.js +255 -0
  9. package/base/events.js +54 -0
  10. package/base/i18n/index.js +80 -0
  11. package/base/i18n/locales/en.js +254 -0
  12. package/base/i18n/locales/zh-CN.js +252 -0
  13. package/base/llm/index.js +119 -0
  14. package/base/llm/providers/anthropic.js +147 -0
  15. package/base/llm/providers/openai.js +155 -0
  16. package/base/llm/sse.js +27 -0
  17. package/base/llm/trace.js +64 -0
  18. package/base/logger.js +57 -0
  19. package/base/memory/index.js +139 -0
  20. package/base/security/index.js +77 -0
  21. package/base/skills/loader.js +297 -0
  22. package/base/test/cli.test.js +91 -0
  23. package/base/test/config.test.js +63 -0
  24. package/base/test/core.test.js +154 -0
  25. package/base/test/i18n.test.js +32 -0
  26. package/base/test/loop.test.js +97 -0
  27. package/base/test/memory.test.js +47 -0
  28. package/base/test/message.test.js +38 -0
  29. package/base/test/session.test.js +324 -0
  30. package/base/test/skills.test.js +236 -0
  31. package/base/test/statusbar.test.js +143 -0
  32. package/base/test/tools.test.js +127 -0
  33. package/base/test/trace.test.js +62 -0
  34. package/base/test/tui.test.js +242 -0
  35. package/base/test/utils.test.js +35 -0
  36. package/base/tools/builtin.js +221 -0
  37. package/base/tools/index.js +157 -0
  38. package/base/transport/cli.js +417 -0
  39. package/base/transport/message.js +81 -0
  40. package/base/transport/statusbar.js +117 -0
  41. package/base/transport/tui.js +397 -0
  42. package/base/utils.js +150 -0
  43. package/docs/TECH_DESIGN.md +544 -0
  44. package/index.js +175 -0
  45. 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
+ }