c456-cli 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 (3) hide show
  1. package/README.md +134 -0
  2. package/dist/index.js +742 -0
  3. package/package.json +41 -0
package/README.md ADDED
@@ -0,0 +1,134 @@
1
+ <div align="center">
2
+
3
+ <img src="logo.svg" alt="C456" width="160" height="160" />
4
+
5
+ <br />
6
+
7
+ <a href="https://c456.com">https://c456.com</a>
8
+
9
+ </div>
10
+
11
+ # c456-cli
12
+
13
+ C456 命令行工具:通过 **HTTP API v1** 读写收录、打法等数据,适合本地使用或与 AI Agent 集成。
14
+
15
+ ## 安装
16
+
17
+ ```bash
18
+ npm install -g c456-cli
19
+ # 或
20
+ bun add -g c456-cli
21
+ ```
22
+
23
+ 安装后的命令名为 **`c456`**。也可用 `npx` / `bunx` 单次执行:
24
+
25
+ ```bash
26
+ npx c456-cli --help
27
+ bunx c456-cli --help
28
+ ```
29
+
30
+ ## 配置
31
+
32
+ ### API Key
33
+
34
+ 在 Web 端登录后,于「API Key / 访问令牌」中创建密钥(仅创建时可见全文)。
35
+
36
+ ```bash
37
+ c456 config set-key <your-token>
38
+ ```
39
+
40
+ 或通过环境变量(适合 CI / Agent):
41
+
42
+ ```bash
43
+ export C456_API_KEY=<your-token>
44
+ ```
45
+
46
+ ### 站点地址
47
+
48
+ 若使用自托管实例,需指定**站点根 URL**(与浏览器访问地址一致,无尾部 `/api`):
49
+
50
+ ```bash
51
+ c456 config set-url https://your-c456.example.com
52
+ ```
53
+
54
+ 或环境变量:
55
+
56
+ ```bash
57
+ export C456_URL=https://your-c456.example.com
58
+ ```
59
+
60
+ 命令行临时覆盖(**推荐用于多环境**,注意与子命令里的 `-u`「目标 URL」区分):
61
+
62
+ ```bash
63
+ c456 -B https://your-c456.example.com intake list
64
+ ```
65
+
66
+ ### 查看配置
67
+
68
+ ```bash
69
+ c456 config show
70
+ ```
71
+
72
+ 配置文件遵循 XDG:默认 `~/.config/c456/config.json`(可通过 `XDG_CONFIG_HOME` 调整)。**请勿将含密钥的文件提交到仓库**;密钥等效于账户凭据。
73
+
74
+ ## 全局选项
75
+
76
+ | 选项 | 环境变量 | 说明 |
77
+ | --- | --- | --- |
78
+ | `-B`, `--base-url <url>` | `C456_URL` | C456 站点根地址;未设置时默认 `https://c456.com`(仍以配置文件为准) |
79
+
80
+ **API Key** 不设全局短选项(与子命令里 **`-k` = kind** 冲突):请用「[配置 > API Key](#api-key)」中的 `c456 config set-key` 或 `C456_API_KEY`。
81
+
82
+ `baseUrl` 优先级:**`-B` / 环境变量 / 配置文件 / 内置默认**;`apiKey` 优先级:**环境变量 / 配置文件**(无内置默认)。
83
+
84
+ ## 常用命令
85
+
86
+ ### 收录(intake)
87
+
88
+ ```bash
89
+ # 按 URL 创建 tool 收录(-B 为站点,-u 为收录目标)
90
+ c456 -B https://c456.example.com intake new -k tool -u "https://github.com/owner/repo"
91
+
92
+ # 纯文本信号(可无 URL)
93
+ c456 intake new -k signal -t "标题" -b "正文"
94
+
95
+ c456 intake show <id>
96
+ c456 intake list -k signal -q "关键词"
97
+ c456 intake update <id> -t "新标题"
98
+ c456 intake delete <id> --force
99
+ ```
100
+
101
+ ### 资料抓取(fetch)
102
+
103
+ ```bash
104
+ c456 fetch profile -u "https://..." -p link_product
105
+ c456 fetch detect -u "https://..."
106
+ ```
107
+
108
+ ### 搜索(search)
109
+
110
+ ```bash
111
+ c456 search signals -q "关键词"
112
+ c456 search playbooks -q "关键词"
113
+ ```
114
+
115
+ ### 打法(playbook)
116
+
117
+ ```bash
118
+ c456 playbook new -t "标题" -b "Markdown 正文"
119
+ c456 playbook show <id>
120
+ c456 playbook list -q "关键词"
121
+ ```
122
+
123
+ ## 开发
124
+
125
+ ```bash
126
+ cd c456-cli
127
+ bun install
128
+ bun run build
129
+ node dist/index.js --help
130
+ ```
131
+
132
+ ## 许可证
133
+
134
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,742 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.js
4
+ import { Command as Command6 } from "commander";
5
+
6
+ // package.json
7
+ var package_default = {
8
+ name: "c456-cli",
9
+ version: "0.1.0",
10
+ description: "C456 CLI - \u5185\u5BB9\u5F55\u5165\u4E0E\u6574\u7406\u5DE5\u5177",
11
+ type: "module",
12
+ bin: {
13
+ c456: "dist/index.js"
14
+ },
15
+ files: [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ scripts: {
20
+ build: "node scripts/build.js",
21
+ prepublishOnly: "npm run build"
22
+ },
23
+ dependencies: {
24
+ cfonts: "^3.3.1",
25
+ commander: "^12.1.0",
26
+ open: "^10.1.0"
27
+ },
28
+ devDependencies: {
29
+ esbuild: "^0.24.0"
30
+ },
31
+ keywords: [
32
+ "c456",
33
+ "cli",
34
+ "content",
35
+ "intake"
36
+ ],
37
+ license: "MIT",
38
+ engines: {
39
+ node: ">=20.0.0"
40
+ },
41
+ homepage: "https://github.com/xiaohui-zhangxh/c456#c456-cli",
42
+ repository: {
43
+ type: "git",
44
+ url: "git+https://github.com/xiaohui-zhangxh/c456.git",
45
+ directory: "c456-cli"
46
+ }
47
+ };
48
+
49
+ // src/commands/intake.js
50
+ import { Command } from "commander";
51
+
52
+ // src/client.js
53
+ import { readFileSync } from "node:fs";
54
+ import { join, dirname } from "node:path";
55
+ import { fileURLToPath } from "node:url";
56
+ var __dirname = dirname(fileURLToPath(import.meta.url));
57
+ var CONFIG_DIR = join(process.env.XDG_CONFIG_HOME || process.env.HOME || process.env.USERPROFILE || ".", ".config", "c456");
58
+ var CONFIG_PATH = join(CONFIG_DIR, "config.json");
59
+ function loadConfig() {
60
+ try {
61
+ const raw = readFileSync(CONFIG_PATH, "utf-8");
62
+ return JSON.parse(raw);
63
+ } catch {
64
+ return {};
65
+ }
66
+ }
67
+ async function saveConfig(config) {
68
+ const fs = await import("node:fs");
69
+ const path = await import("node:path");
70
+ const dir = path.dirname(CONFIG_PATH);
71
+ if (!fs.existsSync(dir)) {
72
+ fs.mkdirSync(dir, { recursive: true });
73
+ }
74
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
75
+ }
76
+ function getApiKey() {
77
+ return process.env.C456_API_KEY || loadConfig().apiKey || null;
78
+ }
79
+ function getBaseUrl(cliBaseUrl) {
80
+ const fromCli = cliBaseUrl !== void 0 && cliBaseUrl !== null && String(cliBaseUrl).trim() !== "" ? String(cliBaseUrl).replace(/\/+$/, "") : null;
81
+ const raw = fromCli || process.env.C456_URL || loadConfig().baseUrl || "https://c456.com";
82
+ return String(raw).replace(/\/+$/, "");
83
+ }
84
+ function getRootCommand(cmd) {
85
+ let c = cmd;
86
+ while (c.parent) c = c.parent;
87
+ return c;
88
+ }
89
+ function metaPerPage(meta) {
90
+ if (!meta) return 20;
91
+ const n = meta.per_page ?? meta.perPage;
92
+ return n !== void 0 && n !== null && Number(n) > 0 ? Number(n) : 20;
93
+ }
94
+ function formatApiErrorMessage(error) {
95
+ if (!error || typeof error !== "object") {
96
+ return String(error ?? "\u8BF7\u6C42\u5931\u8D25");
97
+ }
98
+ const base = (error.message ?? "\u8BF7\u6C42\u5931\u8D25").trim();
99
+ const fields = error.fields;
100
+ if (!fields || typeof fields !== "object") {
101
+ return base;
102
+ }
103
+ const keys = Object.keys(fields);
104
+ if (keys.length === 0) {
105
+ return base;
106
+ }
107
+ const label = (k) => k === "base" ? "\u8BF4\u660E" : k;
108
+ const lines = keys.map((k) => ` \u2022 ${label(k)}\uFF1A${fields[k]}`);
109
+ return `${base}
110
+ ${lines.join("\n")}`;
111
+ }
112
+ var ApiClient = class {
113
+ constructor(baseUrl, apiKey) {
114
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
115
+ this.apiKey = apiKey;
116
+ }
117
+ /**
118
+ * 构建请求头
119
+ */
120
+ headers() {
121
+ const h = { "Content-Type": "application/json" };
122
+ if (this.apiKey) {
123
+ h["Authorization"] = `Bearer ${this.apiKey}`;
124
+ }
125
+ return h;
126
+ }
127
+ /**
128
+ * GET 请求
129
+ */
130
+ async get(path, params = {}) {
131
+ const url = new URL(`${this.baseUrl}/api/v1${path}`);
132
+ Object.entries(params).forEach(([k, v]) => {
133
+ if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
134
+ });
135
+ const res = await fetch(url.toString(), { headers: this.headers() });
136
+ return this.handleResponse(res);
137
+ }
138
+ /**
139
+ * POST 请求
140
+ */
141
+ async post(path, body = {}) {
142
+ const url = `${this.baseUrl}/api/v1${path}`;
143
+ const res = await fetch(url, {
144
+ method: "POST",
145
+ headers: this.headers(),
146
+ body: JSON.stringify(body)
147
+ });
148
+ return this.handleResponse(res);
149
+ }
150
+ /**
151
+ * PATCH 请求
152
+ */
153
+ async patch(path, body = {}) {
154
+ const url = `${this.baseUrl}/api/v1${path}`;
155
+ const res = await fetch(url, {
156
+ method: "PATCH",
157
+ headers: this.headers(),
158
+ body: JSON.stringify(body)
159
+ });
160
+ return this.handleResponse(res);
161
+ }
162
+ /**
163
+ * DELETE 请求
164
+ */
165
+ async delete(path) {
166
+ const url = `${this.baseUrl}/api/v1${path}`;
167
+ const res = await fetch(url, {
168
+ method: "DELETE",
169
+ headers: this.headers()
170
+ });
171
+ return this.handleResponse(res);
172
+ }
173
+ /**
174
+ * 统一响应处理
175
+ */
176
+ async handleResponse(res) {
177
+ const data = await res.json().catch(() => ({}));
178
+ if (!res.ok) {
179
+ const error = data.error || { message: `HTTP ${res.status}` };
180
+ const message = formatApiErrorMessage(error);
181
+ throw new ApiError(message, res.status, {
182
+ fields: error.fields && typeof error.fields === "object" ? error.fields : void 0,
183
+ code: error.code
184
+ });
185
+ }
186
+ return data;
187
+ }
188
+ };
189
+ var ApiError = class extends Error {
190
+ /**
191
+ * @param {string} message
192
+ * @param {number} status
193
+ * @param {{ fields?: Record<string, string>, code?: string }} [meta]
194
+ */
195
+ constructor(message, status, meta = {}) {
196
+ super(message);
197
+ this.name = "ApiError";
198
+ this.status = status;
199
+ this.fields = meta.fields;
200
+ this.code = meta.code;
201
+ }
202
+ };
203
+
204
+ // src/context.js
205
+ function resolveApi(cmd) {
206
+ const root = getRootCommand(cmd);
207
+ const o = root.opts();
208
+ const apiKey = getApiKey();
209
+ const baseUrl = getBaseUrl(o.baseUrl);
210
+ return { apiKey, baseUrl, client: new ApiClient(baseUrl, apiKey) };
211
+ }
212
+
213
+ // src/commands/intake.js
214
+ var intake = new Command().name("intake").description("\u6536\u5F55\u7BA1\u7406 - \u521B\u5EFA\u3001\u66F4\u65B0\u3001\u5220\u9664\u5DE5\u5177/\u6E20\u9053/\u4FE1\u53F7");
215
+ intake.command("new").description("\u521B\u5EFA\u65B0\u6536\u5F55").option("-u, --url <url>", "\u76EE\u6807 URL\uFF08tool/channel \u65F6\u53EF\u9009\uFF0C\u7528\u4E8E\u81EA\u52A8\u89E3\u6790\u8D44\u6599\uFF09").option("-k, --kind <type>", "\u7C7B\u578B\uFF1Asignal/tool/channel\uFF08\u9ED8\u8BA4 signal\uFF09", "signal").option("-t, --title <title>", "\u6807\u9898\uFF08tool/channel \u5FC5\u586B\uFF09").option("-b, --body <text>", "\u6B63\u6587/\u63CF\u8FF0").option("--profile-data-json <json>", "\u8D44\u6599\u6BB5 JSON\uFF08tool/channel\uFF09").action(async (opts, cmd) => {
216
+ const { apiKey, baseUrl, client } = resolveApi(cmd);
217
+ if (!apiKey) {
218
+ console.error("\u9519\u8BEF\uFF1A\u672A\u914D\u7F6E API Key");
219
+ console.error("\u4F7F\u7528 c456 config set-key <token> \u914D\u7F6E\uFF0C\u6216\u8BBE\u7F6E C456_API_KEY \u73AF\u5883\u53D8\u91CF");
220
+ process.exit(1);
221
+ }
222
+ try {
223
+ const body = {
224
+ kind: opts.kind,
225
+ title: opts.title || "",
226
+ body: opts.body || ""
227
+ };
228
+ if (opts.url) {
229
+ body.url = opts.url;
230
+ }
231
+ if (opts.profileDataJson) {
232
+ body.profile_data_json = opts.profileDataJson;
233
+ }
234
+ const result = await client.post("/intakes", body);
235
+ console.log("\u2705 \u6536\u5F55\u521B\u5EFA\u6210\u529F");
236
+ console.log(` ID: ${result.data.id}`);
237
+ console.log(` \u7C7B\u578B\uFF1A${result.data.kind}`);
238
+ console.log(` \u6807\u9898\uFF1A${result.data.title || "(\u65E0)"}`);
239
+ } catch (err) {
240
+ console.error(`\u274C \u521B\u5EFA\u5931\u8D25\uFF1A${err.message}`);
241
+ const kind = String(opts.kind ?? "signal");
242
+ const urlHint = Boolean(opts.url) && kind === "signal" && err instanceof ApiError && err.status === 422;
243
+ if (urlHint) {
244
+ console.error("");
245
+ console.error("\u63D0\u793A\uFF1A\u5F53\u524D\u4E3A signal\uFF08\u9ED8\u8BA4\uFF09\u3002`-u` \u4EC5\u5728 `-k tool` \u6216 `-k channel` \u65F6\u4F1A\u7528\u4E8E\u81EA\u52A8\u89E3\u6790\u8D44\u6599\u3002");
246
+ console.error(" \u793A\u4F8B\uFF1A");
247
+ console.error(' c456 -B <\u7AD9\u70B9> intake new -k channel -u "<\u9891\u9053\u6216\u4E3B\u9875 URL>"');
248
+ console.error(' c456 -B <\u7AD9\u70B9> intake new -k tool -u "<\u5DE5\u5177 / \u4ED3\u5E93 URL>"');
249
+ }
250
+ process.exit(1);
251
+ }
252
+ });
253
+ intake.command("show").description("\u67E5\u770B\u6536\u5F55\u8BE6\u60C5").argument("<id>", "\u6536\u5F55 ID").action(async (id, opts, cmd) => {
254
+ const { apiKey, client } = resolveApi(cmd);
255
+ if (!apiKey) {
256
+ console.error("\u9519\u8BEF\uFF1A\u672A\u914D\u7F6E API Key");
257
+ process.exit(1);
258
+ }
259
+ try {
260
+ const result = await client.get(`/intakes/${id}`);
261
+ const data = result.data;
262
+ console.log(`ID: ${data.id}`);
263
+ console.log(`\u7C7B\u578B\uFF1A${data.kind}`);
264
+ console.log(`\u6807\u9898\uFF1A${data.title || "(\u65E0)"}`);
265
+ console.log(`\u6B63\u6587\uFF1A${data.body || "(\u65E0)"}`);
266
+ if (data.profileData) {
267
+ console.log(`\u8D44\u6599\u6BB5\uFF1A${JSON.stringify(data.profileData, null, 2)}`);
268
+ }
269
+ } catch (err) {
270
+ console.error(`\u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err.message}`);
271
+ process.exit(1);
272
+ }
273
+ });
274
+ intake.command("update").description("\u66F4\u65B0\u6536\u5F55").argument("<id>", "\u6536\u5F55 ID").option("-t, --title <title>", "\u65B0\u6807\u9898").option("-b, --body <text>", "\u65B0\u6B63\u6587").option("--favorited", "\u6807\u8BB0\u4E3A\u6536\u85CF").option("--unfavorited", "\u53D6\u6D88\u6536\u85CF").action(async (id, opts, cmd) => {
275
+ const { apiKey, client } = resolveApi(cmd);
276
+ if (!apiKey) {
277
+ console.error("\u9519\u8BEF\uFF1A\u672A\u914D\u7F6E API Key");
278
+ process.exit(1);
279
+ }
280
+ const body = {};
281
+ if (opts.title) body.title = opts.title;
282
+ if (opts.body) body.body = opts.body;
283
+ if (opts.favorited) body.favorited = true;
284
+ if (opts.unfavorited) body.favorited = false;
285
+ try {
286
+ await client.patch(`/intakes/${id}`, body);
287
+ console.log("\u2705 \u6536\u5F55\u66F4\u65B0\u6210\u529F");
288
+ } catch (err) {
289
+ console.error(`\u274C \u66F4\u65B0\u5931\u8D25\uFF1A${err.message}`);
290
+ process.exit(1);
291
+ }
292
+ });
293
+ intake.command("delete").description("\u5220\u9664\u6536\u5F55").argument("<id>", "\u6536\u5F55 ID").option("-f, --force", "\u5F3A\u5236\u5220\u9664\uFF08\u65E0\u9700\u786E\u8BA4\uFF09").action(async (id, opts, cmd) => {
294
+ const { apiKey, client } = resolveApi(cmd);
295
+ if (!apiKey) {
296
+ console.error("\u9519\u8BEF\uFF1A\u672A\u914D\u7F6E API Key");
297
+ process.exit(1);
298
+ }
299
+ if (!opts.force) {
300
+ const readline = await import("node:readline");
301
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
302
+ const answer = await new Promise((resolve) => {
303
+ rl.question("\u786E\u8BA4\u5220\u9664\uFF1F(y/N): ", (ans) => {
304
+ rl.close();
305
+ resolve(ans.toLowerCase());
306
+ });
307
+ });
308
+ if (answer !== "y" && answer !== "yes") {
309
+ console.log("\u5DF2\u53D6\u6D88");
310
+ return;
311
+ }
312
+ }
313
+ try {
314
+ await client.delete(`/intakes/${id}`);
315
+ console.log("\u2705 \u6536\u5F55\u5DF2\u5220\u9664");
316
+ } catch (err) {
317
+ console.error(`\u274C \u5220\u9664\u5931\u8D25\uFF1A${err.message}`);
318
+ process.exit(1);
319
+ }
320
+ });
321
+ intake.command("list").description("\u5217\u51FA\u6536\u5F55\uFF08\u5206\u9875\uFF09").option("-k, --kind <type>", "\u7C7B\u578B\u8FC7\u6EE4\uFF1Asignal/tool/channel").option("-q, --query <text>", "\u641C\u7D22\u5173\u952E\u8BCD").option("-p, --page <num>", "\u9875\u7801", "1").option("-n, --per-page <num>", "\u6BCF\u9875\u6570\u91CF", "20").action(async (opts, cmd) => {
322
+ const { apiKey, client } = resolveApi(cmd);
323
+ if (!apiKey) {
324
+ console.error("\u9519\u8BEF\uFF1A\u672A\u914D\u7F6E API Key");
325
+ process.exit(1);
326
+ }
327
+ try {
328
+ const result = await client.get("/intakes", {
329
+ kind: opts.kind,
330
+ q: opts.query,
331
+ page: opts.page,
332
+ per_page: opts.perPage
333
+ });
334
+ const { data, meta } = result;
335
+ const perPage = metaPerPage(meta);
336
+ const totalPages = Math.max(1, Math.ceil(meta.total / perPage));
337
+ console.log(`\u5171 ${meta.total} \u6761\u6536\u5F55\uFF08\u7B2C ${meta.page}/${totalPages} \u9875\uFF09
338
+ `);
339
+ data.forEach((item) => {
340
+ const kindBadge = { signal: "\u{1F4E1}", tool: "\u{1F527}", channel: "\u{1F4E2}" }[item.kind] || "\u2022";
341
+ console.log(`${kindBadge} [${item.id}] ${item.kind}`);
342
+ console.log(` ${item.title || item.listSummary || "(\u65E0\u6807\u9898)"}`);
343
+ });
344
+ } catch (err) {
345
+ console.error(`\u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err.message}`);
346
+ process.exit(1);
347
+ }
348
+ });
349
+ var intake_default = intake;
350
+
351
+ // src/commands/fetch.js
352
+ import { Command as Command2 } from "commander";
353
+ var fetchProfile = new Command2().name("fetch").description("\u8D44\u6599\u6293\u53D6 - \u4ECE URL \u81EA\u52A8\u89E3\u6790\u5E73\u53F0\u8D44\u6599");
354
+ fetchProfile.command("profile").description("\u6293\u53D6\u6307\u5B9A URL \u7684\u8D44\u6599\u6BB5\u6570\u636E").requiredOption("-u, --url <url>", "\u76EE\u6807 URL").option("-p, --profile-id <type>", "\u8D44\u6599\u7C7B\u578B\uFF1Alink_product/package_registry/github_origin/social_account").action(async (opts, cmd) => {
355
+ const { apiKey, client } = resolveApi(cmd);
356
+ if (!apiKey) {
357
+ console.error("\u9519\u8BEF\uFF1A\u672A\u914D\u7F6E API Key");
358
+ process.exit(1);
359
+ }
360
+ try {
361
+ const body = { url: opts.url };
362
+ if (opts.profileId) {
363
+ body.profile_id = opts.profileId;
364
+ }
365
+ const result = await client.post("/fetches", body);
366
+ const { data, suggested_title } = result.data;
367
+ console.log("\u2705 \u8D44\u6599\u6293\u53D6\u6210\u529F");
368
+ if (suggested_title) {
369
+ console.log(`\u5EFA\u8BAE\u6807\u9898\uFF1A${suggested_title}`);
370
+ }
371
+ console.log("\n\u8D44\u6599\u6570\u636E\uFF1A");
372
+ console.log(JSON.stringify(data, null, 2));
373
+ } catch (err) {
374
+ console.error(`\u274C \u6293\u53D6\u5931\u8D25\uFF1A${err.message}`);
375
+ process.exit(1);
376
+ }
377
+ });
378
+ fetchProfile.command("detect").description("\u81EA\u52A8\u68C0\u6D4B URL \u7C7B\u578B\u5E76\u6293\u53D6\u8D44\u6599\u5E76\u521B\u5EFA tool \u6536\u5F55").requiredOption("-u, --url <url>", "\u76EE\u6807 URL").action(async (opts, cmd) => {
379
+ const { apiKey, client } = resolveApi(cmd);
380
+ if (!apiKey) {
381
+ console.error("\u9519\u8BEF\uFF1A\u672A\u914D\u7F6E API Key");
382
+ process.exit(1);
383
+ }
384
+ try {
385
+ const result = await client.post("/intakes", {
386
+ kind: "tool",
387
+ url: opts.url
388
+ });
389
+ console.log("\u2705 \u81EA\u52A8\u68C0\u6D4B\u5E76\u6536\u5F55\u6210\u529F");
390
+ console.log(`ID: ${result.data.id}`);
391
+ console.log(`\u7C7B\u578B\uFF1A${result.data.kind}`);
392
+ if (result.data.profileData) {
393
+ console.log("\n\u89E3\u6790\u7684\u8D44\u6599\u6BB5\uFF1A");
394
+ console.log(JSON.stringify(result.data.profileData, null, 2));
395
+ }
396
+ } catch (err) {
397
+ console.error(`\u274C \u68C0\u6D4B\u5931\u8D25\uFF1A${err.message}`);
398
+ process.exit(1);
399
+ }
400
+ });
401
+ var fetch_default = fetchProfile;
402
+
403
+ // src/commands/search.js
404
+ import { Command as Command3 } from "commander";
405
+ var searchCmd = new Command3().name("search").description("\u641C\u7D22 - \u67E5\u627E\u53EF\u5173\u8054\u7684\u6536\u5F55\u6216\u6253\u6CD5");
406
+ searchCmd.command("signals").description("\u641C\u7D22\u6536\u5F55\uFF08\u7528\u4E8E\u4FE1\u53F7\u5173\u8054\uFF09").option("-q, --query <text>", "\u641C\u7D22\u5173\u952E\u8BCD", "").option("-k, --kind <type>", "\u7C7B\u578B\u8FC7\u6EE4\uFF1Asignal/tool/channel").option("-l, --limit <num>", "\u7ED3\u679C\u6570\u91CF\u9650\u5236", "20").action(async (opts, cmd) => {
407
+ const { apiKey, client } = resolveApi(cmd);
408
+ if (!apiKey) {
409
+ console.error("\u9519\u8BEF\uFF1A\u672A\u914D\u7F6E API Key");
410
+ process.exit(1);
411
+ }
412
+ try {
413
+ const result = await client.get("/search/intakes", {
414
+ q: opts.query,
415
+ kind: opts.kind,
416
+ limit: opts.limit
417
+ });
418
+ const data = result.data;
419
+ if (data.length === 0) {
420
+ console.log("\u672A\u627E\u5230\u5339\u914D\u7684\u6536\u5F55");
421
+ return;
422
+ }
423
+ console.log(`\u627E\u5230 ${data.length} \u6761\u7ED3\u679C\uFF1A
424
+ `);
425
+ data.forEach((item) => {
426
+ const kindBadge = { signal: "\u{1F4E1}", tool: "\u{1F527}", channel: "\u{1F4E2}" }[item.kind] || "\u2022";
427
+ const sourceKey = item.source_key ? ` [${item.source_key}]` : "";
428
+ console.log(`${kindBadge} #${item.id}${sourceKey} ${item.title || "(\u65E0\u6807\u9898)"}`);
429
+ if (item.list_summary) {
430
+ console.log(` ${item.list_summary}`);
431
+ }
432
+ });
433
+ console.log("\n--- JSON ---");
434
+ console.log(JSON.stringify(data, null, 2));
435
+ } catch (err) {
436
+ console.error(`\u274C \u641C\u7D22\u5931\u8D25\uFF1A${err.message}`);
437
+ process.exit(1);
438
+ }
439
+ });
440
+ searchCmd.command("playbooks").description("\u641C\u7D22\u6253\u6CD5\uFF08\u7528\u4E8E\u4FE1\u53F7\u5173\u8054\uFF09").option("-q, --query <text>", "\u641C\u7D22\u5173\u952E\u8BCD", "").option("-l, --limit <num>", "\u7ED3\u679C\u6570\u91CF\u9650\u5236", "20").action(async (opts, cmd) => {
441
+ const { apiKey, client } = resolveApi(cmd);
442
+ if (!apiKey) {
443
+ console.error("\u9519\u8BEF\uFF1A\u672A\u914D\u7F6E API Key");
444
+ process.exit(1);
445
+ }
446
+ try {
447
+ const result = await client.get("/search/playbooks", {
448
+ q: opts.query,
449
+ limit: opts.limit
450
+ });
451
+ const data = result.data;
452
+ if (data.length === 0) {
453
+ console.log("\u672A\u627E\u5230\u5339\u914D\u7684\u6253\u6CD5");
454
+ return;
455
+ }
456
+ console.log(`\u627E\u5230 ${data.length} \u6761\u7ED3\u679C\uFF1A
457
+ `);
458
+ data.forEach((item) => {
459
+ console.log(`\u{1F4D8} #${item.id} ${item.title || "(\u65E0\u6807\u9898)"}`);
460
+ if (item.list_summary) {
461
+ console.log(` ${item.list_summary}`);
462
+ }
463
+ });
464
+ console.log("\n--- JSON ---");
465
+ console.log(JSON.stringify(data, null, 2));
466
+ } catch (err) {
467
+ console.error(`\u274C \u641C\u7D22\u5931\u8D25\uFF1A${err.message}`);
468
+ process.exit(1);
469
+ }
470
+ });
471
+ var search_default = searchCmd;
472
+
473
+ // src/commands/playbook.js
474
+ import { Command as Command4 } from "commander";
475
+ var playbookCmd = new Command4().name("playbook").description("\u6253\u6CD5\u7BA1\u7406 - \u521B\u5EFA\u3001\u66F4\u65B0\u3001\u5220\u9664\u6253\u6CD5");
476
+ playbookCmd.command("new").description("\u521B\u5EFA\u65B0\u6253\u6CD5").requiredOption("-t, --title <title>", "\u6253\u6CD5\u6807\u9898").option("-b, --body <text>", "\u6253\u6CD5\u6B63\u6587\uFF08Markdown\uFF09").option("--ref-intake <id>", "\u5F15\u7528\u6536\u5F55 ID\uFF08\u53EF\u591A\u6B21\u6307\u5B9A\uFF09").option("--ref-playbook <id>", "\u5F15\u7528\u6253\u6CD5 ID\uFF08\u53EF\u591A\u6B21\u6307\u5B9A\uFF09").action(async (opts, cmd) => {
477
+ const { apiKey, client } = resolveApi(cmd);
478
+ if (!apiKey) {
479
+ console.error("\u9519\u8BEF\uFF1A\u672A\u914D\u7F6E API Key");
480
+ process.exit(1);
481
+ }
482
+ const referenceTargets = [];
483
+ if (opts.refIntake) {
484
+ const intakeIds = Array.isArray(opts.refIntake) ? opts.refIntake : [opts.refIntake];
485
+ intakeIds.forEach((id) => {
486
+ referenceTargets.push({ type: "intake", id: parseInt(id, 10) });
487
+ });
488
+ }
489
+ if (opts.refPlaybook) {
490
+ const playbookIds = Array.isArray(opts.refPlaybook) ? opts.refPlaybook : [opts.refPlaybook];
491
+ playbookIds.forEach((id) => {
492
+ referenceTargets.push({ type: "playbook", id: parseInt(id, 10) });
493
+ });
494
+ }
495
+ try {
496
+ const body = {
497
+ title: opts.title,
498
+ body: opts.body || ""
499
+ };
500
+ if (referenceTargets.length > 0) {
501
+ body.reference_targets = referenceTargets;
502
+ }
503
+ const result = await client.post("/playbooks", body);
504
+ console.log("\u2705 \u6253\u6CD5\u521B\u5EFA\u6210\u529F");
505
+ console.log(` ID: ${result.data.id}`);
506
+ console.log(` \u6807\u9898\uFF1A${result.data.title}`);
507
+ } catch (err) {
508
+ console.error(`\u274C \u521B\u5EFA\u5931\u8D25\uFF1A${err.message}`);
509
+ process.exit(1);
510
+ }
511
+ });
512
+ playbookCmd.command("show").description("\u67E5\u770B\u6253\u6CD5\u8BE6\u60C5").argument("<id>", "\u6253\u6CD5 ID").action(async (id, opts, cmd) => {
513
+ const { apiKey, client } = resolveApi(cmd);
514
+ if (!apiKey) {
515
+ console.error("\u9519\u8BEF\uFF1A\u672A\u914D\u7F6E API Key");
516
+ process.exit(1);
517
+ }
518
+ try {
519
+ const result = await client.get(`/playbooks/${id}`);
520
+ const data = result.data;
521
+ console.log(`ID: ${data.id}`);
522
+ console.log(`\u6807\u9898\uFF1A${data.title}`);
523
+ console.log(`\u6B63\u6587\uFF1A
524
+ ${data.body || "(\u65E0)"}`);
525
+ if (data.referenceTargets && data.referenceTargets.length > 0) {
526
+ console.log("\n\u5F15\u7528\u76EE\u6807\uFF1A");
527
+ data.referenceTargets.forEach((ref) => {
528
+ console.log(` - ${ref.targetType} #${ref.targetId}: ${ref.title}`);
529
+ });
530
+ }
531
+ if (data.workflow && (data.workflow.nodes?.length || 0) > 0) {
532
+ console.log("\n\u5DE5\u4F5C\u6D41\uFF1A");
533
+ data.workflow.nodes.forEach((node, i) => {
534
+ console.log(` ${i + 1}. ${node.title || "(\u65E0\u6807\u9898)"}`);
535
+ });
536
+ }
537
+ } catch (err) {
538
+ console.error(`\u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err.message}`);
539
+ process.exit(1);
540
+ }
541
+ });
542
+ playbookCmd.command("update").description("\u66F4\u65B0\u6253\u6CD5").argument("<id>", "\u6253\u6CD5 ID").option("-t, --title <title>", "\u65B0\u6807\u9898").option("-b, --body <text>", "\u65B0\u6B63\u6587").action(async (id, opts, cmd) => {
543
+ const { apiKey, client } = resolveApi(cmd);
544
+ if (!apiKey) {
545
+ console.error("\u9519\u8BEF\uFF1A\u672A\u914D\u7F6E API Key");
546
+ process.exit(1);
547
+ }
548
+ const body = {};
549
+ if (opts.title) body.title = opts.title;
550
+ if (opts.body) body.body = opts.body;
551
+ try {
552
+ await client.patch(`/playbooks/${id}`, body);
553
+ console.log("\u2705 \u6253\u6CD5\u66F4\u65B0\u6210\u529F");
554
+ } catch (err) {
555
+ console.error(`\u274C \u66F4\u65B0\u5931\u8D25\uFF1A${err.message}`);
556
+ process.exit(1);
557
+ }
558
+ });
559
+ playbookCmd.command("delete").description("\u5220\u9664\u6253\u6CD5").argument("<id>", "\u6253\u6CD5 ID").option("-f, --force", "\u5F3A\u5236\u5220\u9664\uFF08\u65E0\u9700\u786E\u8BA4\uFF09").action(async (id, opts, cmd) => {
560
+ const { apiKey, client } = resolveApi(cmd);
561
+ if (!apiKey) {
562
+ console.error("\u9519\u8BEF\uFF1A\u672A\u914D\u7F6E API Key");
563
+ process.exit(1);
564
+ }
565
+ if (!opts.force) {
566
+ const readline = await import("node:readline");
567
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
568
+ const answer = await new Promise((resolve) => {
569
+ rl.question("\u786E\u8BA4\u5220\u9664\uFF1F(y/N): ", (ans) => {
570
+ rl.close();
571
+ resolve(ans.toLowerCase());
572
+ });
573
+ });
574
+ if (answer !== "y" && answer !== "yes") {
575
+ console.log("\u5DF2\u53D6\u6D88");
576
+ return;
577
+ }
578
+ }
579
+ try {
580
+ await client.delete(`/playbooks/${id}`);
581
+ console.log("\u2705 \u6253\u6CD5\u5DF2\u5220\u9664");
582
+ } catch (err) {
583
+ console.error(`\u274C \u5220\u9664\u5931\u8D25\uFF1A${err.message}`);
584
+ process.exit(1);
585
+ }
586
+ });
587
+ playbookCmd.command("list").description("\u5217\u51FA\u6253\u6CD5\uFF08\u5206\u9875\uFF09").option("-q, --query <text>", "\u641C\u7D22\u5173\u952E\u8BCD").option("-p, --page <num>", "\u9875\u7801", "1").option("-n, --per-page <num>", "\u6BCF\u9875\u6570\u91CF", "20").action(async (opts, cmd) => {
588
+ const { apiKey, client } = resolveApi(cmd);
589
+ if (!apiKey) {
590
+ console.error("\u9519\u8BEF\uFF1A\u672A\u914D\u7F6E API Key");
591
+ process.exit(1);
592
+ }
593
+ try {
594
+ const result = await client.get("/playbooks", {
595
+ q: opts.query,
596
+ page: opts.page,
597
+ per_page: opts.perPage
598
+ });
599
+ const { data, meta } = result;
600
+ const perPage = metaPerPage(meta);
601
+ const totalPages = Math.max(1, Math.ceil(meta.total / perPage));
602
+ console.log(`\u5171 ${meta.total} \u6761\u6253\u6CD5\uFF08\u7B2C ${meta.page}/${totalPages} \u9875\uFF09
603
+ `);
604
+ data.forEach((item) => {
605
+ console.log(`\u{1F4D8} [${item.id}] ${item.title}`);
606
+ if (item.listSummary) {
607
+ console.log(` ${item.listSummary}`);
608
+ }
609
+ });
610
+ } catch (err) {
611
+ console.error(`\u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err.message}`);
612
+ process.exit(1);
613
+ }
614
+ });
615
+ var playbook_default = playbookCmd;
616
+
617
+ // src/commands/config.js
618
+ import { Command as Command5 } from "commander";
619
+ var configCmd = new Command5().name("config").description("\u914D\u7F6E\u7BA1\u7406 - \u8BBE\u7F6E API Key \u548C\u7CFB\u7EDF\u5730\u5740");
620
+ configCmd.command("set-key").description("\u8BBE\u7F6E API Key").argument("<token>", "API Key \u4EE4\u724C").action((token, cmd) => {
621
+ const config = loadConfig();
622
+ config.apiKey = token;
623
+ saveConfig(config);
624
+ console.log("\u2705 API Key \u5DF2\u4FDD\u5B58\u81F3 ~/.config/c456/config.json");
625
+ console.log(` \u63D0\u793A\uFF1A\u4E5F\u53EF\u901A\u8FC7 C456_API_KEY \u73AF\u5883\u53D8\u91CF\u8BBE\u7F6E`);
626
+ });
627
+ configCmd.command("set-url").description("\u8BBE\u7F6E C456 \u7CFB\u7EDF\u5730\u5740").argument("<url>", "\u7CFB\u7EDF\u5730\u5740\uFF08\u5982 https://c456.com\uFF09").action((url, cmd) => {
628
+ const config = loadConfig();
629
+ config.baseUrl = url;
630
+ saveConfig(config);
631
+ console.log(`\u2705 \u7CFB\u7EDF\u5730\u5740\u5DF2\u8BBE\u7F6E\u4E3A\uFF1A${url}`);
632
+ console.log(` \u63D0\u793A\uFF1A\u4E5F\u53EF\u901A\u8FC7 C456_URL \u73AF\u5883\u53D8\u91CF\u8BBE\u7F6E`);
633
+ });
634
+ configCmd.command("show").description("\u663E\u793A\u5F53\u524D\u914D\u7F6E").action(() => {
635
+ const config = loadConfig();
636
+ console.log("\u5F53\u524D\u914D\u7F6E\uFF1A");
637
+ console.log(` \u7CFB\u7EDF\u5730\u5740\uFF1A${config.baseUrl || "https://c456.com"}`);
638
+ console.log(` API Key\uFF1A${config.apiKey ? config.apiKey.slice(0, 8) + "..." : "(\u672A\u8BBE\u7F6E)"}`);
639
+ console.log(`
640
+ \u914D\u7F6E\u6587\u4EF6\uFF1A~/.config/c456/config.json`);
641
+ });
642
+ configCmd.command("reset").description("\u91CD\u7F6E\u914D\u7F6E\uFF08\u5220\u9664\u914D\u7F6E\u6587\u4EF6\uFF09").option("-f, --force", "\u5F3A\u5236\u91CD\u7F6E\uFF08\u65E0\u9700\u786E\u8BA4\uFF09").action(async (opts) => {
643
+ const fs = await import("node:fs");
644
+ if (!opts.force) {
645
+ const readline = await import("node:readline");
646
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
647
+ const answer = await new Promise((resolve) => {
648
+ rl.question("\u786E\u8BA4\u5220\u9664\u914D\u7F6E\u6587\u4EF6\uFF1F(y/N): ", (ans) => {
649
+ rl.close();
650
+ resolve(ans.toLowerCase());
651
+ });
652
+ });
653
+ if (answer !== "y" && answer !== "yes") {
654
+ console.log("\u5DF2\u53D6\u6D88");
655
+ return;
656
+ }
657
+ }
658
+ if (fs.existsSync(CONFIG_PATH)) {
659
+ fs.unlinkSync(CONFIG_PATH);
660
+ console.log("\u2705 \u914D\u7F6E\u6587\u4EF6\u5DF2\u5220\u9664");
661
+ } else {
662
+ console.log("\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u65E0\u9700\u5220\u9664");
663
+ }
664
+ });
665
+ var config_default = configCmd;
666
+
667
+ // src/banner.js
668
+ import cfonts from "cfonts";
669
+ var { render } = cfonts;
670
+ var CFONTS_OPTIONS = {
671
+ font: "block",
672
+ colors: ["red", "white"],
673
+ env: "node",
674
+ spaceless: true
675
+ };
676
+ var ANSI_RE = /\u001b\[[\d;]*m/g;
677
+ function stripAnsi(s) {
678
+ return s.replace(ANSI_RE, "");
679
+ }
680
+ function renderBannerColored() {
681
+ const out = render("C456", CFONTS_OPTIONS, false, 0);
682
+ if (!out || !out.string) {
683
+ return "";
684
+ }
685
+ return out.string;
686
+ }
687
+ var _cachedColored;
688
+ var _cachedPlain;
689
+ function getBannerColored() {
690
+ if (_cachedColored === void 0) {
691
+ _cachedColored = renderBannerColored();
692
+ }
693
+ return _cachedColored;
694
+ }
695
+ function getBannerPlainText() {
696
+ if (_cachedPlain === void 0) {
697
+ _cachedPlain = stripAnsi(getBannerColored());
698
+ }
699
+ return _cachedPlain;
700
+ }
701
+ function getHelpBanner() {
702
+ if (process.env.C456_NO_BANNER === "1") {
703
+ return "";
704
+ }
705
+ const body = process.stdout.isTTY ? getBannerColored() : getBannerPlainText();
706
+ if (!body) {
707
+ return "";
708
+ }
709
+ return `
710
+ ${body}
711
+ `;
712
+ }
713
+
714
+ // src/index.js
715
+ var program = new Command6();
716
+ program.name("c456").description("C456 CLI - \u5FEB\u901F\u5185\u5BB9\u5F55\u5165\u4E0E\u6574\u7406\u5DE5\u5177").version(package_default.version);
717
+ program.addHelpText("before", () => getHelpBanner());
718
+ program.option(
719
+ "-B, --base-url <url>",
720
+ "C456 \u7AD9\u70B9\u6839\u5730\u5740\uFF1B\u672A\u4F20\u5219\u4F7F\u7528 C456_URL \u73AF\u5883\u53D8\u91CF\u6216 ~/.config/c456/config.json \u7684 baseUrl\uFF0C\u9ED8\u8BA4 https://c456.com"
721
+ );
722
+ program.addCommand(intake_default);
723
+ program.addCommand(fetch_default);
724
+ program.addCommand(search_default);
725
+ program.addCommand(playbook_default);
726
+ program.addCommand(config_default);
727
+ program.on("--help", () => {
728
+ console.log("\n\u793A\u4F8B:");
729
+ console.log(" # \u914D\u7F6E API Key");
730
+ console.log(" c456 config set-key your-api-token");
731
+ console.log("");
732
+ console.log(" # \u81EA\u6258\u7BA1\u7AD9\u70B9 + \u6309 URL \u6536\u5F55\u5DE5\u5177\uFF08-B=\u7AD9\u70B9\uFF0C-u=\u76EE\u6807 URL\uFF09");
733
+ console.log(' c456 -B https://c456.example.com intake new -k tool -u "https://github.com/owner/repo"');
734
+ console.log("");
735
+ console.log(" # \u641C\u7D22\u6536\u5F55");
736
+ console.log(' c456 search signals -q "AI agent"');
737
+ console.log("");
738
+ console.log("\u73AF\u5883\u53D8\u91CF:");
739
+ console.log(" C456_URL - \u7AD9\u70B9\u6839\u5730\u5740\uFF08\u4E0E -B / --base-url \u4E00\u81F4\uFF09");
740
+ console.log(" C456_API_KEY - API Key");
741
+ });
742
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "c456-cli",
3
+ "version": "0.1.0",
4
+ "description": "C456 CLI - 内容录入与整理工具",
5
+ "type": "module",
6
+ "bin": {
7
+ "c456": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "build": "node scripts/build.js",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "dependencies": {
18
+ "cfonts": "^3.3.1",
19
+ "commander": "^12.1.0",
20
+ "open": "^10.1.0"
21
+ },
22
+ "devDependencies": {
23
+ "esbuild": "^0.24.0"
24
+ },
25
+ "keywords": [
26
+ "c456",
27
+ "cli",
28
+ "content",
29
+ "intake"
30
+ ],
31
+ "license": "MIT",
32
+ "engines": {
33
+ "node": ">=20.0.0"
34
+ },
35
+ "homepage": "https://github.com/xiaohui-zhangxh/c456#c456-cli",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/xiaohui-zhangxh/c456.git",
39
+ "directory": "c456-cli"
40
+ }
41
+ }