@social-data/social-neuron-subscription 1.0.0-beta.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/bin/cli.js ADDED
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // ⚠️ 本文件由 backend/app/cli_spec/gen.py 生成,请勿手改;要改改 gen.py 的 _CLI_JS 后重跑 gen。
5
+ //
6
+ // 能力 CLI —— 通用薄运行时(纯 JS,零依赖):一能力一命令、一包、一配置文件,互不影响。
7
+ // <cmd> <action> [--flags] 调用一条命令
8
+ // <cmd> [-h] / <cmd> <action> -h 帮助
9
+ // <cmd> bind --api-url <url> --token <token> 写 ~/.social-neuron/<key>_config.json
10
+ // <cmd> bind --logout 删除该配置文件
11
+ // <cmd> skills install [dir] 覆盖写入 SKILL.md
12
+ // 能力 key / 命令名全部来自同包 manifest.json(见 CAPABILITY / COMMAND);换能力只改 manifest。
13
+
14
+ const fs = require("fs");
15
+ const os = require("os");
16
+ const path = require("path");
17
+
18
+ const MANIFEST = require(path.join(__dirname, "..", "manifest.json"));
19
+ const VERSION = require(path.join(__dirname, "..", "package.json")).version;
20
+ const CAPABILITY = MANIFEST.capability; // ← 能力 key(来自 manifest)
21
+ const COMMAND = MANIFEST.command || ("social-neuron-" + CAPABILITY); // ← 命令名(来自 manifest)
22
+
23
+ const EXIT = { auth_error: 3, validation: 2, config: 2, network: 4, outdated: 2 };
24
+
25
+ // 配置:一能力一文件 ~/.social-neuron/<key>_config.json,扁平 {api_url, token}(不区分环境)。
26
+ function configPath() {
27
+ const dir = process.env.NEURON_CONFIG_DIR || path.join(os.homedir(), ".social-neuron");
28
+ return path.join(dir, CAPABILITY + "_config.json");
29
+ }
30
+ function resolveConfig() {
31
+ let api = process.env.NEURON_API_BASE_URL || "";
32
+ let token = process.env.NEURON_TOKEN || "";
33
+ try {
34
+ const cfg = JSON.parse(fs.readFileSync(configPath(), "utf8"));
35
+ api = api || cfg.api_url || "";
36
+ token = token || cfg.token || "";
37
+ } catch (_e) { /* 无配置文件:仅用 env */ }
38
+ return { api: api.replace(/\/+$/, ""), token };
39
+ }
40
+
41
+ function emit(env) {
42
+ process.stdout.write(JSON.stringify(env) + "\n");
43
+ process.exit(env.ok ? 0 : (EXIT[env.error && env.error.type] ?? 1));
44
+ }
45
+ function fail(type, code, message, extra) {
46
+ emit({ ok: false, error: { type, code, message, ...(extra || {}) } });
47
+ }
48
+
49
+ // 网关统一 {code,msg,data}(code=0 成功)→ 归一化 {ok,data|error}。
50
+ function forgeType(code) {
51
+ const s = String(code);
52
+ if (s.startsWith("401") || s.startsWith("403")) return "auth_error";
53
+ if (s.startsWith("426")) return "outdated";
54
+ if (s.startsWith("422") || s.startsWith("400")) return "validation";
55
+ return "api_error";
56
+ }
57
+ function normalize(body) {
58
+ if (body && typeof body.ok === "boolean") return body;
59
+ if (body && Object.prototype.hasOwnProperty.call(body, "code")) {
60
+ if (body.code === 0) return { ok: true, data: body.data };
61
+ const type = forgeType(body.code);
62
+ let message = body.msg || "";
63
+ if (type === "outdated") message += "(升级后请重新加载 SKILL.md 与 manifest 再调用)";
64
+ return { ok: false, error: { type, code: body.code, message } };
65
+ }
66
+ return { ok: false, error: { type: "api_error", code: "UNKNOWN", message: JSON.stringify(body).slice(0, 200) } };
67
+ }
68
+
69
+ function findCmd(action) { return MANIFEST.tools.find((t) => t.action === action); }
70
+ function _flag(a, name) { const i = a.indexOf(name); return i >= 0 ? a[i + 1] : undefined; }
71
+
72
+ // bind:只写本能力自己的配置文件(原子写 + 0600);--logout 删该文件。互不影响其它能力。
73
+ function doBind(a) {
74
+ const cfgPath = configPath();
75
+ if (a.includes("--logout")) {
76
+ try { fs.unlinkSync(cfgPath); process.stderr.write("[" + COMMAND + "] 已清除配置 " + cfgPath + "\n"); }
77
+ catch (_e) { process.stderr.write("[" + COMMAND + "] 无配置可清除\n"); }
78
+ return;
79
+ }
80
+ const api = _flag(a, "--api-url");
81
+ const token = _flag(a, "--token");
82
+ if (!api || !token) {
83
+ return fail("validation", "BIND_ARGS", "bind 需要 --api-url 与 --token", {
84
+ usage: { example: COMMAND + " bind --api-url http://host/api/v2 --token <token>" },
85
+ });
86
+ }
87
+ const cfg = { api_url: api.replace(/\/+$/, ""), token };
88
+ fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
89
+ const tmp = cfgPath + ".tmp";
90
+ fs.writeFileSync(tmp, JSON.stringify(cfg, null, 2), { mode: 0o600 });
91
+ fs.renameSync(tmp, cfgPath); // 原子替换
92
+ process.stderr.write("[" + COMMAND + "] 已绑定 → " + cfg.api_url + " (" + cfgPath + ")\n");
93
+ printWelcome();
94
+ }
95
+
96
+ function skillsDir() { return process.env.OPENCLAW_SKILLS_DIR || path.join(os.homedir(), ".openclaw", "skills"); }
97
+ function printWelcome() {
98
+ const w = (s) => process.stderr.write(s + "\n");
99
+ const skill = MANIFEST.skill;
100
+ const dir = skillsDir();
101
+ w("");
102
+ w("──────────────────────────────────────────────");
103
+ w(" " + COMMAND + " v" + VERSION + " · 已就绪(能力:" + CAPABILITY + ")");
104
+ w(" 命令:");
105
+ for (const t of MANIFEST.tools) w(" " + COMMAND + " " + t.action + " — " + t.summary);
106
+ if (skill) { w(""); w(" Agent 需加载 SKILL.md:"); w(" " + path.join(dir, skill.slug, "SKILL.md")); }
107
+ w("");
108
+ w(" 下一步:" + COMMAND + " <action> -h 查看入参;" + COMMAND + " skills install 重写技能文件");
109
+ w("──────────────────────────────────────────────");
110
+ }
111
+
112
+ function usageOf(cmd) {
113
+ const line = (f) => " " + f.flag + " " + f.type + (f.required ? " required" : " optional") + " " + (f.desc || "");
114
+ const req = cmd.flags.filter((f) => f.required), opt = cmd.flags.filter((f) => !f.required);
115
+ return {
116
+ command: COMMAND + " " + cmd.action,
117
+ required: req.map((f) => ({ flag: f.flag, type: f.type, desc: f.desc })),
118
+ optional: opt.map((f) => ({ flag: f.flag, type: f.type, desc: f.desc })),
119
+ example: (COMMAND + " " + cmd.action + " " + req.map((f) => f.flag + " <v>").join(" ")).trim(),
120
+ _text: [COMMAND + " " + cmd.action + " — " + cmd.summary, "Inputs:", ...cmd.flags.map(line)].join("\n"),
121
+ };
122
+ }
123
+ function printHelp(action) {
124
+ const w = (s) => process.stderr.write(s + "\n");
125
+ if (action) {
126
+ const c = findCmd(action);
127
+ if (!c) return fail("validation", "UNKNOWN_COMMAND", "未知命令 " + action);
128
+ const u = usageOf(c); w(u._text); w(""); w("示例:" + u.example); return process.exit(0);
129
+ }
130
+ w(COMMAND + " v" + VERSION + " — Social Neuron 能力 CLI(能力:" + CAPABILITY + ";供 AI agent 调用)");
131
+ w("");
132
+ w("用法:" + COMMAND + " <action> [--flags]");
133
+ w(" 标量 --flag v;数组 --flag a,b;对象 --flag '<json>';写/删/触发加 -y");
134
+ w("");
135
+ w("命令:");
136
+ for (const c of MANIFEST.tools) w(" " + COMMAND + " " + c.action + " — " + c.summary);
137
+ w("");
138
+ w("其它:");
139
+ w(" " + COMMAND + " <action> -h 查看某命令完整入参/出参");
140
+ w(" " + COMMAND + " bind --api-url <url> --token <t> 绑定网关+令牌(写 " + configPath() + ")");
141
+ w(" " + COMMAND + " bind --logout 删除本能力配置");
142
+ w(" " + COMMAND + " skills install [dir] 覆盖写入 SKILL.md 到 ~/.openclaw/skills");
143
+ w(" " + COMMAND + " --version");
144
+ w("");
145
+ w("鉴权/网关:env NEURON_API_BASE_URL / NEURON_TOKEN 覆盖配置;令牌走头 X-Social-Neuron-CLI-Key。");
146
+ process.exit(0);
147
+ }
148
+
149
+ function parseArgs(cmd, argv) {
150
+ const byFlag = Object.fromEntries(cmd.flags.map((f) => [f.flag, f]));
151
+ const args = {};
152
+ for (let i = 0; i < argv.length; i++) {
153
+ const a = argv[i];
154
+ if (a === "-y") continue;
155
+ if (!a.startsWith("--")) continue;
156
+ const f = byFlag[a];
157
+ if (!f) return { err: fail("validation", "UNKNOWN_FLAG", "未知参数 " + a) };
158
+ const raw = argv[++i];
159
+ if (raw === undefined) return { err: fail("validation", "MISSING_VALUE", a + " 缺少取值") };
160
+ let val = raw;
161
+ if (f.type.startsWith("list")) val = raw.split(",").map((s) => s.trim()).filter(Boolean);
162
+ else if (f.type.startsWith("object")) { try { val = JSON.parse(raw); } catch { return { err: fail("validation", "BAD_JSON", a + " 需为合法 JSON") }; } }
163
+ else if (f.type === "integer") val = Number(raw);
164
+ args[f.field] = val;
165
+ }
166
+ return { args };
167
+ }
168
+
169
+ async function main() {
170
+ const argv = process.argv.slice(2);
171
+ if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") return printHelp();
172
+ if (argv[0] === "--version" || argv[0] === "-V") { process.stdout.write(VERSION + "\n"); return; }
173
+ if (argv[0] === "bind") return doBind(argv.slice(1));
174
+ if (argv[0] === "skills" && argv[1] === "install") { require(path.join(__dirname, "install-skills.js")).installTo(argv[2]); return; }
175
+
176
+ const [action, ...rest] = argv;
177
+ if (rest.includes("--help") || rest.includes("-h")) return printHelp(action);
178
+
179
+ const cmd = findCmd(action);
180
+ if (!cmd) return fail("validation", "UNKNOWN_COMMAND", "未知命令 " + action);
181
+
182
+ const parsed = parseArgs(cmd, rest);
183
+ if (parsed.err) return;
184
+ const { args } = parsed;
185
+ const missing = cmd.flags.filter((f) => f.required && args[f.field] === undefined).map((f) => f.flag);
186
+ if (missing.length) return fail("validation", "INVALID_ARGS", "缺少必填参数:" + missing.join(" "), { usage: usageOf(cmd) });
187
+
188
+ const { api, token } = resolveConfig();
189
+ if (!api) return fail("config", "CONFIG", "未配置网关地址(" + COMMAND + " bind 或 env NEURON_API_BASE_URL)");
190
+ if (!token) return fail("auth_error", "AUTH_MISSING", "未配置 token(" + COMMAND + " bind 或 env NEURON_TOKEN)");
191
+
192
+ try {
193
+ // 直达能力级出口 /{key}/{op};body 即该 op 的入参对象(send=args)。
194
+ const res = await fetch(api + cmd.transport.path, {
195
+ method: cmd.transport.method,
196
+ headers: {
197
+ "X-Social-Neuron-CLI-Key": token,
198
+ "Content-Type": "application/json",
199
+ "X-Caller": "ai-agent",
200
+ "X-Neuron-CLI-Version": VERSION,
201
+ "X-Neuron-Manifest-Rev": String(MANIFEST.revision),
202
+ },
203
+ body: JSON.stringify(args),
204
+ signal: AbortSignal.timeout(30000),
205
+ });
206
+ const text = await res.text();
207
+ let body;
208
+ try { body = JSON.parse(text); } catch { return fail("api_error", "HTTP_" + res.status, text.slice(0, 200)); }
209
+ emit(normalize(body));
210
+ } catch (e) { fail("network", "NETWORK", "连接网关失败: " + e.message); }
211
+ }
212
+
213
+ main();
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // ⚠️ 本文件由 backend/app/cli_spec/gen.py 生成,请勿手改。
5
+ // postinstall:把包内 SKILL.md 覆盖写到 OpenClaw 技能目录(OPENCLAW_SKILLS_DIR 或 ~/.openclaw/skills)。
6
+
7
+ const fs = require("fs");
8
+ const os = require("os");
9
+ const path = require("path");
10
+
11
+ function installTo(dir) {
12
+ const dst = dir || process.env.OPENCLAW_SKILLS_DIR || path.join(os.homedir(), ".openclaw", "skills");
13
+ const src = path.join(__dirname, "..", "skills");
14
+ if (!fs.existsSync(src)) { console.error("[neuron] no bundled skills; skip"); return; }
15
+ const written = [];
16
+ for (const name of fs.readdirSync(src)) {
17
+ const from = path.join(src, name);
18
+ if (!fs.statSync(from).isDirectory()) continue;
19
+ const to = path.join(dst, name);
20
+ fs.mkdirSync(to, { recursive: true });
21
+ for (const f of fs.readdirSync(from)) { fs.copyFileSync(path.join(from, f), path.join(to, f)); written.push(path.join(to, f)); }
22
+ }
23
+ console.error("[neuron] skills → " + dst + " (" + written.length + " files)");
24
+ for (const f of written.filter((p) => p.endsWith(".md"))) console.error("[neuron] 加载:" + f);
25
+ }
26
+
27
+ module.exports = { installTo };
28
+
29
+ if (require.main === module) {
30
+ try { installTo(); } catch (e) { console.error("[neuron] install-skills skipped:", e.message); }
31
+ }