c456-cli 0.2.0 → 0.4.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/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.js
4
- import { Command as Command9 } from "commander";
4
+ import { Command as Command13 } from "commander";
5
5
 
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "c456-cli",
9
- version: "0.2.0",
9
+ version: "0.4.0",
10
10
  description: "C456 CLI - \u5185\u5BB9\u5F55\u5165\u4E0E\u6574\u7406\u5DE5\u5177",
11
11
  type: "module",
12
12
  bin: {
@@ -14,6 +14,8 @@ var package_default = {
14
14
  },
15
15
  files: [
16
16
  "dist",
17
+ "docs",
18
+ "skills",
17
19
  "README.md"
18
20
  ],
19
21
  scripts: {
@@ -23,7 +25,8 @@ var package_default = {
23
25
  dependencies: {
24
26
  cfonts: "^3.3.1",
25
27
  commander: "^12.1.0",
26
- open: "^10.1.0"
28
+ open: "^10.1.0",
29
+ "playwright-core": "^1.50.0"
27
30
  },
28
31
  devDependencies: {
29
32
  esbuild: "^0.24.0"
@@ -49,36 +52,107 @@ var package_default = {
49
52
  // src/commands/intake.js
50
53
  import { Command } from "commander";
51
54
 
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() {
55
+ // src/lib/workspaceConfig.js
56
+ import fs from "node:fs";
57
+ import os from "node:os";
58
+ import path from "node:path";
59
+ var CLI_DIR_NAME = ".c456-cli";
60
+ function getGlobalConfigDir() {
61
+ const home = os.homedir();
62
+ const base = process.env.XDG_CONFIG_HOME || path.join(home, ".config");
63
+ return path.join(base, "c456");
64
+ }
65
+ function getGlobalConfigPath() {
66
+ return path.join(getGlobalConfigDir(), "config.json");
67
+ }
68
+ function findWorkspaceRootWalk(startDir) {
69
+ let cur = path.resolve(startDir);
70
+ for (; ; ) {
71
+ const marker = path.join(cur, CLI_DIR_NAME);
72
+ try {
73
+ if (fs.existsSync(marker) && fs.statSync(marker).isDirectory()) {
74
+ return cur;
75
+ }
76
+ } catch {
77
+ }
78
+ const parent = path.dirname(cur);
79
+ if (parent === cur) return null;
80
+ cur = parent;
81
+ }
82
+ }
83
+ function getWorkspaceRoot() {
84
+ const raw = process.env.C456_WORKSPACE?.trim();
85
+ if (raw) {
86
+ return path.resolve(raw);
87
+ }
88
+ return findWorkspaceRootWalk(process.cwd());
89
+ }
90
+ function getProjectConfigPath(workspaceRoot) {
91
+ return path.join(workspaceRoot, CLI_DIR_NAME, "config.json");
92
+ }
93
+ function resolveLocalConfigWritePath() {
94
+ const root = getWorkspaceRoot();
95
+ if (root) return getProjectConfigPath(root);
96
+ return path.join(process.cwd(), CLI_DIR_NAME, "config.json");
97
+ }
98
+ function loadMergedConfigSources() {
99
+ const globalPath = getGlobalConfigPath();
100
+ const globalCfg = readJsonFile(globalPath);
101
+ const workspaceRoot = getWorkspaceRoot();
102
+ if (!workspaceRoot) {
103
+ return { merged: { ...globalCfg }, globalPath, localPath: null, workspaceRoot: null };
104
+ }
105
+ const localPath = getProjectConfigPath(workspaceRoot);
106
+ if (!fs.existsSync(localPath)) {
107
+ return { merged: { ...globalCfg }, globalPath, localPath, workspaceRoot };
108
+ }
109
+ const localCfg = readJsonFile(localPath);
110
+ return {
111
+ merged: { ...globalCfg, ...localCfg },
112
+ globalPath,
113
+ localPath,
114
+ workspaceRoot
115
+ };
116
+ }
117
+ function readJsonFile(filePath) {
60
118
  try {
61
- const raw = readFileSync(CONFIG_PATH, "utf-8");
62
- return JSON.parse(raw);
119
+ const raw = fs.readFileSync(filePath, "utf-8");
120
+ const o = JSON.parse(raw);
121
+ return o && typeof o === "object" && !Array.isArray(o) ? o : {};
63
122
  } catch {
64
123
  return {};
65
124
  }
66
125
  }
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 });
126
+
127
+ // src/client.js
128
+ var CONFIG_PATH = getGlobalConfigPath();
129
+ function loadConfig() {
130
+ return loadMergedConfigSources().merged;
131
+ }
132
+ async function saveConfigPatch(patch, options = {}) {
133
+ const fs9 = await import("node:fs");
134
+ const pathMod = await import("node:path");
135
+ const global = options.global === true;
136
+ const targetPath = global ? getGlobalConfigPath() : resolveLocalConfigWritePath();
137
+ let existing = {};
138
+ try {
139
+ const raw = fs9.readFileSync(targetPath, "utf-8");
140
+ const o = JSON.parse(raw);
141
+ if (o && typeof o === "object" && !Array.isArray(o)) existing = o;
142
+ } catch {
73
143
  }
74
- fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
144
+ const next = { ...existing, ...patch };
145
+ fs9.mkdirSync(pathMod.dirname(targetPath), { recursive: true });
146
+ fs9.writeFileSync(targetPath, JSON.stringify(next, null, 2), "utf-8");
75
147
  }
76
148
  function getApiKey() {
77
- return process.env.C456_API_KEY || loadConfig().apiKey || null;
149
+ const v = process.env.C456_API_KEY || loadConfig().apiKey;
150
+ return v !== void 0 && v !== null && String(v).trim() !== "" ? String(v) : null;
78
151
  }
79
152
  function getBaseUrl(cliBaseUrl) {
80
153
  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";
154
+ const fromFile = loadConfig().baseUrl;
155
+ const raw = fromCli || process.env.C456_URL || fromFile || "https://c456.com";
82
156
  return String(raw).replace(/\/+$/, "");
83
157
  }
84
158
  function getRootCommand(cmd) {
@@ -127,8 +201,8 @@ var ApiClient = class {
127
201
  /**
128
202
  * GET 请求
129
203
  */
130
- async get(path, params = {}) {
131
- const url = new URL(`${this.baseUrl}/api/v1${path}`);
204
+ async get(path6, params = {}) {
205
+ const url = new URL(`${this.baseUrl}/api/v1${path6}`);
132
206
  Object.entries(params).forEach(([k, v]) => {
133
207
  if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
134
208
  });
@@ -138,8 +212,8 @@ var ApiClient = class {
138
212
  /**
139
213
  * POST 请求
140
214
  */
141
- async post(path, body = {}) {
142
- const url = `${this.baseUrl}/api/v1${path}`;
215
+ async post(path6, body = {}) {
216
+ const url = `${this.baseUrl}/api/v1${path6}`;
143
217
  const res = await fetch(url, {
144
218
  method: "POST",
145
219
  headers: this.headers(),
@@ -153,17 +227,17 @@ var ApiClient = class {
153
227
  * @param {Record<string, any>} fields
154
228
  * @param {{ fieldName: string, filePath: string, filename?: string, contentType?: string } | null} file
155
229
  */
156
- async postMultipart(path, fields = {}, file = null) {
157
- const url = `${this.baseUrl}/api/v1${path}`;
230
+ async postMultipart(path6, fields = {}, file = null) {
231
+ const url = `${this.baseUrl}/api/v1${path6}`;
158
232
  const form = new FormData();
159
233
  Object.entries(fields || {}).forEach(([k, v]) => {
160
234
  if (v === void 0 || v === null) return;
161
235
  form.append(k, String(v));
162
236
  });
163
237
  if (file && file.filePath) {
164
- const fs = await import("node:fs");
238
+ const fs9 = await import("node:fs");
165
239
  const pathMod = await import("node:path");
166
- const bytes = fs.readFileSync(file.filePath);
240
+ const bytes = fs9.readFileSync(file.filePath);
167
241
  const blob = new Blob([bytes], { type: file.contentType || "application/octet-stream" });
168
242
  const name = file.filename || pathMod.basename(file.filePath);
169
243
  form.append(file.fieldName, blob, name);
@@ -180,8 +254,8 @@ var ApiClient = class {
180
254
  /**
181
255
  * PATCH 请求
182
256
  */
183
- async patch(path, body = {}) {
184
- const url = `${this.baseUrl}/api/v1${path}`;
257
+ async patch(path6, body = {}) {
258
+ const url = `${this.baseUrl}/api/v1${path6}`;
185
259
  const res = await fetch(url, {
186
260
  method: "PATCH",
187
261
  headers: this.headers(),
@@ -195,17 +269,17 @@ var ApiClient = class {
195
269
  * @param {Record<string, any>} fields
196
270
  * @param {{ fieldName: string, filePath: string, filename?: string, contentType?: string } | null} file
197
271
  */
198
- async patchMultipart(path, fields = {}, file = null) {
199
- const url = `${this.baseUrl}/api/v1${path}`;
272
+ async patchMultipart(path6, fields = {}, file = null) {
273
+ const url = `${this.baseUrl}/api/v1${path6}`;
200
274
  const form = new FormData();
201
275
  Object.entries(fields || {}).forEach(([k, v]) => {
202
276
  if (v === void 0 || v === null) return;
203
277
  form.append(k, String(v));
204
278
  });
205
279
  if (file && file.filePath) {
206
- const fs = await import("node:fs");
280
+ const fs9 = await import("node:fs");
207
281
  const pathMod = await import("node:path");
208
- const bytes = fs.readFileSync(file.filePath);
282
+ const bytes = fs9.readFileSync(file.filePath);
209
283
  const blob = new Blob([bytes], { type: file.contentType || "application/octet-stream" });
210
284
  const name = file.filename || pathMod.basename(file.filePath);
211
285
  form.append(file.fieldName, blob, name);
@@ -222,8 +296,8 @@ var ApiClient = class {
222
296
  /**
223
297
  * DELETE 请求
224
298
  */
225
- async delete(path) {
226
- const url = `${this.baseUrl}/api/v1${path}`;
299
+ async delete(path6) {
300
+ const url = `${this.baseUrl}/api/v1${path6}`;
227
301
  const res = await fetch(url, {
228
302
  method: "DELETE",
229
303
  headers: this.headers()
@@ -271,13 +345,13 @@ function resolveApi(cmd) {
271
345
  }
272
346
 
273
347
  // src/textFile.js
274
- import { readFileSync as readFileSync2 } from "node:fs";
275
- function readTextFile(path) {
348
+ import { readFileSync } from "node:fs";
349
+ function readTextFile(path6) {
276
350
  try {
277
- return readFileSync2(path, "utf-8");
351
+ return readFileSync(path6, "utf-8");
278
352
  } catch (e) {
279
353
  const msg = e instanceof Error ? e.message : String(e);
280
- console.error(`\u9519\u8BEF\uFF1A\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6\uFF1A${path}`);
354
+ console.error(`\u9519\u8BEF\uFF1A\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6\uFF1A${path6}`);
281
355
  console.error(msg);
282
356
  process.exit(1);
283
357
  }
@@ -338,7 +412,7 @@ intake.command("show").description("\u67E5\u770B\u6536\u5F55\u8BE6\u60C5").argum
338
412
  process.exit(1);
339
413
  }
340
414
  });
341
- 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\uFF08\u4E0D\u63A8\u8350\u76F4\u63A5\u4F20\uFF1B\u8BF7\u7528 --body-file\uFF09").option("--body-file <path>", "\u65B0\u6B63\u6587\u6587\u4EF6\u8DEF\u5F84\uFF08type: markdown_kramdown\uFF1B\u5EFA\u8BAE\u5199\u5230\u5F53\u524D\u76EE\u5F55 .tmp/\uFF09").option("--favorited", "\u6807\u8BB0\u4E3A\u6536\u85CF").option("--unfavorited", "\u53D6\u6D88\u6536\u85CF").action(async (id, opts, cmd) => {
415
+ 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\uFF08\u4E0D\u63A8\u8350\u76F4\u63A5\u4F20\uFF1B\u8BF7\u7528 --body-file\uFF09").option("--body-file <path>", "\u65B0\u6B63\u6587\u6587\u4EF6\u8DEF\u5F84\uFF08type: markdown_kramdown\uFF1B\u5EFA\u8BAE\u5199\u5230\u5F53\u524D\u76EE\u5F55 .tmp/\uFF09").option("--profile-data-json <json>", "tool/channel\uFF1Aprofile_data \u7247\u6BB5\uFF08JSON \u5B57\u7B26\u4E32\uFF0C\u4E0E API \u5408\u5E76\u89C4\u5219\u4E00\u81F4\uFF09").option("--profile-data-json-file <path>", "\u4ECE\u6587\u4EF6\u8BFB\u53D6 profile_data \u7247\u6BB5 JSON\uFF08\u4E0E --profile-data-json \u4E92\u65A5\uFF09").option("--favorited", "\u6807\u8BB0\u4E3A\u6536\u85CF").option("--unfavorited", "\u53D6\u6D88\u6536\u85CF").action(async (id, opts, cmd) => {
342
416
  const { apiKey, client } = resolveApi(cmd);
343
417
  if (!apiKey) {
344
418
  console.error("\u9519\u8BEF\uFF1A\u672A\u914D\u7F6E API Key");
@@ -352,8 +426,33 @@ intake.command("update").description("\u66F4\u65B0\u6536\u5F55").argument("<id>"
352
426
  }
353
427
  if (opts.bodyFile) body.body = readTextFile(opts.bodyFile);
354
428
  if (opts.body) body.body = opts.body;
429
+ if (opts.profileDataJson && opts.profileDataJsonFile) {
430
+ console.error("\u9519\u8BEF\uFF1A--profile-data-json \u4E0E --profile-data-json-file \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528");
431
+ process.exit(1);
432
+ }
433
+ if (opts.profileDataJsonFile) {
434
+ try {
435
+ body.profile_data = JSON.parse(readTextFile(opts.profileDataJsonFile));
436
+ } catch (e) {
437
+ console.error(`\u9519\u8BEF\uFF1A\u65E0\u6CD5\u89E3\u6790 profile_data JSON\uFF08${e.message}\uFF09`);
438
+ process.exit(1);
439
+ }
440
+ } else if (opts.profileDataJson) {
441
+ try {
442
+ body.profile_data = JSON.parse(opts.profileDataJson);
443
+ } catch (e) {
444
+ console.error(`\u9519\u8BEF\uFF1A\u65E0\u6CD5\u89E3\u6790 --profile-data-json\uFF08${e.message}\uFF09`);
445
+ process.exit(1);
446
+ }
447
+ }
355
448
  if (opts.favorited) body.favorited = true;
356
449
  if (opts.unfavorited) body.favorited = false;
450
+ if (Object.keys(body).length === 0) {
451
+ console.error(
452
+ "\u9519\u8BEF\uFF1A\u8BF7\u81F3\u5C11\u63D0\u4F9B --title\u3001--body/--body-file\u3001--profile-data-json/--profile-data-json-file \u6216 --favorited/--unfavorited"
453
+ );
454
+ process.exit(1);
455
+ }
357
456
  try {
358
457
  await client.patch(`/intakes/${id}`, body);
359
458
  console.log("\u2705 \u6536\u5F55\u66F4\u65B0\u6210\u529F");
@@ -1043,38 +1142,701 @@ walkthroughCmd.command("delete").description("\u5220\u9664\u8BB2\u89E3").argumen
1043
1142
  });
1044
1143
  var walkthrough_default = walkthroughCmd;
1045
1144
 
1046
- // src/commands/config.js
1145
+ // src/commands/asset.js
1146
+ import { createHash } from "node:crypto";
1147
+ import { extname } from "node:path";
1047
1148
  import { Command as Command8 } from "commander";
1048
- var configCmd = new Command8().name("config").description("\u914D\u7F6E\u7BA1\u7406 - \u8BBE\u7F6E API Key \u548C\u7CFB\u7EDF\u5730\u5740");
1049
- configCmd.command("set-key").description("\u8BBE\u7F6E API Key").argument("<token>", "API Key \u4EE4\u724C").action((token, cmd) => {
1050
- const config = loadConfig();
1051
- config.apiKey = token;
1052
- saveConfig(config);
1053
- console.log("\u2705 API Key \u5DF2\u4FDD\u5B58\u81F3 ~/.config/c456/config.json");
1149
+
1150
+ // src/mediaLibraryFingerprint.js
1151
+ function normalizeForFingerprint(markdown) {
1152
+ const OMIT = "__C456_ASSET_URL_OMITTED__";
1153
+ const dquote = /!\[([^\]]*)\]\(\s*(https?:\/\/[^\s)]+)\s+"(c456:asset\/(\d+))"\s*\)/g;
1154
+ const squote = /!\[([^\]]*)\]\(\s*(https?:\/\/[^\s)]+)\s+'(c456:asset\/(\d+))'\s*\)/g;
1155
+ let s = String(markdown ?? "");
1156
+ s = s.replace(dquote, (_m, alt, _url, title) => `![${alt}](${OMIT} "${title}")`);
1157
+ s = s.replace(squote, (_m, alt, _url, title) => `![${alt}](${OMIT} '${title}')`);
1158
+ return s;
1159
+ }
1160
+
1161
+ // src/commands/asset.js
1162
+ var extToMime = {
1163
+ ".png": "image/png",
1164
+ ".jpg": "image/jpeg",
1165
+ ".jpeg": "image/jpeg",
1166
+ ".gif": "image/gif",
1167
+ ".webp": "image/webp",
1168
+ ".svg": "image/svg+xml"
1169
+ };
1170
+ function guessImageContentType(filePath) {
1171
+ const ext = extname(filePath).toLowerCase();
1172
+ return extToMime[ext] || "application/octet-stream";
1173
+ }
1174
+ var assetCmd = new Command8().name("asset").description("\u7D20\u6750\u5E93\uFF08\u56FE\u7247\uFF09\u2014 \u4E0A\u4F20\u3001\u5217\u8868\u3001\u7EED\u671F\u6B63\u6587\u4E2D\u7684\u9884\u89C8\u94FE\u63A5");
1175
+ function requireApiKey3(apiKey) {
1176
+ if (!apiKey) {
1177
+ console.error("\u9519\u8BEF\uFF1A\u672A\u914D\u7F6E API Key");
1178
+ process.exit(1);
1179
+ }
1180
+ }
1181
+ function sha256HexUtf8(markdown) {
1182
+ return createHash("sha256").update(normalizeForFingerprint(markdown), "utf8").digest("hex");
1183
+ }
1184
+ assetCmd.command("upload").description("\u4E0A\u4F20\u56FE\u7247\u5230\u7D20\u6750\u5E93").requiredOption("-f, --file <path>", "\u672C\u5730\u56FE\u7247\u8DEF\u5F84").action(async (opts, cmd) => {
1185
+ const { apiKey, client } = resolveApi(cmd);
1186
+ requireApiKey3(apiKey);
1187
+ const result = await client.postMultipart(
1188
+ "/assets",
1189
+ {},
1190
+ {
1191
+ fieldName: "file",
1192
+ filePath: opts.file,
1193
+ filename: void 0,
1194
+ contentType: guessImageContentType(opts.file)
1195
+ }
1196
+ );
1197
+ const d = result.data;
1198
+ console.log("\u2705 \u4E0A\u4F20\u6210\u529F");
1199
+ console.log(` id: ${d.id}`);
1200
+ console.log(` previewUrl: ${d.previewUrl}`);
1201
+ console.log("");
1202
+ console.log(d.markdownSnippet);
1203
+ });
1204
+ assetCmd.command("list").description("\u5217\u51FA\u7D20\u6750").option("-p, --page <num>", "\u9875\u7801", "1").option("-n, --per-page <num>", "\u6BCF\u9875\u6761\u6570", "50").action(async (opts, cmd) => {
1205
+ const { apiKey, client } = resolveApi(cmd);
1206
+ requireApiKey3(apiKey);
1207
+ const page = Number.parseInt(String(opts.page), 10) || 1;
1208
+ const perPage = Number.parseInt(String(opts.perPage), 10) || 50;
1209
+ const { data, meta } = await client.get("/assets", { page, per_page: perPage });
1210
+ const n = metaPerPage(meta);
1211
+ console.log(`\u5171 ${meta?.total ?? "?"} \u6761 \xB7 \u6BCF\u9875 ${n} \xB7 \u7B2C ${meta?.page ?? page} \u9875`);
1212
+ for (const row of data || []) {
1213
+ console.log(`- #${row.id} ${row.filename ?? ""} ${row.markdownSnippet ?? ""}`);
1214
+ }
1215
+ });
1216
+ assetCmd.command("update").description("\u66F4\u65B0\u7D20\u6750\u5728\u5E93\u4E2D\u7684\u5C55\u793A\u6587\u4EF6\u540D\uFF08\u4E0D\u66FF\u6362\u56FE\u7247\u5185\u5BB9\uFF1BJSON PATCH /assets/:id\uFF09").argument("<id>", "\u7D20\u6750 ID").requiredOption("--filename <name>", "\u65B0\u7684\u5C55\u793A\u6587\u4EF6\u540D\uFF0C\u5982 logo.webp").action(async (id, opts, cmd) => {
1217
+ const { apiKey, client } = resolveApi(cmd);
1218
+ requireApiKey3(apiKey);
1219
+ const { data } = await client.patch(`/assets/${id}`, { filename: opts.filename });
1220
+ console.log("\u2705 \u5DF2\u66F4\u65B0");
1221
+ console.log(` id: ${data.id}`);
1222
+ console.log(` filename: ${data.filename ?? ""}`);
1223
+ console.log("");
1224
+ console.log(data.markdownSnippet ?? "");
1225
+ });
1226
+ assetCmd.command("show").description("\u67E5\u770B\u5355\u6761\u7D20\u6750").argument("<id>", "\u7D20\u6750 ID").action(async (id, _opts, cmd) => {
1227
+ const { apiKey, client } = resolveApi(cmd);
1228
+ requireApiKey3(apiKey);
1229
+ const { data } = await client.get(`/assets/${id}`);
1230
+ console.log(JSON.stringify(data, null, 2));
1231
+ });
1232
+ assetCmd.command("delete").description("\u5220\u9664\u7D20\u6750\uFF08\u82E5\u4ECD\u88AB\u6B63\u6587\u5F15\u7528\u4F1A\u5931\u8D25\uFF09").argument("<id>", "\u7D20\u6750 ID").action(async (id, _opts, cmd) => {
1233
+ const { apiKey, client } = resolveApi(cmd);
1234
+ requireApiKey3(apiKey);
1235
+ await client.delete(`/assets/${id}`);
1236
+ console.log("\u2705 \u5DF2\u5220\u9664");
1237
+ });
1238
+ assetCmd.command("refresh-markdown").description("\u7EED\u671F\u6B63\u6587\u4E2D\u7684\u7D20\u6750\u9884\u89C8 URL\uFF08\u8BFB\u5165 Markdown\uFF0C\u8F93\u51FA\u66FF\u6362\u540E\u7684\u5168\u6587\u5230 stdout\uFF09").option("-b, --body <text>", "Markdown \u5B57\u7B26\u4E32").option("--body-file <path>", "Markdown \u6587\u4EF6\u8DEF\u5F84").action(async (opts, cmd) => {
1239
+ const { apiKey, client } = resolveApi(cmd);
1240
+ requireApiKey3(apiKey);
1241
+ if (opts.body && opts.bodyFile) {
1242
+ console.error("\u9519\u8BEF\uFF1A--body \u4E0E --body-file \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528");
1243
+ process.exit(1);
1244
+ }
1245
+ const markdown = opts.bodyFile ? readTextFile(opts.bodyFile) : opts.body ?? "";
1246
+ if (!markdown) {
1247
+ console.error("\u9519\u8BEF\uFF1A\u8BF7\u63D0\u4F9B --body \u6216 --body-file");
1248
+ process.exit(1);
1249
+ }
1250
+ const { data } = await client.post("/assets/refresh_markdown", { markdown });
1251
+ process.stdout.write(String(data.markdown ?? ""));
1252
+ });
1253
+ assetCmd.command("fingerprint").description("\u5BF9\u6B63\u6587\u505A\u4E0E\u670D\u52A1\u5668\u4E00\u81F4\u7684\u89C4\u8303\u5316\u540E\u8F93\u51FA sha256 hex\uFF08\u4E0D\u8C03\u7528\u7F51\u7EDC\uFF09").option("-b, --body <text>", "Markdown \u5B57\u7B26\u4E32").option("--body-file <path>", "Markdown \u6587\u4EF6\u8DEF\u5F84").action(async (opts) => {
1254
+ if (opts.body && opts.bodyFile) {
1255
+ console.error("\u9519\u8BEF\uFF1A--body \u4E0E --body-file \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528");
1256
+ process.exit(1);
1257
+ }
1258
+ const markdown = opts.bodyFile ? readTextFile(opts.bodyFile) : opts.body ?? "";
1259
+ if (!markdown) {
1260
+ console.error("\u9519\u8BEF\uFF1A\u8BF7\u63D0\u4F9B --body \u6216 --body-file");
1261
+ process.exit(1);
1262
+ }
1263
+ console.log(sha256HexUtf8(markdown));
1264
+ });
1265
+ var asset_default = assetCmd;
1266
+
1267
+ // src/commands/browser.js
1268
+ import fs5 from "node:fs";
1269
+ import { Command as Command9 } from "commander";
1270
+
1271
+ // src/lib/chromeExecutable.js
1272
+ import fs2 from "node:fs";
1273
+ import path2 from "node:path";
1274
+ import process2 from "node:process";
1275
+ function exists(p) {
1276
+ try {
1277
+ fs2.accessSync(p, fs2.constants.X_OK);
1278
+ return true;
1279
+ } catch {
1280
+ return false;
1281
+ }
1282
+ }
1283
+ function resolveChromeExecutable() {
1284
+ const env = process2.env.CHROME_PATH?.trim();
1285
+ if (env && exists(env)) return env;
1286
+ const platform = process2.platform;
1287
+ const candidates = [];
1288
+ if (platform === "darwin") {
1289
+ candidates.push(
1290
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
1291
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
1292
+ "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary"
1293
+ );
1294
+ } else if (platform === "win32") {
1295
+ const pf = process2.env["PROGRAMFILES"] || "C:\\\\Program Files";
1296
+ const pf86 = process2.env["PROGRAMFILES(X86)"] || "C:\\\\Program Files (x86)";
1297
+ candidates.push(
1298
+ path2.join(pf, "Google", "Chrome", "Application", "chrome.exe"),
1299
+ path2.join(pf86, "Google", "Chrome", "Application", "chrome.exe")
1300
+ );
1301
+ } else {
1302
+ candidates.push(
1303
+ "/usr/bin/google-chrome-stable",
1304
+ "/usr/bin/google-chrome",
1305
+ "/usr/bin/chromium-browser",
1306
+ "/usr/bin/chromium",
1307
+ "/snap/bin/chromium"
1308
+ );
1309
+ }
1310
+ for (const p of candidates) {
1311
+ if (exists(p)) return p;
1312
+ }
1313
+ return null;
1314
+ }
1315
+ function chromeExecutableHint() {
1316
+ return [
1317
+ "\u672A\u627E\u5230 Chrome / Chromium \u53EF\u6267\u884C\u6587\u4EF6\u3002",
1318
+ "\u8BF7\u5B89\u88C5 Google Chrome\uFF0C\u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF CHROME_PATH \u6307\u5411\u53EF\u6267\u884C\u6587\u4EF6\u3002",
1319
+ "\uFF08\u53EF\u9009\uFF09\u5728\u65E0\u7CFB\u7EDF Chrome \u7684\u73AF\u5883\u53EF\u5B89\u88C5 Playwright \u81EA\u5E26 Chromium\uFF1A",
1320
+ " npx playwright install chromium",
1321
+ "\u7136\u540E\u5B89\u88C5 npm \u5305 playwright\uFF0C\u5E76\u628A CHROME_PATH \u8BBE\u4E3A `node -e \"console.log(require('playwright').chromium.executablePath())\"` \u7684\u8F93\u51FA\u3002"
1322
+ ].join("\n");
1323
+ }
1324
+
1325
+ // src/lib/chromeCdp.js
1326
+ import http from "node:http";
1327
+ import { spawn } from "node:child_process";
1328
+ import process3 from "node:process";
1329
+ function cdpHttpUrl(port) {
1330
+ return `http://127.0.0.1:${port}`;
1331
+ }
1332
+ async function waitForCdpHttp(port, timeoutMs = 45e3) {
1333
+ const url = `${cdpHttpUrl(port)}/json/version`;
1334
+ const start = Date.now();
1335
+ let lastErr;
1336
+ while (Date.now() - start < timeoutMs) {
1337
+ try {
1338
+ const body = await httpGet(url);
1339
+ if (body && body.includes("webSocketDebuggerUrl")) {
1340
+ return JSON.parse(body);
1341
+ }
1342
+ } catch (e) {
1343
+ lastErr = e;
1344
+ }
1345
+ await new Promise((r) => setTimeout(r, 150));
1346
+ }
1347
+ throw new Error(
1348
+ `\u7B49\u5F85 Chrome DevTools \u7AEF\u53E3 ${port} \u8D85\u65F6${lastErr ? `\uFF1A${lastErr.message}` : ""}`
1349
+ );
1350
+ }
1351
+ function httpGet(url) {
1352
+ return new Promise((resolve, reject) => {
1353
+ const req = http.get(url, (res) => {
1354
+ let data = "";
1355
+ res.setEncoding("utf8");
1356
+ res.on("data", (c) => {
1357
+ data += c;
1358
+ });
1359
+ res.on("end", () => resolve(data));
1360
+ });
1361
+ req.on("error", reject);
1362
+ req.setTimeout(2e3, () => {
1363
+ req.destroy();
1364
+ reject(new Error("\u8BF7\u6C42\u8D85\u65F6"));
1365
+ });
1366
+ });
1367
+ }
1368
+ function spawnChromeWithCdp(chromePath, { userDataDir, port, extraArgs = [] }) {
1369
+ const args = [
1370
+ `--user-data-dir=${userDataDir}`,
1371
+ `--remote-debugging-port=${port}`,
1372
+ "--remote-allow-origins=*",
1373
+ "--no-first-run",
1374
+ "--no-default-browser-check",
1375
+ ...extraArgs
1376
+ ];
1377
+ const child = spawn(chromePath, args, {
1378
+ detached: true,
1379
+ stdio: "ignore",
1380
+ env: { ...process3.env }
1381
+ });
1382
+ child.unref();
1383
+ return { child, port };
1384
+ }
1385
+ function killProcessTree(pid) {
1386
+ if (!pid || pid <= 0) return;
1387
+ if (process3.platform === "win32") {
1388
+ const killer = spawn("taskkill", ["/PID", String(pid), "/T", "/F"], {
1389
+ stdio: "ignore",
1390
+ detached: true
1391
+ });
1392
+ killer.unref();
1393
+ return;
1394
+ }
1395
+ try {
1396
+ process3.kill(-pid, "SIGTERM");
1397
+ } catch {
1398
+ try {
1399
+ process3.kill(pid, "SIGTERM");
1400
+ } catch {
1401
+ }
1402
+ }
1403
+ }
1404
+
1405
+ // src/lib/freePort.js
1406
+ import net from "node:net";
1407
+ function getFreePort() {
1408
+ return new Promise((resolve, reject) => {
1409
+ const s = net.createServer();
1410
+ s.unref();
1411
+ s.on("error", reject);
1412
+ s.listen(0, "127.0.0.1", () => {
1413
+ const addr = s.address();
1414
+ const port = typeof addr === "object" && addr ? addr.port : null;
1415
+ s.close(() => {
1416
+ if (port) resolve(port);
1417
+ else reject(new Error("\u65E0\u6CD5\u5206\u914D\u672C\u5730\u7AEF\u53E3"));
1418
+ });
1419
+ });
1420
+ });
1421
+ }
1422
+ function isPortListening(port, host = "127.0.0.1") {
1423
+ return new Promise((resolve) => {
1424
+ const s = net.createConnection({ port, host }, () => {
1425
+ s.destroy();
1426
+ resolve(true);
1427
+ });
1428
+ s.on("error", () => resolve(false));
1429
+ s.setTimeout(800, () => {
1430
+ s.destroy();
1431
+ resolve(false);
1432
+ });
1433
+ });
1434
+ }
1435
+
1436
+ // src/lib/c456Cache.js
1437
+ import fs3 from "node:fs";
1438
+ import os2 from "node:os";
1439
+ import path3 from "node:path";
1440
+ function getC456CacheDir() {
1441
+ const home = os2.homedir();
1442
+ const xdgCache = process.env.XDG_CACHE_HOME || path3.join(home, ".cache");
1443
+ return path3.join(xdgCache, "c456-cli");
1444
+ }
1445
+ function ensureC456CacheDir() {
1446
+ const dir = getC456CacheDir();
1447
+ fs3.mkdirSync(dir, { recursive: true });
1448
+ return dir;
1449
+ }
1450
+ function getPersistentChromeProfileDir() {
1451
+ return path3.join(getC456CacheDir(), "chrome-profile");
1452
+ }
1453
+ function getBrowserDaemonStatePath() {
1454
+ return path3.join(getC456CacheDir(), "browser-daemon.json");
1455
+ }
1456
+ function getVersionCheckStatePath() {
1457
+ return path3.join(getC456CacheDir(), "version-check-state.json");
1458
+ }
1459
+
1460
+ // src/lib/browserDaemon.js
1461
+ import fs4 from "node:fs";
1462
+ import process4 from "node:process";
1463
+ function readJson(path6) {
1464
+ try {
1465
+ return JSON.parse(fs4.readFileSync(path6, "utf8"));
1466
+ } catch {
1467
+ return null;
1468
+ }
1469
+ }
1470
+ function isPidAlive(pid) {
1471
+ if (!pid || pid <= 0) return false;
1472
+ try {
1473
+ process4.kill(pid, 0);
1474
+ return true;
1475
+ } catch {
1476
+ return false;
1477
+ }
1478
+ }
1479
+ async function loadReconciledDaemonState() {
1480
+ const path6 = getBrowserDaemonStatePath();
1481
+ const st = readJson(path6);
1482
+ if (!st?.port || !st?.pid) return null;
1483
+ const pidOk = isPidAlive(st.pid);
1484
+ const portOk = await isPortListening(st.port);
1485
+ if (!pidOk || !portOk) {
1486
+ try {
1487
+ fs4.unlinkSync(path6);
1488
+ } catch {
1489
+ }
1490
+ return null;
1491
+ }
1492
+ return { ...st, statePath: path6, cdpHttp: cdpHttpUrl(st.port) };
1493
+ }
1494
+ function writeDaemonState(state) {
1495
+ ensureC456CacheDir();
1496
+ const path6 = getBrowserDaemonStatePath();
1497
+ fs4.writeFileSync(path6, `${JSON.stringify(state, null, 2)}
1498
+ `, "utf8");
1499
+ }
1500
+ function clearDaemonStateFile() {
1501
+ try {
1502
+ fs4.unlinkSync(getBrowserDaemonStatePath());
1503
+ } catch {
1504
+ }
1505
+ }
1506
+
1507
+ // src/commands/browser.js
1508
+ function requireChrome() {
1509
+ const exe = resolveChromeExecutable();
1510
+ if (!exe) {
1511
+ console.error(chromeExecutableHint());
1512
+ process.exit(1);
1513
+ }
1514
+ return exe;
1515
+ }
1516
+ var browserCmd = new Command9("browser").name("browser").description(
1517
+ "\u6709\u5934 Chrome\uFF1A\u6301\u4E45 profile\uFF08\u9ED8\u8BA4 ~/.cache/c456-cli/chrome-profile\uFF09\u3001CDP \u7AEF\u53E3\u81EA\u52A8\u5206\u914D\uFF1B\u4FBF\u4E8E\u5148\u767B\u5F55\u518D\u622A\u56FE"
1518
+ );
1519
+ browserCmd.command("start").description("\u542F\u52A8 Chrome\uFF08\u82E5\u5DF2\u5728\u8FD0\u884C\u5219\u6253\u5370\u73B0\u6709 CDP \u5730\u5740\uFF09").option(
1520
+ "-p, --port <n>",
1521
+ "remote-debugging-port\uFF1B\u9ED8\u8BA4\u81EA\u52A8\u9009\u62E9\u672C\u673A\u53EF\u7528\u7AEF\u53E3"
1522
+ ).action(async (opts) => {
1523
+ const existing = await loadReconciledDaemonState();
1524
+ if (existing) {
1525
+ console.log("Chrome \u5DF2\u5728\u8FD0\u884C\uFF08CLI \u6258\u7BA1\uFF09\u3002");
1526
+ console.log(` CDP: ${existing.cdpHttp}`);
1527
+ console.log(` port: ${existing.port}`);
1528
+ console.log(` pid: ${existing.pid}`);
1529
+ console.log(` userDataDir: ${existing.userDataDir}`);
1530
+ return;
1531
+ }
1532
+ const chromePath = requireChrome();
1533
+ ensureC456CacheDir();
1534
+ const userDataDir = getPersistentChromeProfileDir();
1535
+ fs5.mkdirSync(userDataDir, { recursive: true });
1536
+ const port = opts.port != null && opts.port !== "" ? Number.parseInt(String(opts.port), 10) : await getFreePort();
1537
+ if (!Number.isFinite(port) || port <= 0 || port > 65535) {
1538
+ console.error("\u9519\u8BEF\uFF1A--port \u65E0\u6548");
1539
+ process.exit(1);
1540
+ }
1541
+ const { child } = spawnChromeWithCdp(chromePath, { userDataDir, port });
1542
+ try {
1543
+ await waitForCdpHttp(port);
1544
+ writeDaemonState({
1545
+ port,
1546
+ pid: child.pid,
1547
+ userDataDir,
1548
+ cdpHttp: cdpHttpUrl(port),
1549
+ mode: "persistent",
1550
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
1551
+ });
1552
+ console.log("\u2705 \u5DF2\u542F\u52A8\u6709\u5934 Chrome\uFF08\u6301\u4E45 profile\uFF0C\u53EF\u5728\u6B64\u7A97\u53E3\u767B\u5F55\uFF09\u3002");
1553
+ console.log(` CDP: ${cdpHttpUrl(port)}`);
1554
+ console.log(` port: ${port}`);
1555
+ console.log(` pid: ${child.pid}`);
1556
+ console.log(` userDataDir: ${userDataDir}`);
1557
+ console.log("");
1558
+ console.log("\u7ED3\u675F\u8BF7\u6267\u884C\uFF1Ac456 browser stop");
1559
+ console.log("\u622A\u56FE\u53EF\u6267\u884C\uFF1Ac456 screenshot <url> -o <\u6587\u4EF6.png>\uFF08\u5C06\u590D\u7528\u672C\u5B9E\u4F8B\uFF09");
1560
+ } catch (e) {
1561
+ killProcessTree(child.pid);
1562
+ clearDaemonStateFile();
1563
+ console.error(e?.message || e);
1564
+ process.exit(1);
1565
+ }
1566
+ });
1567
+ browserCmd.command("stop").description("\u5173\u95ED\u7531 c456 browser start \u542F\u52A8\u7684 Chrome \u5E76\u91CA\u653E\u7AEF\u53E3\u8BB0\u5F55").action(async () => {
1568
+ const st = await loadReconciledDaemonState();
1569
+ if (!st) {
1570
+ console.log("\u5F53\u524D\u6CA1\u6709\u7531 c456 browser start \u8BB0\u5F55\u7684 Chrome \u8FDB\u7A0B\u3002");
1571
+ clearDaemonStateFile();
1572
+ return;
1573
+ }
1574
+ killProcessTree(st.pid);
1575
+ clearDaemonStateFile();
1576
+ console.log(`\u2705 \u5DF2\u53D1\u9001\u7ED3\u675F\u4FE1\u53F7\uFF08pid ${st.pid}\uFF09\u3002\u82E5\u7A97\u53E3\u4ECD\u5728\uFF0C\u8BF7\u7A0D\u5019\u6216\u624B\u52A8\u5173\u95ED\u3002`);
1577
+ });
1578
+ browserCmd.command("status").description("\u67E5\u770B CLI \u6258\u7BA1\u7684 Chrome / CDP \u662F\u5426\u5728\u8FD0\u884C").action(async () => {
1579
+ const st = await loadReconciledDaemonState();
1580
+ if (!st) {
1581
+ console.log("\u72B6\u6001\uFF1A\u672A\u8FD0\u884C\uFF08\u65E0\u6709\u6548 browser-daemon.json\uFF09");
1582
+ return;
1583
+ }
1584
+ console.log("\u72B6\u6001\uFF1A\u8FD0\u884C\u4E2D");
1585
+ console.log(` CDP: ${st.cdpHttp}`);
1586
+ console.log(` port: ${st.port}`);
1587
+ console.log(` pid: ${st.pid}`);
1588
+ console.log(` userDataDir: ${st.userDataDir}`);
1589
+ });
1590
+ var browser_default = browserCmd;
1591
+
1592
+ // src/commands/screenshot.js
1593
+ import fs6 from "node:fs";
1594
+ import path4 from "node:path";
1595
+ import crypto from "node:crypto";
1596
+ import { Command as Command10 } from "commander";
1597
+ import { chromium } from "playwright-core";
1598
+ function parseViewport(s) {
1599
+ if (!s) return null;
1600
+ const m = String(s).trim().match(/^(\d+)\s*[xX]\s*(\d+)$/);
1601
+ if (!m) return null;
1602
+ return { width: Number(m[1]), height: Number(m[2]) };
1603
+ }
1604
+ function assertHttpUrl(u) {
1605
+ let url;
1606
+ try {
1607
+ url = new URL(u);
1608
+ } catch {
1609
+ throw new Error("URL \u65E0\u6548");
1610
+ }
1611
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
1612
+ throw new Error("\u4EC5\u652F\u6301 http(s) URL");
1613
+ }
1614
+ return url.toString();
1615
+ }
1616
+ var INVALID_FILE_CHARS = /[<>:"/\\|?*\u0000-\u001f]/g;
1617
+ function pad2(n) {
1618
+ return String(n).padStart(2, "0");
1619
+ }
1620
+ function localTimestamp(d = /* @__PURE__ */ new Date()) {
1621
+ return `${d.getFullYear()}${pad2(d.getMonth() + 1)}${pad2(d.getDate())}-${pad2(d.getHours())}${pad2(d.getMinutes())}${pad2(d.getSeconds())}`;
1622
+ }
1623
+ function inferScreenshotOutputPath(urlString, cwd = process.cwd()) {
1624
+ const u = new URL(urlString);
1625
+ let slug = u.hostname;
1626
+ if (u.pathname && u.pathname !== "/") {
1627
+ const pathPart = u.pathname.replace(/\/+/g, "-").replace(/^-|-$/g, "");
1628
+ if (pathPart) {
1629
+ slug += `-${pathPart}`;
1630
+ }
1631
+ }
1632
+ slug = slug.replace(INVALID_FILE_CHARS, "_").replace(/_+/g, "_").replace(/^\.+/, "");
1633
+ if (!slug || slug === "_") {
1634
+ slug = "screenshot";
1635
+ }
1636
+ const max = 120;
1637
+ if (slug.length > max) {
1638
+ slug = `${slug.slice(0, max - 10)}_${slug.slice(-8)}`;
1639
+ }
1640
+ const name = `${slug}_${localTimestamp()}.png`;
1641
+ return path4.resolve(cwd, name);
1642
+ }
1643
+ async function sleep(ms) {
1644
+ await new Promise((r) => setTimeout(r, ms));
1645
+ }
1646
+ async function captureWithCdp(cdpHttp, targetUrl, outPath, captureOpts) {
1647
+ const browser = await chromium.connectOverCDP(cdpHttp);
1648
+ try {
1649
+ const context = browser.contexts()[0];
1650
+ if (!context) {
1651
+ throw new Error("\u672A\u627E\u5230\u9ED8\u8BA4 browser context\uFF08CDP \u5F02\u5E38\uFF09");
1652
+ }
1653
+ const page = await context.newPage();
1654
+ try {
1655
+ await page.goto(targetUrl, { waitUntil: "domcontentloaded" });
1656
+ if (captureOpts.waitAfterMs > 0) {
1657
+ await sleep(captureOpts.waitAfterMs);
1658
+ }
1659
+ await page.screenshot({
1660
+ path: outPath,
1661
+ fullPage: captureOpts.fullPage
1662
+ });
1663
+ } finally {
1664
+ await page.close().catch(() => {
1665
+ });
1666
+ }
1667
+ } finally {
1668
+ await browser.close().catch(() => {
1669
+ });
1670
+ }
1671
+ }
1672
+ async function captureEphemeral(targetUrl, outPath, captureOpts) {
1673
+ const chromePath = resolveChromeExecutable();
1674
+ if (!chromePath) {
1675
+ console.error(chromeExecutableHint());
1676
+ process.exit(1);
1677
+ }
1678
+ ensureC456CacheDir();
1679
+ const sessionId = crypto.randomUUID();
1680
+ const userDataDir = path4.join(
1681
+ getC456CacheDir(),
1682
+ "chrome-ephemeral",
1683
+ sessionId
1684
+ );
1685
+ fs6.mkdirSync(userDataDir, { recursive: true });
1686
+ const port = await getFreePort();
1687
+ const { child } = spawnChromeWithCdp(chromePath, { userDataDir, port });
1688
+ let browser;
1689
+ try {
1690
+ await waitForCdpHttp(port);
1691
+ browser = await chromium.connectOverCDP(cdpHttpUrl(port));
1692
+ const context = browser.contexts()[0];
1693
+ if (!context) throw new Error("\u672A\u627E\u5230\u9ED8\u8BA4 browser context");
1694
+ const page = await context.newPage();
1695
+ if (captureOpts.viewport) {
1696
+ await page.setViewportSize(captureOpts.viewport);
1697
+ }
1698
+ await page.goto(targetUrl, { waitUntil: "domcontentloaded" });
1699
+ if (captureOpts.waitAfterMs > 0) {
1700
+ await sleep(captureOpts.waitAfterMs);
1701
+ }
1702
+ await page.screenshot({ path: outPath, fullPage: captureOpts.fullPage });
1703
+ await page.close().catch(() => {
1704
+ });
1705
+ } catch (e) {
1706
+ try {
1707
+ await browser?.close();
1708
+ } catch {
1709
+ }
1710
+ killProcessTree(child.pid);
1711
+ await sleep(400);
1712
+ try {
1713
+ fs6.rmSync(userDataDir, { recursive: true, force: true });
1714
+ } catch {
1715
+ }
1716
+ throw e;
1717
+ }
1718
+ try {
1719
+ await browser?.close();
1720
+ } catch {
1721
+ }
1722
+ killProcessTree(child.pid);
1723
+ await sleep(400);
1724
+ try {
1725
+ fs6.rmSync(userDataDir, { recursive: true, force: true });
1726
+ } catch {
1727
+ }
1728
+ }
1729
+ var screenshotCmd = new Command10("screenshot").name("screenshot").description("\u6253\u5F00 URL \u5E76\u622A\u56FE\uFF08\u9ED8\u8BA4\u590D\u7528 c456 browser start\uFF1B\u5426\u5219\u4E00\u6B21\u6027\u542F\u52A8\u5E76\u5173\u95ED\uFF09").argument("<url>", "\u8981\u6253\u5F00\u7684 http(s) \u5730\u5740").option(
1730
+ "-o, --output <path>",
1731
+ "\u8F93\u51FA\u56FE\u7247\u8DEF\u5F84\uFF08\u5EFA\u8BAE .png\uFF09\uFF1B\u7701\u7565\u5219\u6839\u636E URL \u751F\u6210\u5B89\u5168\u6587\u4EF6\u540D + \u672C\u5730\u65F6\u95F4\u6233\uFF0C\u5199\u5165\u5F53\u524D\u76EE\u5F55"
1732
+ ).option("-f, --full-page", "\u6574\u9875\u957F\u622A\u56FE", false).option(
1733
+ "--viewport <WxH>",
1734
+ "\u89C6\u53E3\u5927\u5C0F\uFF0C\u5982 1280x720\uFF08\u4EC5\u5BF9\u4E00\u6B21\u6027\u4F1A\u8BDD\u751F\u6548\uFF1B\u590D\u7528\u5DF2\u542F\u52A8 Chrome \u65F6\u6CBF\u7528\u7A97\u53E3\u5C3A\u5BF8\uFF09"
1735
+ ).option(
1736
+ "--wait-after-load <ms>",
1737
+ "\u9875\u9762 domcontentloaded \u540E\u518D\u7B49\u5F85\u7684\u6BEB\u79D2\u6570\uFF08\u9ED8\u8BA4 3000\uFF0C\u4FBF\u4E8E JS/\u52A8\u753B\u6E32\u67D3\uFF09\uFF1B\u8BBE\u4E3A 0 \u5219\u4E0D\u989D\u5916\u7B49\u5F85",
1738
+ "3000"
1739
+ ).option(
1740
+ "--no-reuse",
1741
+ "\u4E0D\u590D\u7528 browser start \u7684\u5B9E\u4F8B\uFF0C\u59CB\u7EC8\u5355\u72EC\u8D77 Chrome \u5E76\u5728\u7ED3\u675F\u540E\u5173\u95ED\u3001\u5220\u9664\u4E34\u65F6 profile"
1742
+ ).action(async (urlArg, opts) => {
1743
+ try {
1744
+ const targetUrl = assertHttpUrl(urlArg);
1745
+ const outPath = opts.output != null && String(opts.output).trim() !== "" ? path4.resolve(process.cwd(), String(opts.output).trim()) : inferScreenshotOutputPath(targetUrl, process.cwd());
1746
+ const waitAfterMs = Number.parseInt(String(opts.waitAfterLoad), 10);
1747
+ const wait = Number.isFinite(waitAfterMs) ? Math.max(0, waitAfterMs) : 3e3;
1748
+ const viewport = parseViewport(opts.viewport);
1749
+ const fullPage = Boolean(opts.fullPage);
1750
+ const reuse = opts.reuse !== false;
1751
+ const captureOpts = { fullPage, waitAfterMs: wait, viewport };
1752
+ if (reuse) {
1753
+ const daemon = await loadReconciledDaemonState();
1754
+ if (daemon) {
1755
+ await captureWithCdp(daemon.cdpHttp, targetUrl, outPath, {
1756
+ fullPage,
1757
+ waitAfterMs: wait,
1758
+ viewport: null
1759
+ });
1760
+ console.log(`\u2705 \u5DF2\u622A\u56FE\uFF08\u590D\u7528 CDP ${daemon.cdpHttp}\uFF09\u2192 ${outPath}`);
1761
+ return;
1762
+ }
1763
+ }
1764
+ await captureEphemeral(targetUrl, outPath, captureOpts);
1765
+ console.log(`\u2705 \u5DF2\u622A\u56FE\uFF08\u4E00\u6B21\u6027\u4F1A\u8BDD\uFF0C\u5DF2\u5173\u95ED\uFF09\u2192 ${outPath}`);
1766
+ } catch (e) {
1767
+ console.error(e?.message || e);
1768
+ process.exit(1);
1769
+ }
1770
+ });
1771
+ var screenshot_default = screenshotCmd;
1772
+
1773
+ // src/commands/config.js
1774
+ import fs7 from "node:fs";
1775
+ import { Command as Command11 } from "commander";
1776
+ var GLOBAL_OPT = "-g, --global";
1777
+ var GLOBAL_DESC = "\u8BFB\u5199\u7528\u6237\u5168\u5C40\u914D\u7F6E\uFF08XDG ~/.config/c456\uFF09\uFF0C\u4E0D\u5199\u5165\u5F53\u524D\u9879\u76EE\u7684 .c456-cli";
1778
+ var configCmd = new Command11().name("config").description("\u914D\u7F6E\u7BA1\u7406 - \u8BBE\u7F6E API Key \u548C\u7CFB\u7EDF\u5730\u5740\uFF08\u9ED8\u8BA4\u5199\u5165\u9879\u76EE .c456-cli\uFF1B\u52A0 -g \u5199\u5165\u5168\u5C40\uFF09");
1779
+ configCmd.command("set-key").description("\u8BBE\u7F6E API Key").argument("<token>", "API Key \u4EE4\u724C").option(GLOBAL_OPT, GLOBAL_DESC).action(async (token, opts) => {
1780
+ const global = opts.global === true;
1781
+ await saveConfigPatch({ apiKey: token }, { global });
1782
+ const target = global ? getGlobalConfigPath() : resolveLocalConfigWritePath();
1783
+ console.log(`\u2705 API Key \u5DF2\u4FDD\u5B58\u81F3 ${target}`);
1054
1784
  console.log(` \u63D0\u793A\uFF1A\u4E5F\u53EF\u901A\u8FC7 C456_API_KEY \u73AF\u5883\u53D8\u91CF\u8BBE\u7F6E`);
1055
1785
  });
1056
- 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) => {
1057
- const config = loadConfig();
1058
- config.baseUrl = url;
1059
- saveConfig(config);
1786
+ configCmd.command("set-url").description("\u8BBE\u7F6E C456 \u7CFB\u7EDF\u5730\u5740").argument("<url>", "\u7CFB\u7EDF\u5730\u5740\uFF08\u5982 https://c456.com\uFF09").option(GLOBAL_OPT, GLOBAL_DESC).action(async (url, opts) => {
1787
+ const global = opts.global === true;
1788
+ await saveConfigPatch({ baseUrl: url }, { global });
1789
+ const target = global ? getGlobalConfigPath() : resolveLocalConfigWritePath();
1060
1790
  console.log(`\u2705 \u7CFB\u7EDF\u5730\u5740\u5DF2\u8BBE\u7F6E\u4E3A\uFF1A${url}`);
1791
+ console.log(` \u5DF2\u5199\u5165\uFF1A${target}`);
1061
1792
  console.log(` \u63D0\u793A\uFF1A\u4E5F\u53EF\u901A\u8FC7 C456_URL \u73AF\u5883\u53D8\u91CF\u8BBE\u7F6E`);
1062
1793
  });
1063
- configCmd.command("show").description("\u663E\u793A\u5F53\u524D\u914D\u7F6E").action(() => {
1064
- const config = loadConfig();
1065
- console.log("\u5F53\u524D\u914D\u7F6E\uFF1A");
1066
- console.log(` \u7CFB\u7EDF\u5730\u5740\uFF1A${config.baseUrl || "https://c456.com"}`);
1067
- console.log(` API Key\uFF1A${config.apiKey ? config.apiKey.slice(0, 8) + "..." : "(\u672A\u8BBE\u7F6E)"}`);
1794
+ configCmd.command("show").description("\u663E\u793A\u5F53\u524D\u6709\u6548\u914D\u7F6E\u53CA\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").option(GLOBAL_OPT, "\u4EC5\u67E5\u770B\u5168\u5C40\u914D\u7F6E\u6587\u4EF6\u4E2D\u7684\u5185\u5BB9\uFF08\u4E0D\u5408\u5E76\u9879\u76EE\u8986\u76D6\uFF09").action((opts) => {
1795
+ const globalOnly = opts.global === true;
1796
+ if (globalOnly) {
1797
+ const p = getGlobalConfigPath();
1798
+ let raw = {};
1799
+ try {
1800
+ raw = JSON.parse(fs7.readFileSync(p, "utf-8"));
1801
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) raw = {};
1802
+ } catch {
1803
+ raw = {};
1804
+ }
1805
+ console.log("\u5168\u5C40\u914D\u7F6E\u6587\u4EF6\u5185\u5BB9\uFF1A");
1806
+ console.log(` \u7CFB\u7EDF\u5730\u5740\uFF1A${raw.baseUrl || "(\u672A\u8BBE\u7F6E\uFF0C\u5408\u5E76\u540E\u9ED8\u8BA4 https://c456.com)"}`);
1807
+ console.log(` API Key\uFF1A${raw.apiKey ? String(raw.apiKey).slice(0, 8) + "..." : "(\u672A\u8BBE\u7F6E)"}`);
1808
+ console.log(`
1809
+ \u6587\u4EF6\uFF1A${p}`);
1810
+ return;
1811
+ }
1812
+ const { merged, globalPath, localPath, workspaceRoot } = loadMergedConfigSources();
1813
+ console.log("\u5F53\u524D\u6709\u6548\u914D\u7F6E\uFF08\u9879\u76EE\u8986\u76D6\u5168\u5C40\uFF0C\u73AF\u5883\u53D8\u91CF\u4F18\u5148\u4E8E\u6587\u4EF6\uFF09\uFF1A");
1814
+ console.log(` \u7CFB\u7EDF\u5730\u5740\uFF1A${merged.baseUrl || "https://c456.com"}`);
1815
+ console.log(` API Key\uFF1A${merged.apiKey ? String(merged.apiKey).slice(0, 8) + "..." : "(\u672A\u8BBE\u7F6E)"}`);
1068
1816
  console.log(`
1069
- \u914D\u7F6E\u6587\u4EF6\uFF1A~/.config/c456/config.json`);
1817
+ \u5168\u5C40\u914D\u7F6E\uFF1A${globalPath}`);
1818
+ if (workspaceRoot) {
1819
+ console.log(`\u5DE5\u4F5C\u533A\u6839\uFF1A${workspaceRoot}`);
1820
+ console.log(
1821
+ `\u9879\u76EE\u914D\u7F6E\uFF1A${localPath}${localPath && fs7.existsSync(localPath) ? "" : "\uFF08\u5C1A\u672A\u521B\u5EFA\uFF0C\u6709\u6548\u503C\u6765\u81EA\u5168\u5C40\uFF09"}`
1822
+ );
1823
+ } else {
1824
+ console.log(
1825
+ `\u9879\u76EE\u914D\u7F6E\uFF1A\u672A\u68C0\u6D4B\u5230\u81EA cwd \u5411\u4E0A\u7684 .c456-cli\uFF1B\u65E0 C456_WORKSPACE \u65F6\uFF0C\u9ED8\u8BA4\u53EF\u5199\u5165\u8DEF\u5F84\u4E3A ${resolveLocalConfigWritePath()}`
1826
+ );
1827
+ }
1070
1828
  });
1071
- 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) => {
1072
- const fs = await import("node:fs");
1829
+ configCmd.command("reset").description("\u91CD\u7F6E\u914D\u7F6E\uFF08\u5220\u9664\u5BF9\u5E94\u8303\u56F4\u5185\u7684\u914D\u7F6E\u6587\u4EF6\uFF09").option(GLOBAL_OPT, GLOBAL_DESC).option("-f, --force", "\u5F3A\u5236\u91CD\u7F6E\uFF08\u65E0\u9700\u786E\u8BA4\uFF09").action(async (opts) => {
1830
+ const fs9 = await import("node:fs");
1831
+ const global = opts.global === true;
1832
+ const targetPath = global ? getGlobalConfigPath() : resolveLocalConfigWritePath();
1073
1833
  if (!opts.force) {
1074
1834
  const readline = await import("node:readline");
1075
1835
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1076
1836
  const answer = await new Promise((resolve) => {
1077
- rl.question("\u786E\u8BA4\u5220\u9664\u914D\u7F6E\u6587\u4EF6\uFF1F(y/N): ", (ans) => {
1837
+ rl.question(`\u786E\u8BA4\u5220\u9664\u914D\u7F6E\u6587\u4EF6\uFF1F
1838
+ ${targetPath}
1839
+ (y/N): `, (ans) => {
1078
1840
  rl.close();
1079
1841
  resolve(ans.toLowerCase());
1080
1842
  });
@@ -1084,15 +1846,126 @@ configCmd.command("reset").description("\u91CD\u7F6E\u914D\u7F6E\uFF08\u5220\u96
1084
1846
  return;
1085
1847
  }
1086
1848
  }
1087
- if (fs.existsSync(CONFIG_PATH)) {
1088
- fs.unlinkSync(CONFIG_PATH);
1089
- console.log("\u2705 \u914D\u7F6E\u6587\u4EF6\u5DF2\u5220\u9664");
1849
+ if (fs9.existsSync(targetPath)) {
1850
+ fs9.unlinkSync(targetPath);
1851
+ console.log(`\u2705 \u5DF2\u5220\u9664\uFF1A${targetPath}`);
1090
1852
  } else {
1091
1853
  console.log("\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u65E0\u9700\u5220\u9664");
1092
1854
  }
1093
1855
  });
1094
1856
  var config_default = configCmd;
1095
1857
 
1858
+ // src/commands/skill.js
1859
+ import path5 from "node:path";
1860
+ import { Command as Command12 } from "commander";
1861
+
1862
+ // src/lib/runNpxSkills.js
1863
+ import { spawn as spawn2 } from "node:child_process";
1864
+ function runNpxSkillsAdd(source, opts = {}) {
1865
+ const {
1866
+ cwd = process.cwd(),
1867
+ global = false,
1868
+ agent = "cursor",
1869
+ copy = false,
1870
+ fullDepth = false,
1871
+ skill = "c456-cli"
1872
+ } = opts;
1873
+ const args = ["--yes", "skills", "add", source, "--skill", skill, "-y"];
1874
+ if (global) args.push("-g");
1875
+ if (agent) {
1876
+ args.push("--agent", agent);
1877
+ }
1878
+ if (copy) args.push("--copy");
1879
+ if (fullDepth) args.push("--full-depth");
1880
+ return new Promise((resolve, reject) => {
1881
+ const child = spawn2("npx", args, {
1882
+ cwd,
1883
+ stdio: "inherit",
1884
+ shell: process.platform === "win32",
1885
+ env: process.env
1886
+ });
1887
+ child.on("error", reject);
1888
+ child.on("close", (code, signal) => {
1889
+ if (code === 0) {
1890
+ resolve();
1891
+ return;
1892
+ }
1893
+ const sig = signal ? ` signal=${signal}` : "";
1894
+ reject(new Error(`npx skills add \u9000\u51FA\u7801 ${code ?? "?"}${sig}`));
1895
+ });
1896
+ });
1897
+ }
1898
+
1899
+ // src/commands/skill.js
1900
+ var REMOTE_SOURCES = [
1901
+ { source: "xiaohui-zhangxh/c456-cli", fullDepth: false },
1902
+ { source: "xiaohui-zhangxh/c456", fullDepth: true }
1903
+ ];
1904
+ function buildSkillsOpts(opts) {
1905
+ const agent = String(opts.agent ?? "cursor").trim() || "cursor";
1906
+ return {
1907
+ cwd: path5.resolve(String(opts.cwd || process.cwd())),
1908
+ global: Boolean(opts.global),
1909
+ agent,
1910
+ copy: Boolean(opts.copy)
1911
+ };
1912
+ }
1913
+ async function installSkillFromRemotes(skillId, base) {
1914
+ let lastErr;
1915
+ for (const { source, fullDepth } of REMOTE_SOURCES) {
1916
+ try {
1917
+ console.error(`\u2192 npx skills add ${source} --skill ${skillId} \u2026`);
1918
+ await runNpxSkillsAdd(source, { ...base, fullDepth, skill: skillId });
1919
+ console.log(`\u2705 \u5DF2\u901A\u8FC7 npx skills \u5B89\u88C5 ${skillId}\uFF08\u6765\u6E90\uFF1A${source}\uFF09`);
1920
+ return;
1921
+ } catch (e) {
1922
+ lastErr = e;
1923
+ console.error(` \u5931\u8D25\uFF1A${e?.message || e}`);
1924
+ }
1925
+ }
1926
+ console.error(
1927
+ `\u9519\u8BEF\uFF1A\u65E0\u6CD5\u901A\u8FC7 npx skills \u4ECE GitHub \u5B89\u88C5 ${skillId}\uFF08\u5DF2\u5C1D\u8BD5 xiaohui-zhangxh/c456-cli \u4E0E xiaohui-zhangxh/c456\uFF09\u3002\u8BF7\u68C0\u67E5\u7F51\u7EDC\u3001\u4EE3\u7406\u4E0E\u4ED3\u5E93\u53EF\u8BBF\u95EE\u6027\u3002`
1928
+ );
1929
+ if (lastErr) {
1930
+ console.error(`\u6700\u540E\u9519\u8BEF\uFF1A${lastErr.message || lastErr}`);
1931
+ }
1932
+ process.exit(1);
1933
+ }
1934
+ var skillCmd = new Command12("skill").name("skill").description("\u5B89\u88C5 c456-cli \u6280\u80FD\uFF1A\u5C01\u88C5\u5B98\u65B9 npx skills add\uFF08\u4EC5\u4ECE\u7F51\u7EDC\u62C9\u53D6\uFF09");
1935
+ skillCmd.command("install").description(
1936
+ "npx skills add\uFF1A\u9ED8\u8BA4\u53EA\u88C5 c456-cli\uFF08GitHub \u6E90\u4F9D\u6B21\u5C1D\u8BD5\uFF09\uFF1B\u52A0 --with-wiki \u65F6\u5148\u88C5 karpathy-wiki \u4E0E c456-llm-wiki\uFF0C\u518D\u88C5 c456-cli"
1937
+ ).option(
1938
+ "-C, --cwd <path>",
1939
+ "\u6267\u884C skills \u7684\u5DE5\u4F5C\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF1B\u5177\u4F53\u5199\u5165\u8DEF\u5F84\u7531 skills CLI \u4E0E\u5404 Agent \u7EA6\u5B9A\uFF09"
1940
+ ).option("-g, --global", "\u4F20\u7ED9 skills add\uFF1A\u5B89\u88C5\u5230\u7528\u6237\u7EA7\u6280\u80FD\u76EE\u5F55", false).option(
1941
+ "-a, --agent <names>",
1942
+ "\u4F20\u7ED9 skills add --agent\uFF08\u5982 cursor\u3001claude-code \u7B49\uFF09\uFF0C\u9ED8\u8BA4 cursor",
1943
+ "cursor"
1944
+ ).option("--copy", "\u4F20\u7ED9 skills add\uFF1A\u590D\u5236\u6587\u4EF6\u800C\u975E symlink", false).option(
1945
+ "--with-wiki",
1946
+ "\u79C1\u4EBA\u77E5\u8BC6\u5E93\uFF1A\u5148\u88C5 karpathy-wiki\uFF08baklib-tools/skills\uFF09\uFF0C\u518D\u88C5 c456-llm-wiki \u4E0E c456-cli\uFF08\u5747\u7ECF npx \u4ECE\u7F51\u7EDC\u62C9\u53D6\uFF09",
1947
+ false
1948
+ ).action(async (opts) => {
1949
+ const base = buildSkillsOpts(opts);
1950
+ if (opts.withWiki) {
1951
+ try {
1952
+ console.error("\u2192 npx skills add baklib-tools/skills --skill karpathy-wiki \u2026");
1953
+ await runNpxSkillsAdd("baklib-tools/skills", {
1954
+ ...base,
1955
+ skill: "karpathy-wiki",
1956
+ fullDepth: false
1957
+ });
1958
+ console.log("\u2705 \u5DF2\u5B89\u88C5 karpathy-wiki\uFF08Karpathy Wiki \u76EE\u5F55\u7EA6\u5B9A\uFF09");
1959
+ } catch (e) {
1960
+ console.error(`karpathy-wiki \u5B89\u88C5\u5931\u8D25\uFF1A${e?.message || e}`);
1961
+ process.exit(1);
1962
+ }
1963
+ await installSkillFromRemotes("c456-llm-wiki", base);
1964
+ }
1965
+ await installSkillFromRemotes("c456-cli", base);
1966
+ });
1967
+ var skill_default = skillCmd;
1968
+
1096
1969
  // src/banner.js
1097
1970
  import cfonts from "cfonts";
1098
1971
  var { render } = cfonts;
@@ -1140,13 +2013,150 @@ ${body}
1140
2013
  `;
1141
2014
  }
1142
2015
 
2016
+ // src/lib/npmLatestVersion.js
2017
+ async function fetchNpmLatestVersion(packageName = "c456-cli") {
2018
+ const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
2019
+ const res = await fetch(url, {
2020
+ headers: { Accept: "application/json" },
2021
+ redirect: "follow"
2022
+ });
2023
+ if (!res.ok) {
2024
+ throw new Error(`registry \u54CD\u5E94 ${res.status}`);
2025
+ }
2026
+ const data = await res.json();
2027
+ const v = data?.version;
2028
+ if (!v || typeof v !== "string") {
2029
+ throw new Error("registry \u8FD4\u56DE\u65E0 version \u5B57\u6BB5");
2030
+ }
2031
+ return v;
2032
+ }
2033
+
2034
+ // src/lib/cliUpdateState.js
2035
+ import fs8 from "node:fs";
2036
+ function readRaw() {
2037
+ const p = getVersionCheckStatePath();
2038
+ try {
2039
+ return JSON.parse(fs8.readFileSync(p, "utf8"));
2040
+ } catch {
2041
+ return null;
2042
+ }
2043
+ }
2044
+ function loadUpdateState() {
2045
+ const raw = readRaw();
2046
+ if (!raw || typeof raw !== "object") {
2047
+ return { lastCheckDay: "", pendingNotifyVersion: "" };
2048
+ }
2049
+ return {
2050
+ lastCheckDay: typeof raw.lastCheckDay === "string" ? raw.lastCheckDay : "",
2051
+ pendingNotifyVersion: typeof raw.pendingNotifyVersion === "string" ? raw.pendingNotifyVersion : ""
2052
+ };
2053
+ }
2054
+ function writeUpdateState(state) {
2055
+ ensureC456CacheDir();
2056
+ const p = getVersionCheckStatePath();
2057
+ fs8.writeFileSync(p, `${JSON.stringify(state, null, 2)}
2058
+ `, "utf8");
2059
+ }
2060
+ function patchUpdateState(partial) {
2061
+ const cur = loadUpdateState();
2062
+ writeUpdateState({ ...cur, ...partial });
2063
+ }
2064
+
2065
+ // src/lib/semverGt.js
2066
+ function semverGt(a, b) {
2067
+ const pa = String(a).split(".").map((x) => Number.parseInt(x, 10) || 0);
2068
+ const pb = String(b).split(".").map((x) => Number.parseInt(x, 10) || 0);
2069
+ const n = Math.max(pa.length, pb.length, 3);
2070
+ for (let i = 0; i < n; i += 1) {
2071
+ const x = pa[i] ?? 0;
2072
+ const y = pb[i] ?? 0;
2073
+ if (x > y) return true;
2074
+ if (x < y) return false;
2075
+ }
2076
+ return false;
2077
+ }
2078
+
2079
+ // src/lib/localCalendarDay.js
2080
+ function localCalendarDay() {
2081
+ const d = /* @__PURE__ */ new Date();
2082
+ const y = d.getFullYear();
2083
+ const m = String(d.getMonth() + 1).padStart(2, "0");
2084
+ const day = String(d.getDate()).padStart(2, "0");
2085
+ return `${y}-${m}-${day}`;
2086
+ }
2087
+
2088
+ // src/startup.js
2089
+ function printPendingUpdateNotice(currentVersion) {
2090
+ const st = loadUpdateState();
2091
+ const pending = st.pendingNotifyVersion?.trim();
2092
+ if (!pending) return;
2093
+ if (!semverGt(pending, currentVersion)) {
2094
+ patchUpdateState({ pendingNotifyVersion: "" });
2095
+ return;
2096
+ }
2097
+ console.error("");
2098
+ console.error(
2099
+ `[c456-cli] \u6709\u65B0\u7248\u672C ${pending}\uFF08\u5F53\u524D ${currentVersion}\uFF09\u3002\u53EF\u6267\u884C\uFF1Anpm i -g c456-cli`
2100
+ );
2101
+ console.error("");
2102
+ patchUpdateState({ pendingNotifyVersion: "" });
2103
+ }
2104
+ function scheduleDailyNpmVersionCheck(currentVersion) {
2105
+ const today = localCalendarDay();
2106
+ const st = loadUpdateState();
2107
+ if (st.lastCheckDay === today) return;
2108
+ setImmediate(() => {
2109
+ void (async () => {
2110
+ let latest;
2111
+ try {
2112
+ latest = await fetchNpmLatestVersion("c456-cli");
2113
+ } catch {
2114
+ patchUpdateState({ lastCheckDay: today });
2115
+ return;
2116
+ }
2117
+ const patch = { lastCheckDay: today };
2118
+ if (semverGt(latest, currentVersion)) {
2119
+ patch.pendingNotifyVersion = latest;
2120
+ }
2121
+ patchUpdateState(patch);
2122
+ })();
2123
+ });
2124
+ }
2125
+ function runCliStartupHooks({ currentVersion }) {
2126
+ if (process.env.C456_SKIP_VERSION_CHECK === "1") return;
2127
+ printPendingUpdateNotice(currentVersion);
2128
+ scheduleDailyNpmVersionCheck(currentVersion);
2129
+ }
2130
+
1143
2131
  // src/index.js
1144
- var program = new Command9();
2132
+ var program = new Command13();
1145
2133
  program.name("c456").description("C456 CLI - \u5FEB\u901F\u5185\u5BB9\u5F55\u5165\u4E0E\u6574\u7406\u5DE5\u5177").version(package_default.version);
1146
- program.addHelpText("before", () => getHelpBanner());
2134
+ program.addHelpText("before", () => {
2135
+ const banner = getHelpBanner();
2136
+ const versionLine = `c456-cli ${package_default.version}`;
2137
+ if (!banner) {
2138
+ return `
2139
+ ${versionLine}
2140
+ `;
2141
+ }
2142
+ return `${banner}
2143
+ ${versionLine}
2144
+ `;
2145
+ });
2146
+ program.exitOverride((err) => {
2147
+ if (err.code === "commander.executeSubCommandAsync") {
2148
+ process.exit(err.exitCode ?? 1);
2149
+ return;
2150
+ }
2151
+ if (err.code === "commander.help" && err.exitCode === 1) {
2152
+ process.exit(0);
2153
+ return;
2154
+ }
2155
+ process.exit(err.exitCode ?? 1);
2156
+ });
1147
2157
  program.option(
1148
2158
  "-B, --base-url <url>",
1149
- "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"
2159
+ "C456 \u7AD9\u70B9\u6839\u5730\u5740\uFF1B\u672A\u4F20\u5219\u4F7F\u7528 C456_URL\uFF0C\u5176\u6B21\u5408\u5E76\u8BFB\u53D6 ~/.config/c456 \u4E0E\u81EA cwd \u5411\u4E0A\u627E\u5230\u7684 .c456-cli/config.json\uFF0C\u9ED8\u8BA4 https://c456.com"
1150
2160
  );
1151
2161
  program.addCommand(signal_default);
1152
2162
  program.addCommand(tool_default);
@@ -1155,8 +2165,12 @@ program.addCommand(fetch_default);
1155
2165
  program.addCommand(search_default);
1156
2166
  program.addCommand(playbook_default);
1157
2167
  program.addCommand(walkthrough_default);
2168
+ program.addCommand(asset_default);
2169
+ program.addCommand(browser_default);
2170
+ program.addCommand(screenshot_default);
1158
2171
  program.addCommand(intake_default);
1159
2172
  program.addCommand(config_default);
2173
+ program.addCommand(skill_default);
1160
2174
  program.on("--help", () => {
1161
2175
  console.log("\n\u793A\u4F8B:");
1162
2176
  console.log(" # \u914D\u7F6E API Key");
@@ -1168,8 +2182,14 @@ program.on("--help", () => {
1168
2182
  console.log(" # \u641C\u7D22\u6536\u5F55");
1169
2183
  console.log(' c456 search signals -q "AI agent"');
1170
2184
  console.log("");
2185
+ console.log(" # \u5B89\u88C5 Agent \u6280\u80FD\uFF08\u5C01\u88C5 npx skills add\uFF1B\u77E5\u8BC6\u5E93\u4E00\u6761\u88C5\u9F50\u8BF7\u52A0 --with-wiki\uFF09");
2186
+ console.log(" c456 skill install --with-wiki");
2187
+ console.log("");
1171
2188
  console.log("\u73AF\u5883\u53D8\u91CF:");
1172
2189
  console.log(" C456_URL - \u7AD9\u70B9\u6839\u5730\u5740\uFF08\u4E0E -B / --base-url \u4E00\u81F4\uFF09");
1173
2190
  console.log(" C456_API_KEY - API Key");
2191
+ console.log(" C456_WORKSPACE - \u5DE5\u4F5C\u533A\u6839\u76EE\u5F55\uFF08\u7EDD\u5BF9\u8DEF\u5F84\uFF09\uFF0C\u5176\u4E0B .c456-cli/config.json \u8986\u76D6\u5168\u5C40\u914D\u7F6E");
2192
+ console.log(" C456_SKIP_VERSION_CHECK=1 - \u8DF3\u8FC7\u6BCF\u65E5 npm \u7248\u672C\u68C0\u67E5\u4E0E\u66F4\u65B0\u63D0\u793A");
1174
2193
  });
2194
+ runCliStartupHooks({ currentVersion: package_default.version });
1175
2195
  program.parse();