ningzhi-agent-cli 0.0.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/CLAUDE.md +197 -0
- package/README.md +35 -0
- package/dist/agent/agent.d.ts +24 -0
- package/dist/agent/agent.js +432 -0
- package/dist/agent/cli.d.ts +1 -0
- package/dist/agent/cli.js +209 -0
- package/dist/agent/colors.d.ts +19 -0
- package/dist/agent/colors.js +92 -0
- package/dist/agent/commands.d.ts +7 -0
- package/dist/agent/commands.js +73 -0
- package/dist/agent/config.d.ts +38 -0
- package/dist/agent/config.js +119 -0
- package/dist/agent/context.d.ts +17 -0
- package/dist/agent/context.js +146 -0
- package/dist/agent/db.d.ts +25 -0
- package/dist/agent/db.js +185 -0
- package/dist/agent/hooks/engine.d.ts +27 -0
- package/dist/agent/hooks/engine.js +122 -0
- package/dist/agent/hooks/index.d.ts +1 -0
- package/dist/agent/hooks/index.js +11 -0
- package/dist/agent/mcp/client.d.ts +31 -0
- package/dist/agent/mcp/client.js +138 -0
- package/dist/agent/mcp/index.d.ts +4 -0
- package/dist/agent/mcp/index.js +27 -0
- package/dist/agent/mcp/wrapper.d.ts +7 -0
- package/dist/agent/mcp/wrapper.js +26 -0
- package/dist/agent/model.d.ts +5 -0
- package/dist/agent/model.js +35 -0
- package/dist/agent/permission/dangerous-path.json +115 -0
- package/dist/agent/permission/exec.d.ts +14 -0
- package/dist/agent/permission/exec.js +35 -0
- package/dist/agent/permission/is-dangerous-path.d.ts +8 -0
- package/dist/agent/permission/is-dangerous-path.js +96 -0
- package/dist/agent/permission/is-safe-domains.d.ts +1 -0
- package/dist/agent/permission/is-safe-domains.js +145 -0
- package/dist/agent/permission/network.d.ts +8 -0
- package/dist/agent/permission/network.js +14 -0
- package/dist/agent/permission/read.d.ts +9 -0
- package/dist/agent/permission/read.js +17 -0
- package/dist/agent/permission/util.d.ts +11 -0
- package/dist/agent/permission/util.js +347 -0
- package/dist/agent/permission/write.d.ts +11 -0
- package/dist/agent/permission/write.js +21 -0
- package/dist/agent/prompt.d.ts +1 -0
- package/dist/agent/prompt.js +52 -0
- package/dist/agent/skills/oppose/SKILL.md +100 -0
- package/dist/agent/skills/planner/SKILL.md +90 -0
- package/dist/agent/skills.d.ts +8 -0
- package/dist/agent/skills.js +103 -0
- package/dist/agent/tools/agent_tool.d.ts +3 -0
- package/dist/agent/tools/agent_tool.js +44 -0
- package/dist/agent/tools/exec_tool.d.ts +3 -0
- package/dist/agent/tools/exec_tool.js +33 -0
- package/dist/agent/tools/load_skill_tool.d.ts +3 -0
- package/dist/agent/tools/load_skill_tool.js +16 -0
- package/dist/agent/tools/memory_create_tool.d.ts +6 -0
- package/dist/agent/tools/memory_create_tool.js +39 -0
- package/dist/agent/tools/memory_delete_tool.d.ts +3 -0
- package/dist/agent/tools/memory_delete_tool.js +28 -0
- package/dist/agent/tools/memory_retrieve_tool.d.ts +4 -0
- package/dist/agent/tools/memory_retrieve_tool.js +26 -0
- package/dist/agent/tools/profile_update_tool.d.ts +3 -0
- package/dist/agent/tools/profile_update_tool.js +51 -0
- package/dist/agent/tools/read_file_tool.d.ts +3 -0
- package/dist/agent/tools/read_file_tool.js +16 -0
- package/dist/agent/tools/run_js_tool.d.ts +3 -0
- package/dist/agent/tools/run_js_tool.js +47 -0
- package/dist/agent/tools/run_py_tool.d.ts +3 -0
- package/dist/agent/tools/run_py_tool.js +37 -0
- package/dist/agent/tools/web_fetch_tool.d.ts +3 -0
- package/dist/agent/tools/web_fetch_tool.js +28 -0
- package/dist/agent/tools/web_search_tool.d.ts +3 -0
- package/dist/agent/tools/web_search_tool.js +38 -0
- package/dist/agent/tools/write_file_tool.d.ts +4 -0
- package/dist/agent/tools/write_file_tool.js +15 -0
- package/dist/agent/tools.d.ts +14 -0
- package/dist/agent/tools.js +218 -0
- package/dist/agent/utils.d.ts +11 -0
- package/dist/agent/utils.js +43 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +81 -0
- package/dist/install.d.ts +1 -0
- package/dist/install.js +150 -0
- package/package.json +56 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.discoverSkills = discoverSkills;
|
|
4
|
+
exports.loadSkill = loadSkill;
|
|
5
|
+
exports.getSkillsListText = getSkillsListText;
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const node_os_1 = require("node:os");
|
|
9
|
+
const skills = [];
|
|
10
|
+
const skillContentMap = new Map();
|
|
11
|
+
// 解析 SKILL.md 文件顶部的 frontmatter(用 --- 包裹的 YAML 头)
|
|
12
|
+
// 提取其中的 name 和 description 字段,供技能注册使用
|
|
13
|
+
function parseFrontmatter(content) {
|
|
14
|
+
// 校验:必须以 "---" 开头,否则不是合法的 frontmatter
|
|
15
|
+
if (!content.startsWith("---")) {
|
|
16
|
+
return { name: "", description: "" };
|
|
17
|
+
}
|
|
18
|
+
// 查找闭合的 "---"(从索引 3 开始,跳过开头那一个)
|
|
19
|
+
const endIdx = content.indexOf("---", 3);
|
|
20
|
+
if (endIdx === -1) {
|
|
21
|
+
return { name: "", description: "" };
|
|
22
|
+
}
|
|
23
|
+
// 截取两个 "---" 之间的内容,按行解析为 key-value 键值对
|
|
24
|
+
const frontmatter = content.slice(3, endIdx).trim();
|
|
25
|
+
const lines = frontmatter.split("\n");
|
|
26
|
+
const result = {};
|
|
27
|
+
for (const line of lines) {
|
|
28
|
+
// 每行格式为 "key: value",没有冒号的行直接跳过
|
|
29
|
+
const colonIdx = line.indexOf(":");
|
|
30
|
+
if (colonIdx === -1)
|
|
31
|
+
continue;
|
|
32
|
+
const key = line.slice(0, colonIdx).trim();
|
|
33
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
34
|
+
result[key] = value;
|
|
35
|
+
}
|
|
36
|
+
// 返回 name 和 description,缺失时回退为空字符串
|
|
37
|
+
return { name: result.name || "", description: result.description || "" };
|
|
38
|
+
}
|
|
39
|
+
// 发现所有技能目录下的 SKILL.md 文件
|
|
40
|
+
function discoverSkills() {
|
|
41
|
+
// 重置缓存:清空技能列表和内容映射,避免重复累积
|
|
42
|
+
skills.length = 0;
|
|
43
|
+
skillContentMap.clear();
|
|
44
|
+
const skillDirs = [
|
|
45
|
+
(0, node_path_1.join)((0, node_os_1.homedir)(), ".agents", "skills"), // 全局skill
|
|
46
|
+
(0, node_path_1.join)((0, node_os_1.homedir)(), ".ningzhiAgentCli", ".agents", "skills"), // 工作空间,第三方安装的skill
|
|
47
|
+
(0, node_path_1.join)((0, node_os_1.homedir)(), ".ningzhiAgentCli", "skills"), // 工作空间,用户自己创建的
|
|
48
|
+
(0, node_path_1.join)(__dirname, "skills"), // cli 内置的 skill
|
|
49
|
+
];
|
|
50
|
+
for (const skillsDir of skillDirs) {
|
|
51
|
+
let entries;
|
|
52
|
+
try {
|
|
53
|
+
entries = (0, fs_1.readdirSync)(skillsDir); // 读取目录下的所有文件和子目录
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
const entryPath = (0, node_path_1.join)(skillsDir, entry); // entryPath 是当前目录下的文件或子目录路径,包含 skillsDir
|
|
60
|
+
try {
|
|
61
|
+
const entryStat = (0, fs_1.statSync)(entryPath); // 获取文件或目录的元信息
|
|
62
|
+
if (!entryStat.isDirectory())
|
|
63
|
+
continue;
|
|
64
|
+
const skillMdPath = (0, node_path_1.join)(entryPath, "SKILL.md");
|
|
65
|
+
const content = (0, fs_1.readFileSync)(skillMdPath, "utf-8");
|
|
66
|
+
const { name, description } = parseFrontmatter(content);
|
|
67
|
+
if (name && description) {
|
|
68
|
+
const existing = skills.find((s) => s.name === name);
|
|
69
|
+
if (existing) {
|
|
70
|
+
existing.description = description;
|
|
71
|
+
existing.dirPath = entryPath;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
skills.push({ name, description, dirPath: entryPath });
|
|
75
|
+
}
|
|
76
|
+
skillContentMap.set(name, content);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// 忽略没有 SKILL.md 的目录或解析失败的情况
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return skills;
|
|
85
|
+
}
|
|
86
|
+
// 根据技能名称从缓存中加载完整的 SKILL.md 内容
|
|
87
|
+
function loadSkill(name) {
|
|
88
|
+
return skillContentMap.get(name) ?? null;
|
|
89
|
+
}
|
|
90
|
+
// 生成技能列表的 Markdown 文本,用于展示给用户或 LLM
|
|
91
|
+
function getSkillsListText() {
|
|
92
|
+
// 将每个技能格式化为 Markdown 无序列表项: "- **技能名**: 描述"
|
|
93
|
+
const lines = skills.map((s) => `- **${s.name}**: ${s.description}`);
|
|
94
|
+
if (skills.length > 0) {
|
|
95
|
+
lines.push("");
|
|
96
|
+
lines.push("skills 的目录:");
|
|
97
|
+
lines.push((0, node_path_1.join)((0, node_os_1.homedir)(), ".agents", "skills"));
|
|
98
|
+
lines.push((0, node_path_1.join)((0, node_os_1.homedir)(), ".ningzhiAgentCli", ".agents", "skills"));
|
|
99
|
+
lines.push((0, node_path_1.join)((0, node_os_1.homedir)(), ".ningzhiAgentCli", "skills"));
|
|
100
|
+
lines.push(`如果增加新 skill,必须放在 ${(0, node_path_1.join)((0, node_os_1.homedir)(), ".ningzhiAgentCli", "skills")} 目录下`);
|
|
101
|
+
}
|
|
102
|
+
return lines.join("\n");
|
|
103
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.agentTool = agentTool;
|
|
37
|
+
async function agentTool({ prompt, }) {
|
|
38
|
+
if (!prompt || !prompt.trim()) {
|
|
39
|
+
return "Error: prompt is empty.";
|
|
40
|
+
}
|
|
41
|
+
// 动态 import 避免循环依赖:agent.ts -> tools.ts -> agent_tool.ts -> agent.ts
|
|
42
|
+
const { runSubAgent } = await Promise.resolve().then(() => __importStar(require("../agent")));
|
|
43
|
+
return runSubAgent(prompt.trim());
|
|
44
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.execTool = execTool;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
const node_util_1 = require("node:util");
|
|
6
|
+
const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
7
|
+
async function execTool({ command, }) {
|
|
8
|
+
const trimmed = command.trim();
|
|
9
|
+
if (!trimmed) {
|
|
10
|
+
return "Error: command is empty.";
|
|
11
|
+
}
|
|
12
|
+
if (trimmed.includes("..")) {
|
|
13
|
+
return "Error: parent directory references (..) are not allowed.";
|
|
14
|
+
}
|
|
15
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
16
|
+
const args = spaceIdx > 0 ? trimmed.slice(spaceIdx) : "";
|
|
17
|
+
// 在命令参数中查找 Unix 风格的绝对路径(以 / 开头),禁止使用绝对路径以限制文件访问范围
|
|
18
|
+
// (?:^|\s) 匹配行首或空白符位置,(\/[^ ]+) 捕获以 / 开头、不含空格的路径
|
|
19
|
+
const absolutePathMatch = args.match(/(?:^|\s)(\/[^ ]+)/);
|
|
20
|
+
if (absolutePathMatch) {
|
|
21
|
+
return `Error: absolute paths are not allowed: "${absolutePathMatch[1]}"`;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const { stdout, stderr } = await execAsync(trimmed, {
|
|
25
|
+
cwd: process.cwd(),
|
|
26
|
+
timeout: 30000,
|
|
27
|
+
});
|
|
28
|
+
return stdout || stderr || "Command executed successfully with no output.";
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
return `Error: ${err.message}`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadSkillTool = loadSkillTool;
|
|
4
|
+
const skills_1 = require("../skills");
|
|
5
|
+
// 根据 skill 名称 返回整个 SKILL.md 文件内容
|
|
6
|
+
async function loadSkillTool({ name, }) {
|
|
7
|
+
const trimmed = name.trim();
|
|
8
|
+
if (!trimmed) {
|
|
9
|
+
return "Error: skill name is empty.";
|
|
10
|
+
}
|
|
11
|
+
const content = (0, skills_1.loadSkill)(trimmed);
|
|
12
|
+
if (!content) {
|
|
13
|
+
return `Error: skill "${trimmed}" not found.`;
|
|
14
|
+
}
|
|
15
|
+
return content;
|
|
16
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.memoryCreateTool = memoryCreateTool;
|
|
7
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
8
|
+
const db_1 = require("../db");
|
|
9
|
+
const VALID_TYPES = ["fact", "event", "preference", "skill"];
|
|
10
|
+
async function memoryCreateTool({ type, content, keywords, importance, }, config) {
|
|
11
|
+
if (!VALID_TYPES.includes(type)) {
|
|
12
|
+
return `Error: type must be one of ${VALID_TYPES.join(", ")}.`;
|
|
13
|
+
}
|
|
14
|
+
const trimmed = content?.trim();
|
|
15
|
+
if (!trimmed) {
|
|
16
|
+
return "Error: content is required.";
|
|
17
|
+
}
|
|
18
|
+
const importanceValue = importance ?? 3;
|
|
19
|
+
if (importanceValue < 1 || importanceValue > 5) {
|
|
20
|
+
return "Error: importance must be between 1 and 5.";
|
|
21
|
+
}
|
|
22
|
+
const sessionId = config?.configurable?.thread_id ?? "default-session";
|
|
23
|
+
const keywordsJson = keywords && keywords.length > 0 ? JSON.stringify(keywords) : null;
|
|
24
|
+
const db = new better_sqlite3_1.default(db_1.DB_PATH);
|
|
25
|
+
try {
|
|
26
|
+
// stmt指的是预处理语句(prepared statement)。预处理语句是一种数据库功能,允许开发者编写一次SQL语句,然后多次执行,只需传递不同的参数。
|
|
27
|
+
// 这种方法提高了性能,因为SQL语句只解析一次,而不是每次执行时都解析,同时也减少了网络通信量
|
|
28
|
+
const stmt = db.prepare(`INSERT INTO memory (type, content, keywords, importance, session_id)
|
|
29
|
+
VALUES (?, ?, ?, ?, ?)`);
|
|
30
|
+
stmt.run(type, trimmed, keywordsJson, importanceValue, sessionId);
|
|
31
|
+
return "Memory saved successfully.";
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
return `Error: ${err.message}`;
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
db.close();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.memoryDeleteTool = memoryDeleteTool;
|
|
7
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
8
|
+
const db_1 = require("../db");
|
|
9
|
+
async function memoryDeleteTool({ id, }, _config) {
|
|
10
|
+
if (!Number.isFinite(id) || id <= 0) {
|
|
11
|
+
return "Error: id must be a positive integer.";
|
|
12
|
+
}
|
|
13
|
+
const db = new better_sqlite3_1.default(db_1.DB_PATH);
|
|
14
|
+
try {
|
|
15
|
+
const existing = db.prepare("SELECT 1 FROM memory WHERE id = ?").get(id);
|
|
16
|
+
if (!existing) {
|
|
17
|
+
return `Error: no memory found with id ${id}.`;
|
|
18
|
+
}
|
|
19
|
+
db.prepare("DELETE FROM memory WHERE id = ?").run(id);
|
|
20
|
+
return `Memory ${id} deleted successfully.`;
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
return `Error: ${err.message}`;
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
db.close();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.memoryRetrieveTool = memoryRetrieveTool;
|
|
4
|
+
const db_1 = require("../db");
|
|
5
|
+
async function memoryRetrieveTool({ query, limit, }, config) {
|
|
6
|
+
const trimmedQueries = query
|
|
7
|
+
?.map((q) => q.trim())
|
|
8
|
+
.filter((q) => q.length > 0);
|
|
9
|
+
if (!trimmedQueries || trimmedQueries.length === 0) {
|
|
10
|
+
return "Error: query is required.";
|
|
11
|
+
}
|
|
12
|
+
const limitValue = limit ?? 10;
|
|
13
|
+
if (limitValue < 1 || limitValue > 50) {
|
|
14
|
+
return "Error: limit must be between 1 and 50.";
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const results = (0, db_1.searchMemories)(trimmedQueries, limitValue);
|
|
18
|
+
if (results.length === 0) {
|
|
19
|
+
return "No relevant memories found.";
|
|
20
|
+
}
|
|
21
|
+
return JSON.stringify(results);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
return `Error: ${err.message}`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.profileUpdateTool = profileUpdateTool;
|
|
4
|
+
const promises_1 = require("node:fs/promises");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const node_os_1 = require("node:os");
|
|
7
|
+
function getProfilePaths() {
|
|
8
|
+
const dir = (0, node_path_1.resolve)((0, node_os_1.homedir)(), ".ningzhiAgentCli", ".data");
|
|
9
|
+
return { dir, path: (0, node_path_1.resolve)(dir, "profile.md") };
|
|
10
|
+
}
|
|
11
|
+
function generateBackupFilename() {
|
|
12
|
+
const dt = Date.now();
|
|
13
|
+
const random = Math.random().toString(36).slice(2, 8);
|
|
14
|
+
return `profile.${dt}-${random}.md`;
|
|
15
|
+
}
|
|
16
|
+
async function profileUpdateTool({ profile_info, }) {
|
|
17
|
+
if (!profile_info || profile_info.trim().length === 0) {
|
|
18
|
+
return "Error: profile_info is empty.";
|
|
19
|
+
}
|
|
20
|
+
const { dir: profileDir, path: profilePath } = getProfilePaths();
|
|
21
|
+
try {
|
|
22
|
+
let existingContent = "";
|
|
23
|
+
let fileExists = false;
|
|
24
|
+
try {
|
|
25
|
+
existingContent = await (0, promises_1.readFile)(profilePath, "utf-8");
|
|
26
|
+
fileExists = true;
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
if (err.code !== "ENOENT") {
|
|
30
|
+
return `Error reading existing profile: ${err.message}`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const normalizedNew = profile_info.trim();
|
|
34
|
+
const normalizedExisting = existingContent.trim();
|
|
35
|
+
if (fileExists && normalizedNew === normalizedExisting) {
|
|
36
|
+
return "Profile is already up to date. No changes made.";
|
|
37
|
+
}
|
|
38
|
+
await (0, promises_1.mkdir)(profileDir, { recursive: true }); // 幂等 递归创建目录
|
|
39
|
+
if (fileExists) {
|
|
40
|
+
const backupFilename = generateBackupFilename();
|
|
41
|
+
const backupPath = (0, node_path_1.resolve)(profileDir, backupFilename);
|
|
42
|
+
await (0, promises_1.copyFile)(profilePath, backupPath);
|
|
43
|
+
}
|
|
44
|
+
// 更新 profile.md 文件
|
|
45
|
+
await (0, promises_1.writeFile)(profilePath, profile_info, "utf-8");
|
|
46
|
+
return `Profile updated successfully. File: ${profilePath}${fileExists ? " (backup created)" : ""}`;
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
return `Error: ${err.message}`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readFileTool = readFileTool;
|
|
4
|
+
const promises_1 = require("node:fs/promises");
|
|
5
|
+
async function readFileTool({ filepath, }) {
|
|
6
|
+
try {
|
|
7
|
+
const content = await (0, promises_1.readFile)(filepath, "utf-8");
|
|
8
|
+
return content;
|
|
9
|
+
}
|
|
10
|
+
catch (err) {
|
|
11
|
+
if (err.code === "ENOENT") {
|
|
12
|
+
return "Error: file not found.";
|
|
13
|
+
}
|
|
14
|
+
return `Error: ${err.message}`;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runJsTool = runJsTool;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
const node_util_1 = require("node:util");
|
|
6
|
+
const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
7
|
+
async function runJsTool({ code }) {
|
|
8
|
+
const trimmed = code.trim();
|
|
9
|
+
if (!trimmed) {
|
|
10
|
+
return "Error: code is empty.";
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
await execAsync("node --version", { timeout: 5000 });
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return "Error: Node.js is not installed on this system. Please install Node.js to execute JavaScript code.";
|
|
17
|
+
}
|
|
18
|
+
// 将源码转成 base64 再嵌入命令,避免源码中的引号、换行、$、反引号等字符破坏 shell 命令结构
|
|
19
|
+
const base64Code = Buffer.from(trimmed).toString("base64");
|
|
20
|
+
// 用 node -e 执行一段 JS:在子进程里把 base64 解码回原始代码,再 eval 执行
|
|
21
|
+
const command = `node -e "eval(Buffer.from('${base64Code}', 'base64').toString('utf-8'))"`;
|
|
22
|
+
try {
|
|
23
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
24
|
+
cwd: process.cwd(),
|
|
25
|
+
timeout: 30000,
|
|
26
|
+
encoding: "utf8",
|
|
27
|
+
});
|
|
28
|
+
// 全面清理子进程输出中的脏字符(兼容 MINGW64/Git Bash、cmd.exe GBK、ANSICON 等多种环境)
|
|
29
|
+
// 1. 去 ANSI 转义序列(颜色、光标控制等,Git Bash 常见)
|
|
30
|
+
// 2. 去 BOM、零宽字符、方向控制、替换符等 Unicode 格式字符
|
|
31
|
+
// 3. 去 ASCII 控制字符(0x00-0x1F),保留换行\n、制表符\t;并把孤立的 \r 回车符删掉
|
|
32
|
+
// 4. 把连续多个换行折叠成单个(避免 cmd/bash 追加的额外空行)
|
|
33
|
+
const clean = (s) => s
|
|
34
|
+
.replace(/\x1B\[[0-9;?]*[A-Za-z]/g, "")
|
|
35
|
+
.replace(/\uFEFF|[\u200B-\u200D\u200E\u200F\uFFFD]/g, "")
|
|
36
|
+
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]|\r/g, "")
|
|
37
|
+
.replace(/\n{2,}/g, "\n");
|
|
38
|
+
const cleanedStdout = clean(stdout || "").trim();
|
|
39
|
+
const cleanedStderr = clean(stderr || "").trim();
|
|
40
|
+
return (cleanedStdout ||
|
|
41
|
+
cleanedStderr ||
|
|
42
|
+
"Code executed successfully with no output.");
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
return `Error: ${err.message}`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runPyTool = runPyTool;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
const node_util_1 = require("node:util");
|
|
6
|
+
// 将 exec 转为 Promise 形式,便于用 await 调用
|
|
7
|
+
const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
8
|
+
async function runPyTool({ code }) {
|
|
9
|
+
// 去除首尾空白,若代码为空直接返回错误
|
|
10
|
+
const trimmed = code.trim();
|
|
11
|
+
if (!trimmed) {
|
|
12
|
+
return "Error: code is empty.";
|
|
13
|
+
}
|
|
14
|
+
// 提前检测 python3 是否可用,避免后续执行时报错难以定位
|
|
15
|
+
try {
|
|
16
|
+
await execAsync("python3 --version", { timeout: 5000 });
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return "Error: Python3 is not installed on this system. Please install Python3 to execute Python code.";
|
|
20
|
+
}
|
|
21
|
+
// 将代码转成 base64,再在 Python 端解码执行
|
|
22
|
+
// 这样做是为了绕过 shell 对引号、换行、特殊字符的转义问题
|
|
23
|
+
const base64Code = Buffer.from(trimmed).toString("base64");
|
|
24
|
+
const command = `python3 -c "import base64; exec(base64.b64decode('${base64Code}').decode('utf-8'))"`;
|
|
25
|
+
try {
|
|
26
|
+
// 执行 Python 命令,设置工作目录和超时时间
|
|
27
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
28
|
+
cwd: process.cwd(), // 以当前进程目录为 Python 执行目录,保证相对路径一致
|
|
29
|
+
timeout: 30000, // 30 秒超时,防止死循环或长时间阻塞
|
|
30
|
+
});
|
|
31
|
+
// 优先返回 stdout,其次 stderr,都没有则返回成功提示
|
|
32
|
+
return stdout || stderr || "Code executed successfully with no output.";
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
return `Error: ${err.message}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.webFetchTool = webFetchTool;
|
|
4
|
+
const MAX_LENGTH = 8000;
|
|
5
|
+
async function webFetchTool({ url }) {
|
|
6
|
+
const trimmed = url.trim();
|
|
7
|
+
if (!trimmed) {
|
|
8
|
+
return "Error: url is empty.";
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const response = await fetch(trimmed, {
|
|
12
|
+
headers: {
|
|
13
|
+
"User-Agent": "Mozilla/5.0 (compatible; NingzhiBot/1.0)", // 模拟浏览器请求
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
return `Error: HTTP ${response.status} ${response.statusText}`;
|
|
18
|
+
}
|
|
19
|
+
const text = await response.text();
|
|
20
|
+
if (text.length > MAX_LENGTH) {
|
|
21
|
+
return (text.slice(0, MAX_LENGTH) + "\n\n[Content truncated due to length]");
|
|
22
|
+
}
|
|
23
|
+
return text;
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
return `Error: ${err.message}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.webSearchTool = webSearchTool;
|
|
4
|
+
const tavily_1 = require("@langchain/tavily");
|
|
5
|
+
const config_1 = require("../config");
|
|
6
|
+
async function webSearchTool({ query, }) {
|
|
7
|
+
const tavilyApiKey = (0, config_1.getEnv)("TAVILY_API_KEY");
|
|
8
|
+
if (!tavilyApiKey) {
|
|
9
|
+
return "Error: 未配置 TAVILY_API_KEY。请在 ~/.ningzhiAgentCli/ningzhi.json 的 env 中设置 TAVILY_API_KEY。";
|
|
10
|
+
}
|
|
11
|
+
const tavilySearch = new tavily_1.TavilySearch({
|
|
12
|
+
maxResults: 2,
|
|
13
|
+
topic: "general",
|
|
14
|
+
tavilyApiKey,
|
|
15
|
+
});
|
|
16
|
+
try {
|
|
17
|
+
const result = await tavilySearch.invoke({ query });
|
|
18
|
+
if ("error" in result && result.error) {
|
|
19
|
+
return `Error: ${result.error}`;
|
|
20
|
+
}
|
|
21
|
+
const lines = [];
|
|
22
|
+
lines.push(`Query: ${result.query}`);
|
|
23
|
+
if (result.answer) {
|
|
24
|
+
lines.push(`Answer: ${result.answer}`);
|
|
25
|
+
}
|
|
26
|
+
lines.push("");
|
|
27
|
+
lines.push("Results:");
|
|
28
|
+
for (let i = 0; i < result.results.length; i++) {
|
|
29
|
+
const r = result.results[i];
|
|
30
|
+
lines.push(`${i + 1}. ${r.title} (${r.url})`);
|
|
31
|
+
lines.push(` ${r.content}`);
|
|
32
|
+
}
|
|
33
|
+
return lines.join("\n");
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
return `Error: ${err.message}`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeFileTool = writeFileTool;
|
|
4
|
+
const promises_1 = require("node:fs/promises");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
async function writeFileTool({ filepath, content, }) {
|
|
7
|
+
try {
|
|
8
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(filepath), { recursive: true }); // recursive 表示创建父目录, 如果没有的话, 则创建
|
|
9
|
+
await (0, promises_1.writeFile)(filepath, content, "utf-8");
|
|
10
|
+
return `File "${filepath}" written successfully.`;
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
return `Error: ${err.message}`;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DynamicStructuredTool } from "@langchain/core/tools";
|
|
2
|
+
export type NingzhiTool = DynamicStructuredTool & {
|
|
3
|
+
permission_level: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* 可能持久化输出,如果输出长度超过最大限制则保存到文件
|
|
7
|
+
* @param {string} content - 要持久化的输出内容
|
|
8
|
+
* @param {string} toolCallId - 工具调用 ID,用于生成文件名
|
|
9
|
+
* @returns {Promise<string>} - 包含持久化信息的 HTML 片段
|
|
10
|
+
*/
|
|
11
|
+
export declare function maybePersistedOutput(content: string, toolCallId: string): Promise<string>;
|
|
12
|
+
export declare const nativeTools: NingzhiTool[];
|
|
13
|
+
export declare let tools: NingzhiTool[];
|
|
14
|
+
export declare function initTools(): Promise<void>;
|