@next-open-ai/openbot 0.1.10 → 0.2.8
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.md +108 -19
- package/{desktop → apps/desktop}/README.md +3 -3
- package/{desktop/renderer/dist/assets/index-J1y_YkPz.js → apps/desktop/renderer/dist/assets/index-BOS-F8a4.js} +34 -34
- package/{desktop/renderer/dist/assets/index-BBoPEPR6.css → apps/desktop/renderer/dist/assets/index-DxqxayUL.css} +2 -2
- package/{desktop → apps/desktop}/renderer/dist/index.html +3 -3
- package/dist/cli/cli.d.ts +2 -0
- package/dist/cli/cli.js +198 -0
- package/dist/cli/service.d.ts +13 -0
- package/dist/cli/service.js +243 -0
- package/dist/cli.d.ts +4 -1
- package/dist/cli.js +4 -172
- package/dist/gateway/auth-hooks.d.ts +17 -0
- package/dist/gateway/auth-hooks.js +19 -0
- package/dist/gateway/channel-handler.d.ts +6 -0
- package/dist/gateway/channel-handler.js +3 -0
- package/dist/gateway/index.d.ts +1 -1
- package/dist/gateway/index.js +1 -1
- package/dist/gateway/methods/agent-cancel.js +1 -1
- package/dist/gateway/methods/agent-chat.js +3 -3
- package/dist/gateway/methods/install-skill-from-path.js +1 -1
- package/dist/gateway/methods/run-scheduled-task.js +3 -3
- package/dist/gateway/paths.d.ts +20 -0
- package/dist/gateway/paths.js +19 -0
- package/dist/gateway/server.d.ts +2 -4
- package/dist/gateway/server.js +99 -208
- package/dist/gateway/sse-handler.d.ts +6 -0
- package/dist/gateway/sse-handler.js +3 -0
- package/dist/gateway/voice-handler.d.ts +12 -0
- package/dist/gateway/voice-handler.js +18 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +5 -5
- package/dist/server/agents/agents.service.js +1 -1
- package/dist/server/bootstrap.d.ts +15 -0
- package/dist/server/bootstrap.js +38 -0
- package/dist/server/config/config.service.d.ts +1 -1
- package/dist/server/config/config.service.js +1 -1
- package/dist/server/main.js +6 -19
- package/dist/server/skills/skills.service.js +1 -1
- package/dist/server/workspace/workspace.service.js +1 -1
- package/package.json +17 -12
- package/desktop/renderer/dist/assets/logo-RfPiqt5-.png +0 -0
- /package/dist/{agent → core/agent}/agent-dir.d.ts +0 -0
- /package/dist/{agent → core/agent}/agent-dir.js +0 -0
- /package/dist/{agent → core/agent}/agent-manager.d.ts +0 -0
- /package/dist/{agent → core/agent}/agent-manager.js +0 -0
- /package/dist/{agent → core/agent}/config-manager.d.ts +0 -0
- /package/dist/{agent → core/agent}/config-manager.js +0 -0
- /package/dist/{agent → core/agent}/run.d.ts +0 -0
- /package/dist/{agent → core/agent}/run.js +0 -0
- /package/dist/{agent → core/agent}/skills.d.ts +0 -0
- /package/dist/{agent → core/agent}/skills.js +0 -0
- /package/dist/{config → core/config}/desktop-config.d.ts +0 -0
- /package/dist/{config → core/config}/desktop-config.js +0 -0
- /package/dist/{config → core/config}/provider-support-default.d.ts +0 -0
- /package/dist/{config → core/config}/provider-support-default.js +0 -0
- /package/dist/{installer → core/installer}/index.d.ts +0 -0
- /package/dist/{installer → core/installer}/index.js +0 -0
- /package/dist/{installer → core/installer}/skill-installer.d.ts +0 -0
- /package/dist/{installer → core/installer}/skill-installer.js +0 -0
- /package/dist/{memory → core/memory}/build-summary.d.ts +0 -0
- /package/dist/{memory → core/memory}/build-summary.js +0 -0
- /package/dist/{memory → core/memory}/compaction-extension.d.ts +0 -0
- /package/dist/{memory → core/memory}/compaction-extension.js +0 -0
- /package/dist/{memory → core/memory}/embedding.d.ts +0 -0
- /package/dist/{memory → core/memory}/embedding.js +0 -0
- /package/dist/{memory → core/memory}/index.d.ts +0 -0
- /package/dist/{memory → core/memory}/index.js +0 -0
- /package/dist/{memory → core/memory}/remote-embedding.d.ts +0 -0
- /package/dist/{memory → core/memory}/remote-embedding.js +0 -0
- /package/dist/{memory → core/memory}/types.d.ts +0 -0
- /package/dist/{memory → core/memory}/types.js +0 -0
- /package/dist/{memory → core/memory}/vector-store.d.ts +0 -0
- /package/dist/{memory → core/memory}/vector-store.js +0 -0
- /package/dist/{tools → core/tools}/browser-tool.d.ts +0 -0
- /package/dist/{tools → core/tools}/browser-tool.js +0 -0
- /package/dist/{tools → core/tools}/index.d.ts +0 -0
- /package/dist/{tools → core/tools}/index.js +0 -0
- /package/dist/{tools → core/tools}/install-skill-tool.d.ts +0 -0
- /package/dist/{tools → core/tools}/install-skill-tool.js +0 -0
- /package/dist/{tools → core/tools}/save-experience-tool.d.ts +0 -0
- /package/dist/{tools → core/tools}/save-experience-tool.js +0 -0
package/dist/cli/cli.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { getOpenbotAgentDir } from "../core/agent/agent-dir.js";
|
|
7
|
+
import { run } from "../core/agent/run.js";
|
|
8
|
+
import { loadDesktopAgentConfig, getBoundAgentIdForCli, setProviderApiKey, setDefaultModel, getDesktopConfigList, syncDesktopConfigToModelsJson, ensureDesktopConfigInitialized, } from "../core/config/desktop-config.js";
|
|
9
|
+
import { writeGatewayPid, removeGatewayPidFile, serviceInstall, serviceUninstall, serviceStop, } from "./service.js";
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const PKG = require("../../package.json");
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const PKG_ROOT = join(__dirname, "..", "..");
|
|
14
|
+
async function runAction(positionalPrompt, opts) {
|
|
15
|
+
const skillPaths = opts.skillPath || [];
|
|
16
|
+
const agentId = (opts.agent && String(opts.agent).trim()) || (await getBoundAgentIdForCli());
|
|
17
|
+
const prompt = (opts.prompt ?? positionalPrompt ?? "").trim();
|
|
18
|
+
if (!prompt) {
|
|
19
|
+
console.error("Error: 请提供提示词(位置参数或 --prompt)");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
console.error(`[openbot] Using agent: ${agentId}${!opts.agent ? " (default from config)" : ""}`);
|
|
23
|
+
const needDesktop = opts.apiKey === undefined || opts.provider === undefined || opts.model === undefined;
|
|
24
|
+
const desktopConfig = needDesktop ? await loadDesktopAgentConfig(agentId) : null;
|
|
25
|
+
const provider = opts.provider ?? desktopConfig?.provider ?? "deepseek";
|
|
26
|
+
const model = opts.model ?? desktopConfig?.model ?? "deepseek-chat";
|
|
27
|
+
const apiKey = opts.apiKey ?? process.env.OPENAI_API_KEY ?? desktopConfig?.apiKey ?? "";
|
|
28
|
+
if (desktopConfig && (desktopConfig.provider || desktopConfig.model)) {
|
|
29
|
+
console.error(`[openbot] Using model: ${provider}/${model} (from desktop config)`);
|
|
30
|
+
}
|
|
31
|
+
if (opts.timing)
|
|
32
|
+
process.env.OPENBOT_TIMING = "1";
|
|
33
|
+
const workspaceName = desktopConfig?.workspace ?? agentId;
|
|
34
|
+
try {
|
|
35
|
+
const result = await run({
|
|
36
|
+
workspace: workspaceName,
|
|
37
|
+
skillPaths,
|
|
38
|
+
userPrompt: prompt,
|
|
39
|
+
dryRun: opts.dryRun ?? false,
|
|
40
|
+
model,
|
|
41
|
+
provider,
|
|
42
|
+
agentDir: opts.agentDir ?? getOpenbotAgentDir(),
|
|
43
|
+
apiKey: apiKey || undefined,
|
|
44
|
+
});
|
|
45
|
+
if (result.dryRun) {
|
|
46
|
+
console.log("=== System prompt (skills) ===");
|
|
47
|
+
console.log(result.systemPrompt);
|
|
48
|
+
console.log("\n=== User prompt ===");
|
|
49
|
+
console.log(result.userPrompt);
|
|
50
|
+
console.log("\n(dry-run: 未调用 LLM,设置 OPENAI_API_KEY 可实际调用)");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Content is already streamed by run()
|
|
54
|
+
if (!result.dryRun &&
|
|
55
|
+
(result.assistantContent === undefined || result.assistantContent === "")) {
|
|
56
|
+
console.warn("(模型返回为空;若此前有工具调用提示,说明当前为单轮模式,模型会基于技能描述直接以文本回答。)");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
console.error(err);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const program = new Command();
|
|
65
|
+
program
|
|
66
|
+
.name("openbot")
|
|
67
|
+
.description("CLI to run prompts with skill paths (Agent Skills style)")
|
|
68
|
+
.version(PKG.version, "-v, --version", "显示版本号")
|
|
69
|
+
.option("-s, --skill-path <paths...>", "Additional skill paths to load")
|
|
70
|
+
.option("-a, --agent <id>", "指定智能体 ID,不传则使用桌面配置中的缺省智能体")
|
|
71
|
+
.option("-p, --prompt <text>", "用户提示词(与位置参数二选一)")
|
|
72
|
+
.option("--dry-run", "只输出组装的 system/user 内容,不调用 LLM")
|
|
73
|
+
.option("--model <id>", "模型 ID", "deepseek-chat")
|
|
74
|
+
.option("--provider <name>", "Provider(pi ModelRegistry);可选 deepseek、dashscope、openai", "deepseek")
|
|
75
|
+
.option("--agent-dir <path>", "Agent 配置目录(默认 ~/.openbot/agent)", getOpenbotAgentDir())
|
|
76
|
+
.option("--api-key <key>", "API Key(不传则使用环境变量 OPENAI_API_KEY)")
|
|
77
|
+
.option("--timing", "打印每轮 LLM 与 tool 耗时到 stderr")
|
|
78
|
+
.option("--max-tool-turns <n>", "最大工具调用轮数(默认 30);可设环境变量 OPENBOT_MAX_TOOL_TURNS", (v) => parseInt(v, 10) || 0, 0)
|
|
79
|
+
.argument("[prompt]", "用户提示词(与 --prompt 二选一)")
|
|
80
|
+
.action(async (positionalPrompt) => {
|
|
81
|
+
await runAction(positionalPrompt, program.opts());
|
|
82
|
+
});
|
|
83
|
+
program.addHelpText("after", `
|
|
84
|
+
Environment:
|
|
85
|
+
DEEPSEEK_API_KEY 默认 provider 为 deepseek 时使用;不设时回退 OPENAI_API_KEY
|
|
86
|
+
OPENAI_API_KEY 通用 API Key(可被 --api-key 覆盖)
|
|
87
|
+
DASHSCOPE_API_KEY provider=dashscope 时使用;不设时回退 OPENAI_API_KEY
|
|
88
|
+
OPENAI_BASE_URL 可选,在 pi 未找到模型时使用的 endpoint
|
|
89
|
+
OPENBOT_AGENT_DIR 缺省 agent 目录(默认 ~/.openbot/agent)
|
|
90
|
+
OPENBOT_TIMING=1 等同 --timing
|
|
91
|
+
OPENBOT_ALLOW_RUN_CODE 缺省 1(启用 run_python);设为 0 关闭
|
|
92
|
+
OPENBOT_MAX_TOOL_TURNS 最大工具轮数(默认 30)
|
|
93
|
+
|
|
94
|
+
Examples:
|
|
95
|
+
openbot "总结一下当前有哪些技能"
|
|
96
|
+
openbot -a my-agent "总结一下当前有哪些技能" 使用指定智能体
|
|
97
|
+
openbot -s ./skills "总结一下当前有哪些技能"
|
|
98
|
+
openbot -s ./my-skills --prompt "用 weather 技能查北京天气" --dry-run
|
|
99
|
+
`);
|
|
100
|
+
// Gateway server command
|
|
101
|
+
program
|
|
102
|
+
.command("gateway")
|
|
103
|
+
.description("Start WebSocket gateway server")
|
|
104
|
+
.option("-p, --port <port>", "Port to listen on", "38080")
|
|
105
|
+
.action(async (options) => {
|
|
106
|
+
const port = parseInt(options.port, 10);
|
|
107
|
+
if (isNaN(port) || port <= 0 || port > 65535) {
|
|
108
|
+
console.error("Error: Invalid port number");
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
writeGatewayPid();
|
|
112
|
+
const { startGatewayServer } = await import("../gateway/index.js");
|
|
113
|
+
const { close } = await startGatewayServer(port);
|
|
114
|
+
const shutdown = async () => {
|
|
115
|
+
console.log("\nShutting down...");
|
|
116
|
+
removeGatewayPidFile();
|
|
117
|
+
await close();
|
|
118
|
+
process.exit(0);
|
|
119
|
+
};
|
|
120
|
+
process.on("SIGINT", shutdown);
|
|
121
|
+
process.on("SIGTERM", shutdown);
|
|
122
|
+
});
|
|
123
|
+
// Service: 开机自启 install / uninstall,以及 stop
|
|
124
|
+
const serviceCmd = program
|
|
125
|
+
.command("service")
|
|
126
|
+
.description("Gateway 开机/登录自启与停止(Linux cron、macOS LaunchAgent、Windows 计划任务)");
|
|
127
|
+
serviceCmd
|
|
128
|
+
.command("install")
|
|
129
|
+
.description("添加开机/登录自启:下次重启或登录后自动启动 gateway")
|
|
130
|
+
.action(() => {
|
|
131
|
+
const nodePath = process.execPath;
|
|
132
|
+
const cliPath = join(__dirname, "cli.js");
|
|
133
|
+
serviceInstall({ nodePath, cliPath });
|
|
134
|
+
});
|
|
135
|
+
serviceCmd
|
|
136
|
+
.command("uninstall")
|
|
137
|
+
.description("移除开机/登录自启")
|
|
138
|
+
.action(() => {
|
|
139
|
+
serviceUninstall();
|
|
140
|
+
});
|
|
141
|
+
serviceCmd
|
|
142
|
+
.command("stop")
|
|
143
|
+
.description("停止当前运行的 gateway 进程")
|
|
144
|
+
.action(() => {
|
|
145
|
+
serviceStop();
|
|
146
|
+
});
|
|
147
|
+
// Login command(写入桌面 config;可选 model,不传则取该 provider 第一个模型,补齐后可直接运行)
|
|
148
|
+
program
|
|
149
|
+
.command("login")
|
|
150
|
+
.description("Save API key for a provider to desktop config (~/.openbot/desktop); optional model, default first model")
|
|
151
|
+
.argument("<provider>", "Provider name (e.g., deepseek, dashscope, openai)")
|
|
152
|
+
.argument("<apiKey>", "API Key")
|
|
153
|
+
.argument("[model]", "Model ID (optional; default: first model for provider)")
|
|
154
|
+
.action(async (provider, apiKey, model) => {
|
|
155
|
+
await setProviderApiKey(provider, apiKey, model);
|
|
156
|
+
console.log(`[openbot] API key saved for provider: ${provider}`);
|
|
157
|
+
});
|
|
158
|
+
// Config command
|
|
159
|
+
const configCmd = program.command("config").description("Manage configurations");
|
|
160
|
+
configCmd
|
|
161
|
+
.command("set-model")
|
|
162
|
+
.description("Set default provider and model in desktop config (~/.openbot/desktop)")
|
|
163
|
+
.argument("<provider>", "Provider name")
|
|
164
|
+
.argument("<modelId>", "Model ID")
|
|
165
|
+
.action(async (provider, modelId) => {
|
|
166
|
+
await setDefaultModel(provider, modelId);
|
|
167
|
+
console.log(`[openbot] Default model set: ${provider}/${modelId}`);
|
|
168
|
+
});
|
|
169
|
+
configCmd
|
|
170
|
+
.command("list")
|
|
171
|
+
.description("List desktop config (centralized config source)")
|
|
172
|
+
.action(async () => {
|
|
173
|
+
const list = await getDesktopConfigList();
|
|
174
|
+
if (list.providers.length === 0) {
|
|
175
|
+
console.log("No providers in desktop config. Run: openbot login <provider> <apiKey>");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
console.log(`Default: ${list.defaultProvider} / ${list.defaultModel}\n`);
|
|
179
|
+
console.table(list.providers.map((r) => ({
|
|
180
|
+
Provider: r.provider,
|
|
181
|
+
"Default Model": r.defaultModel,
|
|
182
|
+
"API Key": r.hasKey ? "✅" : "❌",
|
|
183
|
+
})));
|
|
184
|
+
});
|
|
185
|
+
configCmd
|
|
186
|
+
.command("sync")
|
|
187
|
+
.description("Sync desktop config to ~/.openbot/agent/models.json for pi-agent")
|
|
188
|
+
.action(async () => {
|
|
189
|
+
await syncDesktopConfigToModelsJson();
|
|
190
|
+
console.log("[openbot] Synced desktop providers to agent models.json");
|
|
191
|
+
});
|
|
192
|
+
(async () => {
|
|
193
|
+
await ensureDesktopConfigInitialized();
|
|
194
|
+
await program.parseAsync(process.argv);
|
|
195
|
+
})().catch((err) => {
|
|
196
|
+
console.error(err);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** 供 gateway 命令调用:写入当前进程 PID */
|
|
2
|
+
export declare function writeGatewayPid(): void;
|
|
3
|
+
/** 供 gateway 命令调用:关闭时删除 PID 文件 */
|
|
4
|
+
export declare function removeGatewayPidFile(): void;
|
|
5
|
+
/** 获取 PID 文件路径(供外部读取) */
|
|
6
|
+
export declare function getGatewayPidPath(): string;
|
|
7
|
+
export type ServiceOptions = {
|
|
8
|
+
nodePath: string;
|
|
9
|
+
cliPath: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function serviceInstall(opts: ServiceOptions): void;
|
|
12
|
+
export declare function serviceUninstall(): void;
|
|
13
|
+
export declare function serviceStop(): boolean;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* openbot service install / uninstall / stop
|
|
3
|
+
* - install: 开机/登录自启(Linux cron @reboot, macOS LaunchAgent, Windows 计划任务)
|
|
4
|
+
* - uninstall: 移除自启
|
|
5
|
+
* - stop: 停止当前运行的 gateway 进程(依赖 gateway 写入的 PID 文件)
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { homedir, platform } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
11
|
+
const OPENBOT_USER_DIR = process.env.OPENBOT_USER_DIR ?? join(homedir(), ".openbot");
|
|
12
|
+
const GATEWAY_PID_FILE = join(OPENBOT_USER_DIR, "gateway.pid");
|
|
13
|
+
const GATEWAY_PORT = 38080;
|
|
14
|
+
/** 供 gateway 命令调用:写入当前进程 PID */
|
|
15
|
+
export function writeGatewayPid() {
|
|
16
|
+
if (!existsSync(OPENBOT_USER_DIR))
|
|
17
|
+
mkdirSync(OPENBOT_USER_DIR, { recursive: true });
|
|
18
|
+
writeFileSync(GATEWAY_PID_FILE, String(process.pid), "utf-8");
|
|
19
|
+
}
|
|
20
|
+
/** 供 gateway 命令调用:关闭时删除 PID 文件 */
|
|
21
|
+
export function removeGatewayPidFile() {
|
|
22
|
+
try {
|
|
23
|
+
if (existsSync(GATEWAY_PID_FILE))
|
|
24
|
+
rmSync(GATEWAY_PID_FILE);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// ignore
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** 获取 PID 文件路径(供外部读取) */
|
|
31
|
+
export function getGatewayPidPath() {
|
|
32
|
+
return GATEWAY_PID_FILE;
|
|
33
|
+
}
|
|
34
|
+
function getBootCommand(nodePath, cliPath) {
|
|
35
|
+
const port = process.env.OPENBOT_GATEWAY_PORT ?? String(GATEWAY_PORT);
|
|
36
|
+
return `"${nodePath}" "${cliPath}" gateway -p ${port}`;
|
|
37
|
+
}
|
|
38
|
+
function detectPlatform() {
|
|
39
|
+
const p = platform();
|
|
40
|
+
if (p === "win32")
|
|
41
|
+
return "win32";
|
|
42
|
+
if (p === "darwin")
|
|
43
|
+
return "darwin";
|
|
44
|
+
return "linux";
|
|
45
|
+
}
|
|
46
|
+
/** Linux: cron @reboot */
|
|
47
|
+
function installLinux(nodePath, cliPath) {
|
|
48
|
+
const cronLine = `@reboot ${getBootCommand(nodePath, cliPath)}\n`;
|
|
49
|
+
const marker = "# openbot-gateway";
|
|
50
|
+
let current = "";
|
|
51
|
+
try {
|
|
52
|
+
current = execSync("crontab -l 2>/dev/null || true", { encoding: "utf-8" });
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// no crontab
|
|
56
|
+
}
|
|
57
|
+
if (current.includes(marker)) {
|
|
58
|
+
throw new Error("已存在 openbot gateway 的开机自启,请先执行 openbot service uninstall");
|
|
59
|
+
}
|
|
60
|
+
const newCrontab = current.trimEnd() + (current ? "\n" : "") + marker + "\n" + cronLine;
|
|
61
|
+
execSync("crontab -", { input: newCrontab, stdio: ["pipe", "inherit", "inherit"] });
|
|
62
|
+
console.log("[openbot] 已添加 cron @reboot,下次重启将自动启动 gateway");
|
|
63
|
+
}
|
|
64
|
+
function uninstallLinux() {
|
|
65
|
+
let current = "";
|
|
66
|
+
try {
|
|
67
|
+
current = execSync("crontab -l 2>/dev/null || true", { encoding: "utf-8" });
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
console.log("[openbot] 未找到 crontab 或已为空");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const marker = "# openbot-gateway";
|
|
74
|
+
const lines = current.split("\n");
|
|
75
|
+
const out = [];
|
|
76
|
+
let skipNext = false;
|
|
77
|
+
for (const line of lines) {
|
|
78
|
+
if (line.includes(marker)) {
|
|
79
|
+
skipNext = true;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (skipNext && line.trim().startsWith("@reboot") && line.includes("gateway")) {
|
|
83
|
+
skipNext = false;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
skipNext = false;
|
|
87
|
+
out.push(line);
|
|
88
|
+
}
|
|
89
|
+
const newCrontab = out.join("\n").replace(/\n\n+/g, "\n").trim();
|
|
90
|
+
if (newCrontab)
|
|
91
|
+
execSync("crontab -", { input: newCrontab, stdio: ["pipe", "inherit", "inherit"] });
|
|
92
|
+
else
|
|
93
|
+
execSync("crontab -r 2>/dev/null || true", { stdio: "inherit" });
|
|
94
|
+
console.log("[openbot] 已移除 cron @reboot");
|
|
95
|
+
}
|
|
96
|
+
function escapePlistString(s) {
|
|
97
|
+
return s
|
|
98
|
+
.replace(/&/g, "&")
|
|
99
|
+
.replace(/</g, "<")
|
|
100
|
+
.replace(/>/g, ">")
|
|
101
|
+
.replace(/"/g, """);
|
|
102
|
+
}
|
|
103
|
+
/** macOS: LaunchAgent (用户级,登录时运行) */
|
|
104
|
+
function installDarwin(nodePath, cliPath) {
|
|
105
|
+
const launchAgentsDir = join(homedir(), "Library", "LaunchAgents");
|
|
106
|
+
if (!existsSync(launchAgentsDir))
|
|
107
|
+
mkdirSync(launchAgentsDir, { recursive: true });
|
|
108
|
+
const plistPath = join(launchAgentsDir, "com.openbot.gateway.plist");
|
|
109
|
+
if (existsSync(plistPath)) {
|
|
110
|
+
throw new Error("已存在 openbot gateway 自启,请先执行 openbot service uninstall");
|
|
111
|
+
}
|
|
112
|
+
const port = process.env.OPENBOT_GATEWAY_PORT ?? String(GATEWAY_PORT);
|
|
113
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
114
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
115
|
+
<plist version="1.0">
|
|
116
|
+
<dict>
|
|
117
|
+
<key>Label</key>
|
|
118
|
+
<string>com.openbot.gateway</string>
|
|
119
|
+
<key>ProgramArguments</key>
|
|
120
|
+
<array>
|
|
121
|
+
<string>${escapePlistString(nodePath)}</string>
|
|
122
|
+
<string>${escapePlistString(cliPath)}</string>
|
|
123
|
+
<string>gateway</string>
|
|
124
|
+
<string>-p</string>
|
|
125
|
+
<string>${port}</string>
|
|
126
|
+
</array>
|
|
127
|
+
<key>RunAtLoad</key>
|
|
128
|
+
<true/>
|
|
129
|
+
<key>KeepAlive</key>
|
|
130
|
+
<false/>
|
|
131
|
+
<key>StandardOutPath</key>
|
|
132
|
+
<string>${escapePlistString(OPENBOT_USER_DIR)}/gateway.log</string>
|
|
133
|
+
<key>StandardErrorPath</key>
|
|
134
|
+
<string>${escapePlistString(OPENBOT_USER_DIR)}/gateway.err</string>
|
|
135
|
+
</dict>
|
|
136
|
+
</plist>
|
|
137
|
+
`;
|
|
138
|
+
writeFileSync(plistPath, plist, "utf-8");
|
|
139
|
+
execSync(`launchctl load "${plistPath}"`, { stdio: "inherit" });
|
|
140
|
+
console.log("[openbot] 已安装 LaunchAgent,下次登录将自动启动 gateway");
|
|
141
|
+
}
|
|
142
|
+
function uninstallDarwin() {
|
|
143
|
+
const plistPath = join(homedir(), "Library", "LaunchAgents", "com.openbot.gateway.plist");
|
|
144
|
+
if (existsSync(plistPath)) {
|
|
145
|
+
try {
|
|
146
|
+
execSync(`launchctl unload "${plistPath}"`, { stdio: "inherit" });
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// may already be unloaded
|
|
150
|
+
}
|
|
151
|
+
rmSync(plistPath);
|
|
152
|
+
console.log("[openbot] 已移除 LaunchAgent");
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
console.log("[openbot] 未找到自启配置");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** Windows: 任务计划(用户登录时运行) */
|
|
159
|
+
function installWin32(nodePath, cliPath) {
|
|
160
|
+
const taskName = "OpenBotGateway";
|
|
161
|
+
const r = spawnSync("schtasks", ["/query", "/tn", taskName], { encoding: "utf-8" });
|
|
162
|
+
if (r.status === 0) {
|
|
163
|
+
throw new Error("已存在 OpenBot Gateway 自启任务,请先执行 openbot service uninstall");
|
|
164
|
+
}
|
|
165
|
+
const port = process.env.OPENBOT_GATEWAY_PORT ?? String(GATEWAY_PORT);
|
|
166
|
+
const cmd = `"${nodePath}" "${cliPath}" gateway -p ${port}`;
|
|
167
|
+
const r2 = spawnSync("schtasks", [
|
|
168
|
+
"/create",
|
|
169
|
+
"/tn", taskName,
|
|
170
|
+
"/tr", cmd,
|
|
171
|
+
"/sc", "onlogon",
|
|
172
|
+
"/rl", "highest",
|
|
173
|
+
"/f",
|
|
174
|
+
], { encoding: "utf-8", stdio: "inherit", shell: true });
|
|
175
|
+
if (r2.status !== 0) {
|
|
176
|
+
throw new Error("创建计划任务失败,请以管理员身份运行或检查 schtasks");
|
|
177
|
+
}
|
|
178
|
+
console.log("[openbot] 已创建计划任务,下次登录将自动启动 gateway");
|
|
179
|
+
}
|
|
180
|
+
function uninstallWin32() {
|
|
181
|
+
const taskName = "OpenBotGateway";
|
|
182
|
+
const r = spawnSync("schtasks", ["/query", "/tn", taskName], { encoding: "utf-8" });
|
|
183
|
+
if (r.status !== 0) {
|
|
184
|
+
console.log("[openbot] 未找到自启任务");
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
spawnSync("schtasks", ["/delete", "/tn", taskName, "/f"], { stdio: "inherit", shell: true });
|
|
188
|
+
console.log("[openbot] 已删除计划任务");
|
|
189
|
+
}
|
|
190
|
+
/** 停止 gateway:读 PID 文件发 SIGTERM */
|
|
191
|
+
function stopGateway() {
|
|
192
|
+
if (!existsSync(GATEWAY_PID_FILE)) {
|
|
193
|
+
console.log("[openbot] 未找到 gateway PID 文件,可能未以后台/服务方式运行");
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
const pidStr = readFileSync(GATEWAY_PID_FILE, "utf-8").trim();
|
|
197
|
+
const pid = parseInt(pidStr, 10);
|
|
198
|
+
if (Number.isNaN(pid)) {
|
|
199
|
+
console.log("[openbot] PID 文件内容无效");
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
if (platform() === "win32") {
|
|
204
|
+
spawnSync("taskkill", ["/pid", String(pid), "/f"], { stdio: "inherit", shell: true });
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
process.kill(pid, "SIGTERM");
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
212
|
+
if (msg.includes("kill") || msg.includes("ESRCH")) {
|
|
213
|
+
console.log("[openbot] 进程已不存在");
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
throw e;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
removeGatewayPidFile();
|
|
220
|
+
console.log("[openbot] 已停止 gateway");
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
export function serviceInstall(opts) {
|
|
224
|
+
const p = detectPlatform();
|
|
225
|
+
if (p === "linux")
|
|
226
|
+
installLinux(opts.nodePath, opts.cliPath);
|
|
227
|
+
else if (p === "darwin")
|
|
228
|
+
installDarwin(opts.nodePath, opts.cliPath);
|
|
229
|
+
else
|
|
230
|
+
installWin32(opts.nodePath, opts.cliPath);
|
|
231
|
+
}
|
|
232
|
+
export function serviceUninstall() {
|
|
233
|
+
const p = detectPlatform();
|
|
234
|
+
if (p === "linux")
|
|
235
|
+
uninstallLinux();
|
|
236
|
+
else if (p === "darwin")
|
|
237
|
+
uninstallDarwin();
|
|
238
|
+
else
|
|
239
|
+
uninstallWin32();
|
|
240
|
+
}
|
|
241
|
+
export function serviceStop() {
|
|
242
|
+
return stopGateway();
|
|
243
|
+
}
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,173 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
import { getOpenbotAgentDir } from "./agent/agent-dir.js";
|
|
7
|
-
import { run } from "./agent/run.js";
|
|
8
|
-
import { loadDesktopAgentConfig, getBoundAgentIdForCli, setProviderApiKey, setDefaultModel, getDesktopConfigList, syncDesktopConfigToModelsJson, ensureDesktopConfigInitialized, } from "./config/desktop-config.js";
|
|
9
|
-
const require = createRequire(import.meta.url);
|
|
10
|
-
const PKG = require("../package.json");
|
|
11
|
-
// Based on distillate, the package root is one level up from dist/
|
|
12
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
const PKG_ROOT = join(__dirname, "..");
|
|
14
|
-
async function runAction(positionalPrompt, opts) {
|
|
15
|
-
const skillPaths = opts.skillPath || [];
|
|
16
|
-
const agentId = (opts.agent && String(opts.agent).trim()) || (await getBoundAgentIdForCli());
|
|
17
|
-
const prompt = (opts.prompt ?? positionalPrompt ?? "").trim();
|
|
18
|
-
if (!prompt) {
|
|
19
|
-
console.error("Error: 请提供提示词(位置参数或 --prompt)");
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
console.error(`[openbot] Using agent: ${agentId}${!opts.agent ? " (default from config)" : ""}`);
|
|
23
|
-
const needDesktop = opts.apiKey === undefined || opts.provider === undefined || opts.model === undefined;
|
|
24
|
-
const desktopConfig = needDesktop ? await loadDesktopAgentConfig(agentId) : null;
|
|
25
|
-
const provider = opts.provider ?? desktopConfig?.provider ?? "deepseek";
|
|
26
|
-
const model = opts.model ?? desktopConfig?.model ?? "deepseek-chat";
|
|
27
|
-
const apiKey = opts.apiKey ?? process.env.OPENAI_API_KEY ?? desktopConfig?.apiKey ?? "";
|
|
28
|
-
if (desktopConfig && (desktopConfig.provider || desktopConfig.model)) {
|
|
29
|
-
console.error(`[openbot] Using model: ${provider}/${model} (from desktop config)`);
|
|
30
|
-
}
|
|
31
|
-
if (opts.timing)
|
|
32
|
-
process.env.OPENBOT_TIMING = "1";
|
|
33
|
-
const workspaceName = desktopConfig?.workspace ?? agentId;
|
|
34
|
-
try {
|
|
35
|
-
const result = await run({
|
|
36
|
-
workspace: workspaceName,
|
|
37
|
-
skillPaths,
|
|
38
|
-
userPrompt: prompt,
|
|
39
|
-
dryRun: opts.dryRun ?? false,
|
|
40
|
-
model,
|
|
41
|
-
provider,
|
|
42
|
-
agentDir: opts.agentDir ?? getOpenbotAgentDir(),
|
|
43
|
-
apiKey: apiKey || undefined,
|
|
44
|
-
});
|
|
45
|
-
if (result.dryRun) {
|
|
46
|
-
console.log("=== System prompt (skills) ===");
|
|
47
|
-
console.log(result.systemPrompt);
|
|
48
|
-
console.log("\n=== User prompt ===");
|
|
49
|
-
console.log(result.userPrompt);
|
|
50
|
-
console.log("\n(dry-run: 未调用 LLM,设置 OPENAI_API_KEY 可实际调用)");
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
// Content is already streamed by run()
|
|
54
|
-
if (!result.dryRun &&
|
|
55
|
-
(result.assistantContent === undefined || result.assistantContent === "")) {
|
|
56
|
-
console.warn("(模型返回为空;若此前有工具调用提示,说明当前为单轮模式,模型会基于技能描述直接以文本回答。)");
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
catch (err) {
|
|
60
|
-
console.error(err);
|
|
61
|
-
process.exit(1);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
const program = new Command();
|
|
65
|
-
program
|
|
66
|
-
.name("openbot")
|
|
67
|
-
.description("CLI to run prompts with skill paths (Agent Skills style)")
|
|
68
|
-
.version(PKG.version, "-v, --version", "显示版本号")
|
|
69
|
-
.option("-s, --skill-path <paths...>", "Additional skill paths to load")
|
|
70
|
-
.option("-a, --agent <id>", "指定智能体 ID,不传则使用桌面配置中的缺省智能体")
|
|
71
|
-
.option("-p, --prompt <text>", "用户提示词(与位置参数二选一)")
|
|
72
|
-
.option("--dry-run", "只输出组装的 system/user 内容,不调用 LLM")
|
|
73
|
-
.option("--model <id>", "模型 ID", "deepseek-chat")
|
|
74
|
-
.option("--provider <name>", "Provider(pi ModelRegistry);可选 deepseek、dashscope、openai", "deepseek")
|
|
75
|
-
.option("--agent-dir <path>", "Agent 配置目录(默认 ~/.openbot/agent)", getOpenbotAgentDir())
|
|
76
|
-
.option("--api-key <key>", "API Key(不传则使用环境变量 OPENAI_API_KEY)")
|
|
77
|
-
.option("--timing", "打印每轮 LLM 与 tool 耗时到 stderr")
|
|
78
|
-
.option("--max-tool-turns <n>", "最大工具调用轮数(默认 30);可设环境变量 OPENBOT_MAX_TOOL_TURNS", (v) => parseInt(v, 10) || 0, 0)
|
|
79
|
-
.argument("[prompt]", "用户提示词(与 --prompt 二选一)")
|
|
80
|
-
.action(async (positionalPrompt) => {
|
|
81
|
-
await runAction(positionalPrompt, program.opts());
|
|
82
|
-
});
|
|
83
|
-
program.addHelpText("after", `
|
|
84
|
-
Environment:
|
|
85
|
-
DEEPSEEK_API_KEY 默认 provider 为 deepseek 时使用;不设时回退 OPENAI_API_KEY
|
|
86
|
-
OPENAI_API_KEY 通用 API Key(可被 --api-key 覆盖)
|
|
87
|
-
DASHSCOPE_API_KEY provider=dashscope 时使用;不设时回退 OPENAI_API_KEY
|
|
88
|
-
OPENAI_BASE_URL 可选,在 pi 未找到模型时使用的 endpoint
|
|
89
|
-
OPENBOT_AGENT_DIR 缺省 agent 目录(默认 ~/.openbot/agent)
|
|
90
|
-
OPENBOT_TIMING=1 等同 --timing
|
|
91
|
-
OPENBOT_ALLOW_RUN_CODE 缺省 1(启用 run_python);设为 0 关闭
|
|
92
|
-
OPENBOT_MAX_TOOL_TURNS 最大工具轮数(默认 30)
|
|
93
|
-
|
|
94
|
-
Examples:
|
|
95
|
-
openbot "总结一下当前有哪些技能"
|
|
96
|
-
openbot -a my-agent "总结一下当前有哪些技能" 使用指定智能体
|
|
97
|
-
openbot -s ./skills "总结一下当前有哪些技能"
|
|
98
|
-
openbot -s ./my-skills --prompt "用 weather 技能查北京天气" --dry-run
|
|
99
|
-
`);
|
|
100
|
-
// Gateway server command
|
|
101
|
-
program
|
|
102
|
-
.command("gateway")
|
|
103
|
-
.description("Start WebSocket gateway server")
|
|
104
|
-
.option("-p, --port <port>", "Port to listen on", "38080")
|
|
105
|
-
.action(async (options) => {
|
|
106
|
-
const port = parseInt(options.port, 10);
|
|
107
|
-
if (isNaN(port) || port <= 0 || port > 65535) {
|
|
108
|
-
console.error("Error: Invalid port number");
|
|
109
|
-
process.exit(1);
|
|
110
|
-
}
|
|
111
|
-
const { startGatewayServer } = await import("./gateway/index.js");
|
|
112
|
-
const { close } = await startGatewayServer(port);
|
|
113
|
-
// Handle graceful shutdown
|
|
114
|
-
const shutdown = async () => {
|
|
115
|
-
console.log("\nShutting down...");
|
|
116
|
-
await close();
|
|
117
|
-
process.exit(0);
|
|
118
|
-
};
|
|
119
|
-
process.on("SIGINT", shutdown);
|
|
120
|
-
process.on("SIGTERM", shutdown);
|
|
121
|
-
});
|
|
122
|
-
// Login command(写入桌面 config;可选 model,不传则取该 provider 第一个模型,补齐后可直接运行)
|
|
123
|
-
program
|
|
124
|
-
.command("login")
|
|
125
|
-
.description("Save API key for a provider to desktop config (~/.openbot/desktop); optional model, default first model")
|
|
126
|
-
.argument("<provider>", "Provider name (e.g., deepseek, dashscope, openai)")
|
|
127
|
-
.argument("<apiKey>", "API Key")
|
|
128
|
-
.argument("[model]", "Model ID (optional; default: first model for provider)")
|
|
129
|
-
.action(async (provider, apiKey, model) => {
|
|
130
|
-
await setProviderApiKey(provider, apiKey, model);
|
|
131
|
-
console.log(`[openbot] API key saved for provider: ${provider}`);
|
|
132
|
-
});
|
|
133
|
-
// Config command
|
|
134
|
-
const configCmd = program.command("config").description("Manage configurations");
|
|
135
|
-
configCmd
|
|
136
|
-
.command("set-model")
|
|
137
|
-
.description("Set default provider and model in desktop config (~/.openbot/desktop)")
|
|
138
|
-
.argument("<provider>", "Provider name")
|
|
139
|
-
.argument("<modelId>", "Model ID")
|
|
140
|
-
.action(async (provider, modelId) => {
|
|
141
|
-
await setDefaultModel(provider, modelId);
|
|
142
|
-
console.log(`[openbot] Default model set: ${provider}/${modelId}`);
|
|
143
|
-
});
|
|
144
|
-
configCmd
|
|
145
|
-
.command("list")
|
|
146
|
-
.description("List desktop config (centralized config source)")
|
|
147
|
-
.action(async () => {
|
|
148
|
-
const list = await getDesktopConfigList();
|
|
149
|
-
if (list.providers.length === 0) {
|
|
150
|
-
console.log("No providers in desktop config. Run: openbot login <provider> <apiKey>");
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
console.log(`Default: ${list.defaultProvider} / ${list.defaultModel}\n`);
|
|
154
|
-
console.table(list.providers.map((r) => ({
|
|
155
|
-
Provider: r.provider,
|
|
156
|
-
"Default Model": r.defaultModel,
|
|
157
|
-
"API Key": r.hasKey ? "✅" : "❌",
|
|
158
|
-
})));
|
|
159
|
-
});
|
|
160
|
-
configCmd
|
|
161
|
-
.command("sync")
|
|
162
|
-
.description("Sync desktop config to ~/.openbot/agent/models.json for pi-agent")
|
|
163
|
-
.action(async () => {
|
|
164
|
-
await syncDesktopConfigToModelsJson();
|
|
165
|
-
console.log("[openbot] Synced desktop providers to agent models.json");
|
|
166
|
-
});
|
|
167
|
-
(async () => {
|
|
168
|
-
await ensureDesktopConfigInitialized();
|
|
169
|
-
await program.parseAsync(process.argv);
|
|
170
|
-
})().catch((err) => {
|
|
171
|
-
console.error(err);
|
|
172
|
-
process.exit(1);
|
|
173
|
-
});
|
|
2
|
+
/**
|
|
3
|
+
* 兼容旧命令 node dist/cli.js / openbot → 实际入口为 dist/cli/cli.js
|
|
4
|
+
*/
|
|
5
|
+
import "./cli/cli.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway 各通道入口的鉴权/安全钩子(占位模块)。
|
|
3
|
+
* 连接之初可在此做 token 校验、限流等,当前暂不加逻辑,仅透传。
|
|
4
|
+
*/
|
|
5
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
6
|
+
import type { IncomingMessage } from 'http';
|
|
7
|
+
/** HTTP:/server-api 进入 Nest 前的鉴权钩子(占位,直接 next) */
|
|
8
|
+
export declare function authHookServerApi(req: Request, res: Response, next: NextFunction): void;
|
|
9
|
+
/** HTTP:/channel 进入通道模块前的鉴权钩子(占位,直接 next) */
|
|
10
|
+
export declare function authHookChannel(req: Request, res: Response, next: NextFunction): void;
|
|
11
|
+
/** HTTP:/sse 进入 SSE 前的鉴权钩子(占位,直接 next) */
|
|
12
|
+
export declare function authHookSse(req: Request, res: Response, next: NextFunction): void;
|
|
13
|
+
/**
|
|
14
|
+
* WebSocket upgrade:/ws 或 /ws/voice 连接前的鉴权钩子(占位)。
|
|
15
|
+
* 返回 true 表示放行,false 表示拒绝(调用方应关闭 socket)。
|
|
16
|
+
*/
|
|
17
|
+
export declare function authHookWs(_req: IncomingMessage, _path: string): boolean;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** HTTP:/server-api 进入 Nest 前的鉴权钩子(占位,直接 next) */
|
|
2
|
+
export function authHookServerApi(req, res, next) {
|
|
3
|
+
next();
|
|
4
|
+
}
|
|
5
|
+
/** HTTP:/channel 进入通道模块前的鉴权钩子(占位,直接 next) */
|
|
6
|
+
export function authHookChannel(req, res, next) {
|
|
7
|
+
next();
|
|
8
|
+
}
|
|
9
|
+
/** HTTP:/sse 进入 SSE 前的鉴权钩子(占位,直接 next) */
|
|
10
|
+
export function authHookSse(req, res, next) {
|
|
11
|
+
next();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* WebSocket upgrade:/ws 或 /ws/voice 连接前的鉴权钩子(占位)。
|
|
15
|
+
* 返回 true 表示放行,false 表示拒绝(调用方应关闭 socket)。
|
|
16
|
+
*/
|
|
17
|
+
export function authHookWs(_req, _path) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|