c456-cli 0.3.0 → 0.5.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/README.md +69 -2
- package/dist/index.js +1014 -69
- package/package.json +3 -4
- package/docs/private-knowledge-base.md +0 -129
- package/skills/c456-cli/SKILL.md +0 -118
- package/skills/c456-cli/references/content-syntax-kramdown.md +0 -80
- package/skills/c456-cli/references/douyin-channel-intake.md +0 -65
- package/skills/c456-cli/references/intake-profile-data-json.md +0 -164
- package/skills/c456-cli/references/media-library-and-icons.md +0 -46
- package/skills/c456-llm-wiki/SKILL.md +0 -242
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
|
|
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.
|
|
9
|
+
version: "0.5.0",
|
|
10
10
|
description: "C456 CLI - \u5185\u5BB9\u5F55\u5165\u4E0E\u6574\u7406\u5DE5\u5177",
|
|
11
11
|
type: "module",
|
|
12
12
|
bin: {
|
|
@@ -14,8 +14,6 @@ var package_default = {
|
|
|
14
14
|
},
|
|
15
15
|
files: [
|
|
16
16
|
"dist",
|
|
17
|
-
"docs",
|
|
18
|
-
"skills",
|
|
19
17
|
"README.md"
|
|
20
18
|
],
|
|
21
19
|
scripts: {
|
|
@@ -25,7 +23,8 @@ var package_default = {
|
|
|
25
23
|
dependencies: {
|
|
26
24
|
cfonts: "^3.3.1",
|
|
27
25
|
commander: "^12.1.0",
|
|
28
|
-
open: "^10.1.0"
|
|
26
|
+
open: "^10.1.0",
|
|
27
|
+
"playwright-core": "^1.50.0"
|
|
29
28
|
},
|
|
30
29
|
devDependencies: {
|
|
31
30
|
esbuild: "^0.24.0"
|
|
@@ -51,36 +50,107 @@ var package_default = {
|
|
|
51
50
|
// src/commands/intake.js
|
|
52
51
|
import { Command } from "commander";
|
|
53
52
|
|
|
54
|
-
// src/
|
|
55
|
-
import
|
|
56
|
-
import
|
|
57
|
-
import
|
|
58
|
-
var
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
// src/lib/workspaceConfig.js
|
|
54
|
+
import fs from "node:fs";
|
|
55
|
+
import os from "node:os";
|
|
56
|
+
import path from "node:path";
|
|
57
|
+
var CLI_DIR_NAME = ".c456-cli";
|
|
58
|
+
function getGlobalConfigDir() {
|
|
59
|
+
const home = os.homedir();
|
|
60
|
+
const base = process.env.XDG_CONFIG_HOME || path.join(home, ".config");
|
|
61
|
+
return path.join(base, "c456");
|
|
62
|
+
}
|
|
63
|
+
function getGlobalConfigPath() {
|
|
64
|
+
return path.join(getGlobalConfigDir(), "config.json");
|
|
65
|
+
}
|
|
66
|
+
function findWorkspaceRootWalk(startDir) {
|
|
67
|
+
let cur = path.resolve(startDir);
|
|
68
|
+
for (; ; ) {
|
|
69
|
+
const marker = path.join(cur, CLI_DIR_NAME);
|
|
70
|
+
try {
|
|
71
|
+
if (fs.existsSync(marker) && fs.statSync(marker).isDirectory()) {
|
|
72
|
+
return cur;
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
const parent = path.dirname(cur);
|
|
77
|
+
if (parent === cur) return null;
|
|
78
|
+
cur = parent;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function getWorkspaceRoot() {
|
|
82
|
+
const raw = process.env.C456_WORKSPACE?.trim();
|
|
83
|
+
if (raw) {
|
|
84
|
+
return path.resolve(raw);
|
|
85
|
+
}
|
|
86
|
+
return findWorkspaceRootWalk(process.cwd());
|
|
87
|
+
}
|
|
88
|
+
function getProjectConfigPath(workspaceRoot) {
|
|
89
|
+
return path.join(workspaceRoot, CLI_DIR_NAME, "config.json");
|
|
90
|
+
}
|
|
91
|
+
function resolveLocalConfigWritePath() {
|
|
92
|
+
const root = getWorkspaceRoot();
|
|
93
|
+
if (root) return getProjectConfigPath(root);
|
|
94
|
+
return path.join(process.cwd(), CLI_DIR_NAME, "config.json");
|
|
95
|
+
}
|
|
96
|
+
function loadMergedConfigSources() {
|
|
97
|
+
const globalPath = getGlobalConfigPath();
|
|
98
|
+
const globalCfg = readJsonFile(globalPath);
|
|
99
|
+
const workspaceRoot = getWorkspaceRoot();
|
|
100
|
+
if (!workspaceRoot) {
|
|
101
|
+
return { merged: { ...globalCfg }, globalPath, localPath: null, workspaceRoot: null };
|
|
102
|
+
}
|
|
103
|
+
const localPath = getProjectConfigPath(workspaceRoot);
|
|
104
|
+
if (!fs.existsSync(localPath)) {
|
|
105
|
+
return { merged: { ...globalCfg }, globalPath, localPath, workspaceRoot };
|
|
106
|
+
}
|
|
107
|
+
const localCfg = readJsonFile(localPath);
|
|
108
|
+
return {
|
|
109
|
+
merged: { ...globalCfg, ...localCfg },
|
|
110
|
+
globalPath,
|
|
111
|
+
localPath,
|
|
112
|
+
workspaceRoot
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function readJsonFile(filePath) {
|
|
62
116
|
try {
|
|
63
|
-
const raw = readFileSync(
|
|
64
|
-
|
|
117
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
118
|
+
const o = JSON.parse(raw);
|
|
119
|
+
return o && typeof o === "object" && !Array.isArray(o) ? o : {};
|
|
65
120
|
} catch {
|
|
66
121
|
return {};
|
|
67
122
|
}
|
|
68
123
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
124
|
+
|
|
125
|
+
// src/client.js
|
|
126
|
+
var CONFIG_PATH = getGlobalConfigPath();
|
|
127
|
+
function loadConfig() {
|
|
128
|
+
return loadMergedConfigSources().merged;
|
|
129
|
+
}
|
|
130
|
+
async function saveConfigPatch(patch, options = {}) {
|
|
131
|
+
const fs9 = await import("node:fs");
|
|
132
|
+
const pathMod = await import("node:path");
|
|
133
|
+
const global = options.global === true;
|
|
134
|
+
const targetPath = global ? getGlobalConfigPath() : resolveLocalConfigWritePath();
|
|
135
|
+
let existing = {};
|
|
136
|
+
try {
|
|
137
|
+
const raw = fs9.readFileSync(targetPath, "utf-8");
|
|
138
|
+
const o = JSON.parse(raw);
|
|
139
|
+
if (o && typeof o === "object" && !Array.isArray(o)) existing = o;
|
|
140
|
+
} catch {
|
|
75
141
|
}
|
|
76
|
-
|
|
142
|
+
const next = { ...existing, ...patch };
|
|
143
|
+
fs9.mkdirSync(pathMod.dirname(targetPath), { recursive: true });
|
|
144
|
+
fs9.writeFileSync(targetPath, JSON.stringify(next, null, 2), "utf-8");
|
|
77
145
|
}
|
|
78
146
|
function getApiKey() {
|
|
79
|
-
|
|
147
|
+
const v = process.env.C456_API_KEY || loadConfig().apiKey;
|
|
148
|
+
return v !== void 0 && v !== null && String(v).trim() !== "" ? String(v) : null;
|
|
80
149
|
}
|
|
81
150
|
function getBaseUrl(cliBaseUrl) {
|
|
82
151
|
const fromCli = cliBaseUrl !== void 0 && cliBaseUrl !== null && String(cliBaseUrl).trim() !== "" ? String(cliBaseUrl).replace(/\/+$/, "") : null;
|
|
83
|
-
const
|
|
152
|
+
const fromFile = loadConfig().baseUrl;
|
|
153
|
+
const raw = fromCli || process.env.C456_URL || fromFile || "https://c456.com";
|
|
84
154
|
return String(raw).replace(/\/+$/, "");
|
|
85
155
|
}
|
|
86
156
|
function getRootCommand(cmd) {
|
|
@@ -129,8 +199,8 @@ var ApiClient = class {
|
|
|
129
199
|
/**
|
|
130
200
|
* GET 请求
|
|
131
201
|
*/
|
|
132
|
-
async get(
|
|
133
|
-
const url = new URL(`${this.baseUrl}/api/v1${
|
|
202
|
+
async get(path6, params = {}) {
|
|
203
|
+
const url = new URL(`${this.baseUrl}/api/v1${path6}`);
|
|
134
204
|
Object.entries(params).forEach(([k, v]) => {
|
|
135
205
|
if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
|
|
136
206
|
});
|
|
@@ -140,8 +210,8 @@ var ApiClient = class {
|
|
|
140
210
|
/**
|
|
141
211
|
* POST 请求
|
|
142
212
|
*/
|
|
143
|
-
async post(
|
|
144
|
-
const url = `${this.baseUrl}/api/v1${
|
|
213
|
+
async post(path6, body = {}) {
|
|
214
|
+
const url = `${this.baseUrl}/api/v1${path6}`;
|
|
145
215
|
const res = await fetch(url, {
|
|
146
216
|
method: "POST",
|
|
147
217
|
headers: this.headers(),
|
|
@@ -155,17 +225,17 @@ var ApiClient = class {
|
|
|
155
225
|
* @param {Record<string, any>} fields
|
|
156
226
|
* @param {{ fieldName: string, filePath: string, filename?: string, contentType?: string } | null} file
|
|
157
227
|
*/
|
|
158
|
-
async postMultipart(
|
|
159
|
-
const url = `${this.baseUrl}/api/v1${
|
|
228
|
+
async postMultipart(path6, fields = {}, file = null) {
|
|
229
|
+
const url = `${this.baseUrl}/api/v1${path6}`;
|
|
160
230
|
const form = new FormData();
|
|
161
231
|
Object.entries(fields || {}).forEach(([k, v]) => {
|
|
162
232
|
if (v === void 0 || v === null) return;
|
|
163
233
|
form.append(k, String(v));
|
|
164
234
|
});
|
|
165
235
|
if (file && file.filePath) {
|
|
166
|
-
const
|
|
236
|
+
const fs9 = await import("node:fs");
|
|
167
237
|
const pathMod = await import("node:path");
|
|
168
|
-
const bytes =
|
|
238
|
+
const bytes = fs9.readFileSync(file.filePath);
|
|
169
239
|
const blob = new Blob([bytes], { type: file.contentType || "application/octet-stream" });
|
|
170
240
|
const name = file.filename || pathMod.basename(file.filePath);
|
|
171
241
|
form.append(file.fieldName, blob, name);
|
|
@@ -182,8 +252,8 @@ var ApiClient = class {
|
|
|
182
252
|
/**
|
|
183
253
|
* PATCH 请求
|
|
184
254
|
*/
|
|
185
|
-
async patch(
|
|
186
|
-
const url = `${this.baseUrl}/api/v1${
|
|
255
|
+
async patch(path6, body = {}) {
|
|
256
|
+
const url = `${this.baseUrl}/api/v1${path6}`;
|
|
187
257
|
const res = await fetch(url, {
|
|
188
258
|
method: "PATCH",
|
|
189
259
|
headers: this.headers(),
|
|
@@ -197,17 +267,17 @@ var ApiClient = class {
|
|
|
197
267
|
* @param {Record<string, any>} fields
|
|
198
268
|
* @param {{ fieldName: string, filePath: string, filename?: string, contentType?: string } | null} file
|
|
199
269
|
*/
|
|
200
|
-
async patchMultipart(
|
|
201
|
-
const url = `${this.baseUrl}/api/v1${
|
|
270
|
+
async patchMultipart(path6, fields = {}, file = null) {
|
|
271
|
+
const url = `${this.baseUrl}/api/v1${path6}`;
|
|
202
272
|
const form = new FormData();
|
|
203
273
|
Object.entries(fields || {}).forEach(([k, v]) => {
|
|
204
274
|
if (v === void 0 || v === null) return;
|
|
205
275
|
form.append(k, String(v));
|
|
206
276
|
});
|
|
207
277
|
if (file && file.filePath) {
|
|
208
|
-
const
|
|
278
|
+
const fs9 = await import("node:fs");
|
|
209
279
|
const pathMod = await import("node:path");
|
|
210
|
-
const bytes =
|
|
280
|
+
const bytes = fs9.readFileSync(file.filePath);
|
|
211
281
|
const blob = new Blob([bytes], { type: file.contentType || "application/octet-stream" });
|
|
212
282
|
const name = file.filename || pathMod.basename(file.filePath);
|
|
213
283
|
form.append(file.fieldName, blob, name);
|
|
@@ -224,8 +294,8 @@ var ApiClient = class {
|
|
|
224
294
|
/**
|
|
225
295
|
* DELETE 请求
|
|
226
296
|
*/
|
|
227
|
-
async delete(
|
|
228
|
-
const url = `${this.baseUrl}/api/v1${
|
|
297
|
+
async delete(path6) {
|
|
298
|
+
const url = `${this.baseUrl}/api/v1${path6}`;
|
|
229
299
|
const res = await fetch(url, {
|
|
230
300
|
method: "DELETE",
|
|
231
301
|
headers: this.headers()
|
|
@@ -273,13 +343,13 @@ function resolveApi(cmd) {
|
|
|
273
343
|
}
|
|
274
344
|
|
|
275
345
|
// src/textFile.js
|
|
276
|
-
import { readFileSync
|
|
277
|
-
function readTextFile(
|
|
346
|
+
import { readFileSync } from "node:fs";
|
|
347
|
+
function readTextFile(path6) {
|
|
278
348
|
try {
|
|
279
|
-
return
|
|
349
|
+
return readFileSync(path6, "utf-8");
|
|
280
350
|
} catch (e) {
|
|
281
351
|
const msg = e instanceof Error ? e.message : String(e);
|
|
282
|
-
console.error(`\u9519\u8BEF\uFF1A\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6\uFF1A${
|
|
352
|
+
console.error(`\u9519\u8BEF\uFF1A\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6\uFF1A${path6}`);
|
|
283
353
|
console.error(msg);
|
|
284
354
|
process.exit(1);
|
|
285
355
|
}
|
|
@@ -1192,38 +1262,678 @@ assetCmd.command("fingerprint").description("\u5BF9\u6B63\u6587\u505A\u4E0E\u670
|
|
|
1192
1262
|
});
|
|
1193
1263
|
var asset_default = assetCmd;
|
|
1194
1264
|
|
|
1195
|
-
// src/commands/
|
|
1265
|
+
// src/commands/browser.js
|
|
1266
|
+
import fs5 from "node:fs";
|
|
1196
1267
|
import { Command as Command9 } from "commander";
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1268
|
+
|
|
1269
|
+
// src/lib/chromeExecutable.js
|
|
1270
|
+
import fs2 from "node:fs";
|
|
1271
|
+
import path2 from "node:path";
|
|
1272
|
+
import process2 from "node:process";
|
|
1273
|
+
function exists(p) {
|
|
1274
|
+
try {
|
|
1275
|
+
fs2.accessSync(p, fs2.constants.X_OK);
|
|
1276
|
+
return true;
|
|
1277
|
+
} catch {
|
|
1278
|
+
return false;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
function resolveChromeExecutable() {
|
|
1282
|
+
const env = process2.env.CHROME_PATH?.trim();
|
|
1283
|
+
if (env && exists(env)) return env;
|
|
1284
|
+
const platform = process2.platform;
|
|
1285
|
+
const candidates = [];
|
|
1286
|
+
if (platform === "darwin") {
|
|
1287
|
+
candidates.push(
|
|
1288
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
1289
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
1290
|
+
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary"
|
|
1291
|
+
);
|
|
1292
|
+
} else if (platform === "win32") {
|
|
1293
|
+
const pf = process2.env["PROGRAMFILES"] || "C:\\\\Program Files";
|
|
1294
|
+
const pf86 = process2.env["PROGRAMFILES(X86)"] || "C:\\\\Program Files (x86)";
|
|
1295
|
+
candidates.push(
|
|
1296
|
+
path2.join(pf, "Google", "Chrome", "Application", "chrome.exe"),
|
|
1297
|
+
path2.join(pf86, "Google", "Chrome", "Application", "chrome.exe")
|
|
1298
|
+
);
|
|
1299
|
+
} else {
|
|
1300
|
+
candidates.push(
|
|
1301
|
+
"/usr/bin/google-chrome-stable",
|
|
1302
|
+
"/usr/bin/google-chrome",
|
|
1303
|
+
"/usr/bin/chromium-browser",
|
|
1304
|
+
"/usr/bin/chromium",
|
|
1305
|
+
"/snap/bin/chromium"
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
for (const p of candidates) {
|
|
1309
|
+
if (exists(p)) return p;
|
|
1310
|
+
}
|
|
1311
|
+
return null;
|
|
1312
|
+
}
|
|
1313
|
+
function chromeExecutableHint() {
|
|
1314
|
+
return [
|
|
1315
|
+
"\u672A\u627E\u5230 Chrome / Chromium \u53EF\u6267\u884C\u6587\u4EF6\u3002",
|
|
1316
|
+
"\u8BF7\u5B89\u88C5 Google Chrome\uFF0C\u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF CHROME_PATH \u6307\u5411\u53EF\u6267\u884C\u6587\u4EF6\u3002",
|
|
1317
|
+
"\uFF08\u53EF\u9009\uFF09\u5728\u65E0\u7CFB\u7EDF Chrome \u7684\u73AF\u5883\u53EF\u5B89\u88C5 Playwright \u81EA\u5E26 Chromium\uFF1A",
|
|
1318
|
+
" npx playwright install chromium",
|
|
1319
|
+
"\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"
|
|
1320
|
+
].join("\n");
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// src/lib/chromeCdp.js
|
|
1324
|
+
import http from "node:http";
|
|
1325
|
+
import { spawn } from "node:child_process";
|
|
1326
|
+
import process3 from "node:process";
|
|
1327
|
+
function cdpHttpUrl(port) {
|
|
1328
|
+
return `http://127.0.0.1:${port}`;
|
|
1329
|
+
}
|
|
1330
|
+
async function waitForCdpHttp(port, timeoutMs = 45e3) {
|
|
1331
|
+
const url = `${cdpHttpUrl(port)}/json/version`;
|
|
1332
|
+
const start = Date.now();
|
|
1333
|
+
let lastErr;
|
|
1334
|
+
while (Date.now() - start < timeoutMs) {
|
|
1335
|
+
try {
|
|
1336
|
+
const body = await httpGet(url);
|
|
1337
|
+
if (body && body.includes("webSocketDebuggerUrl")) {
|
|
1338
|
+
return JSON.parse(body);
|
|
1339
|
+
}
|
|
1340
|
+
} catch (e) {
|
|
1341
|
+
lastErr = e;
|
|
1342
|
+
}
|
|
1343
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
1344
|
+
}
|
|
1345
|
+
throw new Error(
|
|
1346
|
+
`\u7B49\u5F85 Chrome DevTools \u7AEF\u53E3 ${port} \u8D85\u65F6${lastErr ? `\uFF1A${lastErr.message}` : ""}`
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
function httpGet(url) {
|
|
1350
|
+
return new Promise((resolve, reject) => {
|
|
1351
|
+
const req = http.get(url, (res) => {
|
|
1352
|
+
let data = "";
|
|
1353
|
+
res.setEncoding("utf8");
|
|
1354
|
+
res.on("data", (c) => {
|
|
1355
|
+
data += c;
|
|
1356
|
+
});
|
|
1357
|
+
res.on("end", () => resolve(data));
|
|
1358
|
+
});
|
|
1359
|
+
req.on("error", reject);
|
|
1360
|
+
req.setTimeout(2e3, () => {
|
|
1361
|
+
req.destroy();
|
|
1362
|
+
reject(new Error("\u8BF7\u6C42\u8D85\u65F6"));
|
|
1363
|
+
});
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
function spawnChromeWithCdp(chromePath, { userDataDir, port, extraArgs = [] }) {
|
|
1367
|
+
const args = [
|
|
1368
|
+
`--user-data-dir=${userDataDir}`,
|
|
1369
|
+
`--remote-debugging-port=${port}`,
|
|
1370
|
+
"--remote-allow-origins=*",
|
|
1371
|
+
"--no-first-run",
|
|
1372
|
+
"--no-default-browser-check",
|
|
1373
|
+
...extraArgs
|
|
1374
|
+
];
|
|
1375
|
+
const child = spawn(chromePath, args, {
|
|
1376
|
+
detached: true,
|
|
1377
|
+
stdio: "ignore",
|
|
1378
|
+
env: { ...process3.env }
|
|
1379
|
+
});
|
|
1380
|
+
child.unref();
|
|
1381
|
+
return { child, port };
|
|
1382
|
+
}
|
|
1383
|
+
function killProcessTree(pid) {
|
|
1384
|
+
if (!pid || pid <= 0) return;
|
|
1385
|
+
if (process3.platform === "win32") {
|
|
1386
|
+
const killer = spawn("taskkill", ["/PID", String(pid), "/T", "/F"], {
|
|
1387
|
+
stdio: "ignore",
|
|
1388
|
+
detached: true
|
|
1389
|
+
});
|
|
1390
|
+
killer.unref();
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
try {
|
|
1394
|
+
process3.kill(-pid, "SIGTERM");
|
|
1395
|
+
} catch {
|
|
1396
|
+
try {
|
|
1397
|
+
process3.kill(pid, "SIGTERM");
|
|
1398
|
+
} catch {
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// src/lib/freePort.js
|
|
1404
|
+
import net from "node:net";
|
|
1405
|
+
function getFreePort() {
|
|
1406
|
+
return new Promise((resolve, reject) => {
|
|
1407
|
+
const s = net.createServer();
|
|
1408
|
+
s.unref();
|
|
1409
|
+
s.on("error", reject);
|
|
1410
|
+
s.listen(0, "127.0.0.1", () => {
|
|
1411
|
+
const addr = s.address();
|
|
1412
|
+
const port = typeof addr === "object" && addr ? addr.port : null;
|
|
1413
|
+
s.close(() => {
|
|
1414
|
+
if (port) resolve(port);
|
|
1415
|
+
else reject(new Error("\u65E0\u6CD5\u5206\u914D\u672C\u5730\u7AEF\u53E3"));
|
|
1416
|
+
});
|
|
1417
|
+
});
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
function isPortListening(port, host = "127.0.0.1") {
|
|
1421
|
+
return new Promise((resolve) => {
|
|
1422
|
+
const s = net.createConnection({ port, host }, () => {
|
|
1423
|
+
s.destroy();
|
|
1424
|
+
resolve(true);
|
|
1425
|
+
});
|
|
1426
|
+
s.on("error", () => resolve(false));
|
|
1427
|
+
s.setTimeout(800, () => {
|
|
1428
|
+
s.destroy();
|
|
1429
|
+
resolve(false);
|
|
1430
|
+
});
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
// src/lib/c456Cache.js
|
|
1435
|
+
import fs3 from "node:fs";
|
|
1436
|
+
import os2 from "node:os";
|
|
1437
|
+
import path3 from "node:path";
|
|
1438
|
+
function getC456CacheDir() {
|
|
1439
|
+
const home = os2.homedir();
|
|
1440
|
+
const xdgCache = process.env.XDG_CACHE_HOME || path3.join(home, ".cache");
|
|
1441
|
+
return path3.join(xdgCache, "c456-cli");
|
|
1442
|
+
}
|
|
1443
|
+
function ensureC456CacheDir() {
|
|
1444
|
+
const dir = getC456CacheDir();
|
|
1445
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
1446
|
+
return dir;
|
|
1447
|
+
}
|
|
1448
|
+
function getPersistentChromeProfileDir() {
|
|
1449
|
+
return path3.join(getC456CacheDir(), "chrome-profile");
|
|
1450
|
+
}
|
|
1451
|
+
function getBrowserDaemonStatePath() {
|
|
1452
|
+
return path3.join(getC456CacheDir(), "browser-daemon.json");
|
|
1453
|
+
}
|
|
1454
|
+
function getVersionCheckStatePath() {
|
|
1455
|
+
return path3.join(getC456CacheDir(), "version-check-state.json");
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// src/lib/browserDaemon.js
|
|
1459
|
+
import fs4 from "node:fs";
|
|
1460
|
+
import process4 from "node:process";
|
|
1461
|
+
function readJson(path6) {
|
|
1462
|
+
try {
|
|
1463
|
+
return JSON.parse(fs4.readFileSync(path6, "utf8"));
|
|
1464
|
+
} catch {
|
|
1465
|
+
return null;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
function isPidAlive(pid) {
|
|
1469
|
+
if (!pid || pid <= 0) return false;
|
|
1470
|
+
try {
|
|
1471
|
+
process4.kill(pid, 0);
|
|
1472
|
+
return true;
|
|
1473
|
+
} catch {
|
|
1474
|
+
return false;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
async function loadReconciledDaemonState() {
|
|
1478
|
+
const path6 = getBrowserDaemonStatePath();
|
|
1479
|
+
const st = readJson(path6);
|
|
1480
|
+
if (!st?.port || !st?.pid) return null;
|
|
1481
|
+
const pidOk = isPidAlive(st.pid);
|
|
1482
|
+
const portOk = await isPortListening(st.port);
|
|
1483
|
+
if (!pidOk || !portOk) {
|
|
1484
|
+
try {
|
|
1485
|
+
fs4.unlinkSync(path6);
|
|
1486
|
+
} catch {
|
|
1487
|
+
}
|
|
1488
|
+
return null;
|
|
1489
|
+
}
|
|
1490
|
+
return { ...st, statePath: path6, cdpHttp: cdpHttpUrl(st.port) };
|
|
1491
|
+
}
|
|
1492
|
+
function writeDaemonState(state) {
|
|
1493
|
+
ensureC456CacheDir();
|
|
1494
|
+
const path6 = getBrowserDaemonStatePath();
|
|
1495
|
+
fs4.writeFileSync(path6, `${JSON.stringify(state, null, 2)}
|
|
1496
|
+
`, "utf8");
|
|
1497
|
+
}
|
|
1498
|
+
function clearDaemonStateFile() {
|
|
1499
|
+
try {
|
|
1500
|
+
fs4.unlinkSync(getBrowserDaemonStatePath());
|
|
1501
|
+
} catch {
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
// src/commands/browser.js
|
|
1506
|
+
function requireChrome() {
|
|
1507
|
+
const exe = resolveChromeExecutable();
|
|
1508
|
+
if (!exe) {
|
|
1509
|
+
console.error(chromeExecutableHint());
|
|
1510
|
+
process.exit(1);
|
|
1511
|
+
}
|
|
1512
|
+
return exe;
|
|
1513
|
+
}
|
|
1514
|
+
var browserCmd = new Command9("browser").name("browser").description(
|
|
1515
|
+
"\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"
|
|
1516
|
+
);
|
|
1517
|
+
browserCmd.command("start").description("\u542F\u52A8 Chrome\uFF08\u82E5\u5DF2\u5728\u8FD0\u884C\u5219\u6253\u5370\u73B0\u6709 CDP \u5730\u5740\uFF09").option(
|
|
1518
|
+
"-p, --port <n>",
|
|
1519
|
+
"remote-debugging-port\uFF1B\u9ED8\u8BA4\u81EA\u52A8\u9009\u62E9\u672C\u673A\u53EF\u7528\u7AEF\u53E3"
|
|
1520
|
+
).action(async (opts) => {
|
|
1521
|
+
const existing = await loadReconciledDaemonState();
|
|
1522
|
+
if (existing) {
|
|
1523
|
+
console.log("Chrome \u5DF2\u5728\u8FD0\u884C\uFF08CLI \u6258\u7BA1\uFF09\u3002");
|
|
1524
|
+
console.log(` CDP: ${existing.cdpHttp}`);
|
|
1525
|
+
console.log(` port: ${existing.port}`);
|
|
1526
|
+
console.log(` pid: ${existing.pid}`);
|
|
1527
|
+
console.log(` userDataDir: ${existing.userDataDir}`);
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
const chromePath = requireChrome();
|
|
1531
|
+
ensureC456CacheDir();
|
|
1532
|
+
const userDataDir = getPersistentChromeProfileDir();
|
|
1533
|
+
fs5.mkdirSync(userDataDir, { recursive: true });
|
|
1534
|
+
const port = opts.port != null && opts.port !== "" ? Number.parseInt(String(opts.port), 10) : await getFreePort();
|
|
1535
|
+
if (!Number.isFinite(port) || port <= 0 || port > 65535) {
|
|
1536
|
+
console.error("\u9519\u8BEF\uFF1A--port \u65E0\u6548");
|
|
1537
|
+
process.exit(1);
|
|
1538
|
+
}
|
|
1539
|
+
const { child } = spawnChromeWithCdp(chromePath, { userDataDir, port });
|
|
1540
|
+
try {
|
|
1541
|
+
await waitForCdpHttp(port);
|
|
1542
|
+
writeDaemonState({
|
|
1543
|
+
port,
|
|
1544
|
+
pid: child.pid,
|
|
1545
|
+
userDataDir,
|
|
1546
|
+
cdpHttp: cdpHttpUrl(port),
|
|
1547
|
+
mode: "persistent",
|
|
1548
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1549
|
+
});
|
|
1550
|
+
console.log("\u2705 \u5DF2\u542F\u52A8\u6709\u5934 Chrome\uFF08\u6301\u4E45 profile\uFF0C\u53EF\u5728\u6B64\u7A97\u53E3\u767B\u5F55\uFF09\u3002");
|
|
1551
|
+
console.log(` CDP: ${cdpHttpUrl(port)}`);
|
|
1552
|
+
console.log(` port: ${port}`);
|
|
1553
|
+
console.log(` pid: ${child.pid}`);
|
|
1554
|
+
console.log(` userDataDir: ${userDataDir}`);
|
|
1555
|
+
console.log("");
|
|
1556
|
+
console.log("\u7ED3\u675F\u8BF7\u6267\u884C\uFF1Ac456 browser stop");
|
|
1557
|
+
console.log("\u622A\u56FE\u53EF\u6267\u884C\uFF1Ac456 screenshot <url> -o <\u6587\u4EF6.png>\uFF08\u5C06\u590D\u7528\u672C\u5B9E\u4F8B\uFF09");
|
|
1558
|
+
} catch (e) {
|
|
1559
|
+
killProcessTree(child.pid);
|
|
1560
|
+
clearDaemonStateFile();
|
|
1561
|
+
console.error(e?.message || e);
|
|
1562
|
+
process.exit(1);
|
|
1563
|
+
}
|
|
1564
|
+
});
|
|
1565
|
+
browserCmd.command("stop").description("\u5173\u95ED\u7531 c456 browser start \u542F\u52A8\u7684 Chrome \u5E76\u91CA\u653E\u7AEF\u53E3\u8BB0\u5F55").action(async () => {
|
|
1566
|
+
const st = await loadReconciledDaemonState();
|
|
1567
|
+
if (!st) {
|
|
1568
|
+
console.log("\u5F53\u524D\u6CA1\u6709\u7531 c456 browser start \u8BB0\u5F55\u7684 Chrome \u8FDB\u7A0B\u3002");
|
|
1569
|
+
clearDaemonStateFile();
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
killProcessTree(st.pid);
|
|
1573
|
+
clearDaemonStateFile();
|
|
1574
|
+
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`);
|
|
1575
|
+
});
|
|
1576
|
+
browserCmd.command("status").description("\u67E5\u770B CLI \u6258\u7BA1\u7684 Chrome / CDP \u662F\u5426\u5728\u8FD0\u884C").action(async () => {
|
|
1577
|
+
const st = await loadReconciledDaemonState();
|
|
1578
|
+
if (!st) {
|
|
1579
|
+
console.log("\u72B6\u6001\uFF1A\u672A\u8FD0\u884C\uFF08\u65E0\u6709\u6548 browser-daemon.json\uFF09");
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
console.log("\u72B6\u6001\uFF1A\u8FD0\u884C\u4E2D");
|
|
1583
|
+
console.log(` CDP: ${st.cdpHttp}`);
|
|
1584
|
+
console.log(` port: ${st.port}`);
|
|
1585
|
+
console.log(` pid: ${st.pid}`);
|
|
1586
|
+
console.log(` userDataDir: ${st.userDataDir}`);
|
|
1587
|
+
});
|
|
1588
|
+
var browser_default = browserCmd;
|
|
1589
|
+
|
|
1590
|
+
// src/commands/screenshot.js
|
|
1591
|
+
import fs6 from "node:fs";
|
|
1592
|
+
import path4 from "node:path";
|
|
1593
|
+
import crypto from "node:crypto";
|
|
1594
|
+
import { Command as Command10 } from "commander";
|
|
1595
|
+
import { chromium } from "playwright-core";
|
|
1596
|
+
function parseViewport(s) {
|
|
1597
|
+
if (!s) return null;
|
|
1598
|
+
const m = String(s).trim().match(/^(\d+)\s*[xX]\s*(\d+)$/);
|
|
1599
|
+
if (!m) return null;
|
|
1600
|
+
return { width: Number(m[1]), height: Number(m[2]) };
|
|
1601
|
+
}
|
|
1602
|
+
function assertHttpUrl(u) {
|
|
1603
|
+
let url;
|
|
1604
|
+
try {
|
|
1605
|
+
url = new URL(u);
|
|
1606
|
+
} catch {
|
|
1607
|
+
throw new Error("URL \u65E0\u6548");
|
|
1608
|
+
}
|
|
1609
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
1610
|
+
throw new Error("\u4EC5\u652F\u6301 http(s) URL");
|
|
1611
|
+
}
|
|
1612
|
+
return url.toString();
|
|
1613
|
+
}
|
|
1614
|
+
var INVALID_FILE_CHARS = /[<>:"/\\|?*\u0000-\u001f]/g;
|
|
1615
|
+
function pad2(n) {
|
|
1616
|
+
return String(n).padStart(2, "0");
|
|
1617
|
+
}
|
|
1618
|
+
function localTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
1619
|
+
return `${d.getFullYear()}${pad2(d.getMonth() + 1)}${pad2(d.getDate())}-${pad2(d.getHours())}${pad2(d.getMinutes())}${pad2(d.getSeconds())}`;
|
|
1620
|
+
}
|
|
1621
|
+
function inferScreenshotOutputPath(urlString, cwd = process.cwd()) {
|
|
1622
|
+
const u = new URL(urlString);
|
|
1623
|
+
let slug = u.hostname;
|
|
1624
|
+
if (u.pathname && u.pathname !== "/") {
|
|
1625
|
+
const pathPart = u.pathname.replace(/\/+/g, "-").replace(/^-|-$/g, "");
|
|
1626
|
+
if (pathPart) {
|
|
1627
|
+
slug += `-${pathPart}`;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
slug = slug.replace(INVALID_FILE_CHARS, "_").replace(/_+/g, "_").replace(/^\.+/, "");
|
|
1631
|
+
if (!slug || slug === "_") {
|
|
1632
|
+
slug = "screenshot";
|
|
1633
|
+
}
|
|
1634
|
+
const max = 120;
|
|
1635
|
+
if (slug.length > max) {
|
|
1636
|
+
slug = `${slug.slice(0, max - 10)}_${slug.slice(-8)}`;
|
|
1637
|
+
}
|
|
1638
|
+
const name = `${slug}_${localTimestamp()}.png`;
|
|
1639
|
+
return path4.resolve(cwd, name);
|
|
1640
|
+
}
|
|
1641
|
+
async function sleep(ms) {
|
|
1642
|
+
await new Promise((r) => setTimeout(r, ms));
|
|
1643
|
+
}
|
|
1644
|
+
function isGithubComHost(urlString) {
|
|
1645
|
+
let u;
|
|
1646
|
+
try {
|
|
1647
|
+
u = new URL(urlString);
|
|
1648
|
+
} catch {
|
|
1649
|
+
return false;
|
|
1650
|
+
}
|
|
1651
|
+
const h = u.hostname.toLowerCase();
|
|
1652
|
+
return h === "github.com" || h === "www.github.com";
|
|
1653
|
+
}
|
|
1654
|
+
var GITHUB_FILES_TABLE_SELECTOR = 'table[aria-labelledby="folders-and-files"], [aria-labelledby="folders-and-files"]';
|
|
1655
|
+
async function maybeHideGithubFilesTable(page, targetUrl, captureOpts) {
|
|
1656
|
+
if (captureOpts.keepGithubFilesTable) {
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
if (!isGithubComHost(targetUrl)) {
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
try {
|
|
1663
|
+
await page.waitForSelector(GITHUB_FILES_TABLE_SELECTOR, {
|
|
1664
|
+
timeout: 15e3,
|
|
1665
|
+
state: "attached"
|
|
1666
|
+
});
|
|
1667
|
+
} catch {
|
|
1668
|
+
}
|
|
1669
|
+
let hidden = 0;
|
|
1670
|
+
try {
|
|
1671
|
+
hidden = await page.evaluate(() => {
|
|
1672
|
+
const sels = [
|
|
1673
|
+
'table[aria-labelledby="folders-and-files"]',
|
|
1674
|
+
'[aria-labelledby="folders-and-files"]'
|
|
1675
|
+
];
|
|
1676
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1677
|
+
let n = 0;
|
|
1678
|
+
for (const sel of sels) {
|
|
1679
|
+
for (const el of document.querySelectorAll(sel)) {
|
|
1680
|
+
if (seen.has(el)) {
|
|
1681
|
+
continue;
|
|
1682
|
+
}
|
|
1683
|
+
seen.add(el);
|
|
1684
|
+
el.style.setProperty("display", "none", "important");
|
|
1685
|
+
n += 1;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
return n;
|
|
1689
|
+
});
|
|
1690
|
+
} catch (e) {
|
|
1691
|
+
console.error(`[c456 screenshot] GitHub \u9690\u85CF\u6587\u4EF6\u8868\u5931\u8D25\uFF1A${e?.message || e}`);
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
if (hidden === 0) {
|
|
1695
|
+
console.error(
|
|
1696
|
+
"[c456 screenshot] \u63D0\u793A\uFF1A\u672A\u627E\u5230\u53EF\u9690\u85CF\u7684\u6587\u4EF6\u8868\u8282\u70B9\uFF08\u9875\u9762\u672A\u542B\u8BE5\u7ED3\u6784\u3001\u4ECD\u5728\u9AA8\u67B6\u3001\u6216 GitHub DOM \u5DF2\u6539\u7248\uFF09\u3002\u53EF\u52A0 --pause \u5728\u6D4F\u89C8\u5668\u91CC\u624B\u52A8\u68C0\u67E5\uFF1B\u4E0D\u9700\u8981\u9690\u85CF\u65F6\u53EF\u7528 --keep-github-files-table\u3002"
|
|
1697
|
+
);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
async function waitForEnterFromStdin(message) {
|
|
1701
|
+
const readline = await import("node:readline");
|
|
1702
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1703
|
+
await new Promise((resolve) => {
|
|
1704
|
+
rl.question(message, () => {
|
|
1705
|
+
rl.close();
|
|
1706
|
+
resolve(void 0);
|
|
1707
|
+
});
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
async function captureScreenshotPipeline(page, targetUrl, outPath, captureOpts) {
|
|
1711
|
+
const waitUntil = isGithubComHost(targetUrl) ? "load" : "domcontentloaded";
|
|
1712
|
+
await page.goto(targetUrl, { waitUntil, timeout: 12e4 });
|
|
1713
|
+
if (captureOpts.waitAfterMs > 0) {
|
|
1714
|
+
await sleep(captureOpts.waitAfterMs);
|
|
1715
|
+
}
|
|
1716
|
+
await maybeHideGithubFilesTable(page, targetUrl, captureOpts);
|
|
1717
|
+
if (captureOpts.pause) {
|
|
1718
|
+
await waitForEnterFromStdin(
|
|
1719
|
+
"\uFF08\u8C03\u8BD5\uFF09\u5DF2\u5728\u6D4F\u89C8\u5668\u4E2D\u5B8C\u6210\u52A0\u8F7D\u4E0E GitHub \u9690\u85CF\u5904\u7406\uFF1B\u68C0\u67E5\u9875\u9762\u540E\u6309 Enter \u7EE7\u7EED\u622A\u56FE\u2026\n"
|
|
1720
|
+
);
|
|
1721
|
+
}
|
|
1722
|
+
await page.screenshot({
|
|
1723
|
+
path: outPath,
|
|
1724
|
+
fullPage: captureOpts.fullPage
|
|
1725
|
+
});
|
|
1726
|
+
if (captureOpts.pause) {
|
|
1727
|
+
await waitForEnterFromStdin(
|
|
1728
|
+
"\uFF08\u8C03\u8BD5\uFF09\u622A\u56FE\u5DF2\u5199\u5165\uFF1B\u6309 Enter \u5173\u95ED\u672C\u6807\u7B7E\u9875\u5E76\u7ED3\u675F CLI\uFF08\u4E00\u6B21\u6027\u4F1A\u8BDD\u4E0B\u968F\u540E\u4F1A\u9000\u51FA Chrome\uFF09\u2026\n"
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
async function captureWithCdp(cdpHttp, targetUrl, outPath, captureOpts) {
|
|
1733
|
+
const browser = await chromium.connectOverCDP(cdpHttp);
|
|
1734
|
+
try {
|
|
1735
|
+
const context = browser.contexts()[0];
|
|
1736
|
+
if (!context) {
|
|
1737
|
+
throw new Error("\u672A\u627E\u5230\u9ED8\u8BA4 browser context\uFF08CDP \u5F02\u5E38\uFF09");
|
|
1738
|
+
}
|
|
1739
|
+
const page = await context.newPage();
|
|
1740
|
+
try {
|
|
1741
|
+
await captureScreenshotPipeline(page, targetUrl, outPath, captureOpts);
|
|
1742
|
+
} finally {
|
|
1743
|
+
await page.close().catch(() => {
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
} finally {
|
|
1747
|
+
await browser.close().catch(() => {
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
async function captureEphemeral(targetUrl, outPath, captureOpts) {
|
|
1752
|
+
const chromePath = resolveChromeExecutable();
|
|
1753
|
+
if (!chromePath) {
|
|
1754
|
+
console.error(chromeExecutableHint());
|
|
1755
|
+
process.exit(1);
|
|
1756
|
+
}
|
|
1757
|
+
ensureC456CacheDir();
|
|
1758
|
+
const sessionId = crypto.randomUUID();
|
|
1759
|
+
const userDataDir = path4.join(
|
|
1760
|
+
getC456CacheDir(),
|
|
1761
|
+
"chrome-ephemeral",
|
|
1762
|
+
sessionId
|
|
1763
|
+
);
|
|
1764
|
+
fs6.mkdirSync(userDataDir, { recursive: true });
|
|
1765
|
+
const port = await getFreePort();
|
|
1766
|
+
const { child } = spawnChromeWithCdp(chromePath, { userDataDir, port });
|
|
1767
|
+
let browser;
|
|
1768
|
+
try {
|
|
1769
|
+
await waitForCdpHttp(port);
|
|
1770
|
+
browser = await chromium.connectOverCDP(cdpHttpUrl(port));
|
|
1771
|
+
const context = browser.contexts()[0];
|
|
1772
|
+
if (!context) throw new Error("\u672A\u627E\u5230\u9ED8\u8BA4 browser context");
|
|
1773
|
+
const page = await context.newPage();
|
|
1774
|
+
if (captureOpts.viewport) {
|
|
1775
|
+
await page.setViewportSize(captureOpts.viewport);
|
|
1776
|
+
}
|
|
1777
|
+
await captureScreenshotPipeline(page, targetUrl, outPath, captureOpts);
|
|
1778
|
+
await page.close().catch(() => {
|
|
1779
|
+
});
|
|
1780
|
+
} catch (e) {
|
|
1781
|
+
try {
|
|
1782
|
+
await browser?.close();
|
|
1783
|
+
} catch {
|
|
1784
|
+
}
|
|
1785
|
+
killProcessTree(child.pid);
|
|
1786
|
+
await sleep(400);
|
|
1787
|
+
try {
|
|
1788
|
+
fs6.rmSync(userDataDir, { recursive: true, force: true });
|
|
1789
|
+
} catch {
|
|
1790
|
+
}
|
|
1791
|
+
throw e;
|
|
1792
|
+
}
|
|
1793
|
+
try {
|
|
1794
|
+
await browser?.close();
|
|
1795
|
+
} catch {
|
|
1796
|
+
}
|
|
1797
|
+
killProcessTree(child.pid);
|
|
1798
|
+
await sleep(400);
|
|
1799
|
+
try {
|
|
1800
|
+
fs6.rmSync(userDataDir, { recursive: true, force: true });
|
|
1801
|
+
} catch {
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
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(
|
|
1805
|
+
"-o, --output <path>",
|
|
1806
|
+
"\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"
|
|
1807
|
+
).option("-f, --full-page", "\u6574\u9875\u957F\u622A\u56FE", false).option(
|
|
1808
|
+
"--viewport <WxH>",
|
|
1809
|
+
"\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"
|
|
1810
|
+
).option(
|
|
1811
|
+
"--wait-after-load <ms>",
|
|
1812
|
+
"\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",
|
|
1813
|
+
"3000"
|
|
1814
|
+
).option(
|
|
1815
|
+
"--no-reuse",
|
|
1816
|
+
"\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"
|
|
1817
|
+
).option(
|
|
1818
|
+
"--keep-github-files-table",
|
|
1819
|
+
"github.com \u4E0A\u4FDD\u7559\u300C\u6587\u4EF6\u4E0E\u76EE\u5F55\u300D\u8868\u683C\uFF08\u9ED8\u8BA4\u4F1A\u9690\u85CF\u8BE5\u8868\u4EE5\u4FBF\u622A\u56FE\u7A81\u51FA README\uFF09",
|
|
1820
|
+
false
|
|
1821
|
+
).option(
|
|
1822
|
+
"--pause",
|
|
1823
|
+
"\u8C03\u8BD5\uFF1A\u622A\u56FE\u524D\u540E\u5728\u7EC8\u7AEF\u6309 Enter \u518D\u7EE7\u7EED\uFF1B\u671F\u95F4\u4E0D\u5173\u95ED\u6807\u7B7E\u9875\uFF0C\u4FBF\u4E8E\u5728\u6D4F\u89C8\u5668\u91CC\u68C0\u67E5 DOM\uFF08\u9700\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\uFF09",
|
|
1824
|
+
false
|
|
1825
|
+
).action(async (urlArg, opts) => {
|
|
1826
|
+
try {
|
|
1827
|
+
const targetUrl = assertHttpUrl(urlArg);
|
|
1828
|
+
const outPath = opts.output != null && String(opts.output).trim() !== "" ? path4.resolve(process.cwd(), String(opts.output).trim()) : inferScreenshotOutputPath(targetUrl, process.cwd());
|
|
1829
|
+
const waitAfterMs = Number.parseInt(String(opts.waitAfterLoad), 10);
|
|
1830
|
+
const wait = Number.isFinite(waitAfterMs) ? Math.max(0, waitAfterMs) : 3e3;
|
|
1831
|
+
const viewport = parseViewport(opts.viewport);
|
|
1832
|
+
const fullPage = Boolean(opts.fullPage);
|
|
1833
|
+
const reuse = opts.reuse !== false;
|
|
1834
|
+
const keepGithubFilesTable = Boolean(opts.keepGithubFilesTable);
|
|
1835
|
+
const pause = Boolean(opts.pause);
|
|
1836
|
+
if (pause && !process.stdin.isTTY) {
|
|
1837
|
+
console.error("\u9519\u8BEF\uFF1A--pause \u4EC5\u5728\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\uFF08stdin \u4E3A TTY\uFF09\u4E0B\u53EF\u7528");
|
|
1838
|
+
process.exit(1);
|
|
1839
|
+
}
|
|
1840
|
+
const captureOpts = {
|
|
1841
|
+
fullPage,
|
|
1842
|
+
waitAfterMs: wait,
|
|
1843
|
+
viewport,
|
|
1844
|
+
keepGithubFilesTable,
|
|
1845
|
+
pause
|
|
1846
|
+
};
|
|
1847
|
+
if (reuse) {
|
|
1848
|
+
const daemon = await loadReconciledDaemonState();
|
|
1849
|
+
if (daemon) {
|
|
1850
|
+
await captureWithCdp(daemon.cdpHttp, targetUrl, outPath, {
|
|
1851
|
+
fullPage,
|
|
1852
|
+
waitAfterMs: wait,
|
|
1853
|
+
viewport: null,
|
|
1854
|
+
keepGithubFilesTable,
|
|
1855
|
+
pause
|
|
1856
|
+
});
|
|
1857
|
+
console.log(`\u2705 \u5DF2\u622A\u56FE\uFF08\u590D\u7528 CDP ${daemon.cdpHttp}\uFF09\u2192 ${outPath}`);
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
await captureEphemeral(targetUrl, outPath, captureOpts);
|
|
1862
|
+
console.log(`\u2705 \u5DF2\u622A\u56FE\uFF08\u4E00\u6B21\u6027\u4F1A\u8BDD\uFF0C\u5DF2\u5173\u95ED\uFF09\u2192 ${outPath}`);
|
|
1863
|
+
} catch (e) {
|
|
1864
|
+
console.error(e?.message || e);
|
|
1865
|
+
process.exit(1);
|
|
1866
|
+
}
|
|
1867
|
+
});
|
|
1868
|
+
var screenshot_default = screenshotCmd;
|
|
1869
|
+
|
|
1870
|
+
// src/commands/config.js
|
|
1871
|
+
import fs7 from "node:fs";
|
|
1872
|
+
import { Command as Command11 } from "commander";
|
|
1873
|
+
var GLOBAL_OPT = "-g, --global";
|
|
1874
|
+
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";
|
|
1875
|
+
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");
|
|
1876
|
+
configCmd.command("set-key").description("\u8BBE\u7F6E API Key").argument("<token>", "API Key \u4EE4\u724C").option(GLOBAL_OPT, GLOBAL_DESC).action(async (token, opts) => {
|
|
1877
|
+
const global = opts.global === true;
|
|
1878
|
+
await saveConfigPatch({ apiKey: token }, { global });
|
|
1879
|
+
const target = global ? getGlobalConfigPath() : resolveLocalConfigWritePath();
|
|
1880
|
+
console.log(`\u2705 API Key \u5DF2\u4FDD\u5B58\u81F3 ${target}`);
|
|
1203
1881
|
console.log(` \u63D0\u793A\uFF1A\u4E5F\u53EF\u901A\u8FC7 C456_API_KEY \u73AF\u5883\u53D8\u91CF\u8BBE\u7F6E`);
|
|
1204
1882
|
});
|
|
1205
|
-
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,
|
|
1206
|
-
const
|
|
1207
|
-
|
|
1208
|
-
|
|
1883
|
+
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) => {
|
|
1884
|
+
const global = opts.global === true;
|
|
1885
|
+
await saveConfigPatch({ baseUrl: url }, { global });
|
|
1886
|
+
const target = global ? getGlobalConfigPath() : resolveLocalConfigWritePath();
|
|
1209
1887
|
console.log(`\u2705 \u7CFB\u7EDF\u5730\u5740\u5DF2\u8BBE\u7F6E\u4E3A\uFF1A${url}`);
|
|
1888
|
+
console.log(` \u5DF2\u5199\u5165\uFF1A${target}`);
|
|
1210
1889
|
console.log(` \u63D0\u793A\uFF1A\u4E5F\u53EF\u901A\u8FC7 C456_URL \u73AF\u5883\u53D8\u91CF\u8BBE\u7F6E`);
|
|
1211
1890
|
});
|
|
1212
|
-
configCmd.command("show").description("\u663E\u793A\u5F53\u524D\u914D\u7F6E").action(() => {
|
|
1213
|
-
const
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1891
|
+
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) => {
|
|
1892
|
+
const globalOnly = opts.global === true;
|
|
1893
|
+
if (globalOnly) {
|
|
1894
|
+
const p = getGlobalConfigPath();
|
|
1895
|
+
let raw = {};
|
|
1896
|
+
try {
|
|
1897
|
+
raw = JSON.parse(fs7.readFileSync(p, "utf-8"));
|
|
1898
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) raw = {};
|
|
1899
|
+
} catch {
|
|
1900
|
+
raw = {};
|
|
1901
|
+
}
|
|
1902
|
+
console.log("\u5168\u5C40\u914D\u7F6E\u6587\u4EF6\u5185\u5BB9\uFF1A");
|
|
1903
|
+
console.log(` \u7CFB\u7EDF\u5730\u5740\uFF1A${raw.baseUrl || "(\u672A\u8BBE\u7F6E\uFF0C\u5408\u5E76\u540E\u9ED8\u8BA4 https://c456.com)"}`);
|
|
1904
|
+
console.log(` API Key\uFF1A${raw.apiKey ? String(raw.apiKey).slice(0, 8) + "..." : "(\u672A\u8BBE\u7F6E)"}`);
|
|
1905
|
+
console.log(`
|
|
1906
|
+
\u6587\u4EF6\uFF1A${p}`);
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
const { merged, globalPath, localPath, workspaceRoot } = loadMergedConfigSources();
|
|
1910
|
+
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");
|
|
1911
|
+
console.log(` \u7CFB\u7EDF\u5730\u5740\uFF1A${merged.baseUrl || "https://c456.com"}`);
|
|
1912
|
+
console.log(` API Key\uFF1A${merged.apiKey ? String(merged.apiKey).slice(0, 8) + "..." : "(\u672A\u8BBE\u7F6E)"}`);
|
|
1217
1913
|
console.log(`
|
|
1218
|
-
\u914D\u7F6E\
|
|
1914
|
+
\u5168\u5C40\u914D\u7F6E\uFF1A${globalPath}`);
|
|
1915
|
+
if (workspaceRoot) {
|
|
1916
|
+
console.log(`\u5DE5\u4F5C\u533A\u6839\uFF1A${workspaceRoot}`);
|
|
1917
|
+
console.log(
|
|
1918
|
+
`\u9879\u76EE\u914D\u7F6E\uFF1A${localPath}${localPath && fs7.existsSync(localPath) ? "" : "\uFF08\u5C1A\u672A\u521B\u5EFA\uFF0C\u6709\u6548\u503C\u6765\u81EA\u5168\u5C40\uFF09"}`
|
|
1919
|
+
);
|
|
1920
|
+
} else {
|
|
1921
|
+
console.log(
|
|
1922
|
+
`\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()}`
|
|
1923
|
+
);
|
|
1924
|
+
}
|
|
1219
1925
|
});
|
|
1220
|
-
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) => {
|
|
1221
|
-
const
|
|
1926
|
+
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) => {
|
|
1927
|
+
const fs9 = await import("node:fs");
|
|
1928
|
+
const global = opts.global === true;
|
|
1929
|
+
const targetPath = global ? getGlobalConfigPath() : resolveLocalConfigWritePath();
|
|
1222
1930
|
if (!opts.force) {
|
|
1223
1931
|
const readline = await import("node:readline");
|
|
1224
1932
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1225
1933
|
const answer = await new Promise((resolve) => {
|
|
1226
|
-
rl.question(
|
|
1934
|
+
rl.question(`\u786E\u8BA4\u5220\u9664\u914D\u7F6E\u6587\u4EF6\uFF1F
|
|
1935
|
+
${targetPath}
|
|
1936
|
+
(y/N): `, (ans) => {
|
|
1227
1937
|
rl.close();
|
|
1228
1938
|
resolve(ans.toLowerCase());
|
|
1229
1939
|
});
|
|
@@ -1233,15 +1943,126 @@ configCmd.command("reset").description("\u91CD\u7F6E\u914D\u7F6E\uFF08\u5220\u96
|
|
|
1233
1943
|
return;
|
|
1234
1944
|
}
|
|
1235
1945
|
}
|
|
1236
|
-
if (
|
|
1237
|
-
|
|
1238
|
-
console.log(
|
|
1946
|
+
if (fs9.existsSync(targetPath)) {
|
|
1947
|
+
fs9.unlinkSync(targetPath);
|
|
1948
|
+
console.log(`\u2705 \u5DF2\u5220\u9664\uFF1A${targetPath}`);
|
|
1239
1949
|
} else {
|
|
1240
1950
|
console.log("\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u65E0\u9700\u5220\u9664");
|
|
1241
1951
|
}
|
|
1242
1952
|
});
|
|
1243
1953
|
var config_default = configCmd;
|
|
1244
1954
|
|
|
1955
|
+
// src/commands/skill.js
|
|
1956
|
+
import path5 from "node:path";
|
|
1957
|
+
import { Command as Command12 } from "commander";
|
|
1958
|
+
|
|
1959
|
+
// src/lib/runNpxSkills.js
|
|
1960
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
1961
|
+
function runNpxSkillsAdd(source, opts = {}) {
|
|
1962
|
+
const {
|
|
1963
|
+
cwd = process.cwd(),
|
|
1964
|
+
global = false,
|
|
1965
|
+
agent = "cursor",
|
|
1966
|
+
copy = false,
|
|
1967
|
+
fullDepth = false,
|
|
1968
|
+
skill = "c456-cli"
|
|
1969
|
+
} = opts;
|
|
1970
|
+
const args = ["--yes", "skills", "add", source, "--skill", skill, "-y"];
|
|
1971
|
+
if (global) args.push("-g");
|
|
1972
|
+
if (agent) {
|
|
1973
|
+
args.push("--agent", agent);
|
|
1974
|
+
}
|
|
1975
|
+
if (copy) args.push("--copy");
|
|
1976
|
+
if (fullDepth) args.push("--full-depth");
|
|
1977
|
+
return new Promise((resolve, reject) => {
|
|
1978
|
+
const child = spawn2("npx", args, {
|
|
1979
|
+
cwd,
|
|
1980
|
+
stdio: "inherit",
|
|
1981
|
+
shell: process.platform === "win32",
|
|
1982
|
+
env: process.env
|
|
1983
|
+
});
|
|
1984
|
+
child.on("error", reject);
|
|
1985
|
+
child.on("close", (code, signal) => {
|
|
1986
|
+
if (code === 0) {
|
|
1987
|
+
resolve();
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
const sig = signal ? ` signal=${signal}` : "";
|
|
1991
|
+
reject(new Error(`npx skills add \u9000\u51FA\u7801 ${code ?? "?"}${sig}`));
|
|
1992
|
+
});
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
// src/commands/skill.js
|
|
1997
|
+
var REMOTE_SOURCES = [
|
|
1998
|
+
{ source: "xiaohui-zhangxh/c456-cli", fullDepth: false },
|
|
1999
|
+
{ source: "xiaohui-zhangxh/c456", fullDepth: true }
|
|
2000
|
+
];
|
|
2001
|
+
function buildSkillsOpts(opts) {
|
|
2002
|
+
const agent = String(opts.agent ?? "cursor").trim() || "cursor";
|
|
2003
|
+
return {
|
|
2004
|
+
cwd: path5.resolve(String(opts.cwd || process.cwd())),
|
|
2005
|
+
global: Boolean(opts.global),
|
|
2006
|
+
agent,
|
|
2007
|
+
copy: Boolean(opts.copy)
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
async function installSkillFromRemotes(skillId, base) {
|
|
2011
|
+
let lastErr;
|
|
2012
|
+
for (const { source, fullDepth } of REMOTE_SOURCES) {
|
|
2013
|
+
try {
|
|
2014
|
+
console.error(`\u2192 npx skills add ${source} --skill ${skillId} \u2026`);
|
|
2015
|
+
await runNpxSkillsAdd(source, { ...base, fullDepth, skill: skillId });
|
|
2016
|
+
console.log(`\u2705 \u5DF2\u901A\u8FC7 npx skills \u5B89\u88C5 ${skillId}\uFF08\u6765\u6E90\uFF1A${source}\uFF09`);
|
|
2017
|
+
return;
|
|
2018
|
+
} catch (e) {
|
|
2019
|
+
lastErr = e;
|
|
2020
|
+
console.error(` \u5931\u8D25\uFF1A${e?.message || e}`);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
console.error(
|
|
2024
|
+
`\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`
|
|
2025
|
+
);
|
|
2026
|
+
if (lastErr) {
|
|
2027
|
+
console.error(`\u6700\u540E\u9519\u8BEF\uFF1A${lastErr.message || lastErr}`);
|
|
2028
|
+
}
|
|
2029
|
+
process.exit(1);
|
|
2030
|
+
}
|
|
2031
|
+
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");
|
|
2032
|
+
skillCmd.command("install").description(
|
|
2033
|
+
"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"
|
|
2034
|
+
).option(
|
|
2035
|
+
"-C, --cwd <path>",
|
|
2036
|
+
"\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"
|
|
2037
|
+
).option("-g, --global", "\u4F20\u7ED9 skills add\uFF1A\u5B89\u88C5\u5230\u7528\u6237\u7EA7\u6280\u80FD\u76EE\u5F55", false).option(
|
|
2038
|
+
"-a, --agent <names>",
|
|
2039
|
+
"\u4F20\u7ED9 skills add --agent\uFF08\u5982 cursor\u3001claude-code \u7B49\uFF09\uFF0C\u9ED8\u8BA4 cursor",
|
|
2040
|
+
"cursor"
|
|
2041
|
+
).option("--copy", "\u4F20\u7ED9 skills add\uFF1A\u590D\u5236\u6587\u4EF6\u800C\u975E symlink", false).option(
|
|
2042
|
+
"--with-wiki",
|
|
2043
|
+
"\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",
|
|
2044
|
+
false
|
|
2045
|
+
).action(async (opts) => {
|
|
2046
|
+
const base = buildSkillsOpts(opts);
|
|
2047
|
+
if (opts.withWiki) {
|
|
2048
|
+
try {
|
|
2049
|
+
console.error("\u2192 npx skills add baklib-tools/skills --skill karpathy-wiki \u2026");
|
|
2050
|
+
await runNpxSkillsAdd("baklib-tools/skills", {
|
|
2051
|
+
...base,
|
|
2052
|
+
skill: "karpathy-wiki",
|
|
2053
|
+
fullDepth: false
|
|
2054
|
+
});
|
|
2055
|
+
console.log("\u2705 \u5DF2\u5B89\u88C5 karpathy-wiki\uFF08Karpathy Wiki \u76EE\u5F55\u7EA6\u5B9A\uFF09");
|
|
2056
|
+
} catch (e) {
|
|
2057
|
+
console.error(`karpathy-wiki \u5B89\u88C5\u5931\u8D25\uFF1A${e?.message || e}`);
|
|
2058
|
+
process.exit(1);
|
|
2059
|
+
}
|
|
2060
|
+
await installSkillFromRemotes("c456-llm-wiki", base);
|
|
2061
|
+
}
|
|
2062
|
+
await installSkillFromRemotes("c456-cli", base);
|
|
2063
|
+
});
|
|
2064
|
+
var skill_default = skillCmd;
|
|
2065
|
+
|
|
1245
2066
|
// src/banner.js
|
|
1246
2067
|
import cfonts from "cfonts";
|
|
1247
2068
|
var { render } = cfonts;
|
|
@@ -1289,8 +2110,123 @@ ${body}
|
|
|
1289
2110
|
`;
|
|
1290
2111
|
}
|
|
1291
2112
|
|
|
2113
|
+
// src/lib/npmLatestVersion.js
|
|
2114
|
+
async function fetchNpmLatestVersion(packageName = "c456-cli") {
|
|
2115
|
+
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
|
|
2116
|
+
const res = await fetch(url, {
|
|
2117
|
+
headers: { Accept: "application/json" },
|
|
2118
|
+
redirect: "follow"
|
|
2119
|
+
});
|
|
2120
|
+
if (!res.ok) {
|
|
2121
|
+
throw new Error(`registry \u54CD\u5E94 ${res.status}`);
|
|
2122
|
+
}
|
|
2123
|
+
const data = await res.json();
|
|
2124
|
+
const v = data?.version;
|
|
2125
|
+
if (!v || typeof v !== "string") {
|
|
2126
|
+
throw new Error("registry \u8FD4\u56DE\u65E0 version \u5B57\u6BB5");
|
|
2127
|
+
}
|
|
2128
|
+
return v;
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
// src/lib/cliUpdateState.js
|
|
2132
|
+
import fs8 from "node:fs";
|
|
2133
|
+
function readRaw() {
|
|
2134
|
+
const p = getVersionCheckStatePath();
|
|
2135
|
+
try {
|
|
2136
|
+
return JSON.parse(fs8.readFileSync(p, "utf8"));
|
|
2137
|
+
} catch {
|
|
2138
|
+
return null;
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
function loadUpdateState() {
|
|
2142
|
+
const raw = readRaw();
|
|
2143
|
+
if (!raw || typeof raw !== "object") {
|
|
2144
|
+
return { lastCheckDay: "", pendingNotifyVersion: "" };
|
|
2145
|
+
}
|
|
2146
|
+
return {
|
|
2147
|
+
lastCheckDay: typeof raw.lastCheckDay === "string" ? raw.lastCheckDay : "",
|
|
2148
|
+
pendingNotifyVersion: typeof raw.pendingNotifyVersion === "string" ? raw.pendingNotifyVersion : ""
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
function writeUpdateState(state) {
|
|
2152
|
+
ensureC456CacheDir();
|
|
2153
|
+
const p = getVersionCheckStatePath();
|
|
2154
|
+
fs8.writeFileSync(p, `${JSON.stringify(state, null, 2)}
|
|
2155
|
+
`, "utf8");
|
|
2156
|
+
}
|
|
2157
|
+
function patchUpdateState(partial) {
|
|
2158
|
+
const cur = loadUpdateState();
|
|
2159
|
+
writeUpdateState({ ...cur, ...partial });
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
// src/lib/semverGt.js
|
|
2163
|
+
function semverGt(a, b) {
|
|
2164
|
+
const pa = String(a).split(".").map((x) => Number.parseInt(x, 10) || 0);
|
|
2165
|
+
const pb = String(b).split(".").map((x) => Number.parseInt(x, 10) || 0);
|
|
2166
|
+
const n = Math.max(pa.length, pb.length, 3);
|
|
2167
|
+
for (let i = 0; i < n; i += 1) {
|
|
2168
|
+
const x = pa[i] ?? 0;
|
|
2169
|
+
const y = pb[i] ?? 0;
|
|
2170
|
+
if (x > y) return true;
|
|
2171
|
+
if (x < y) return false;
|
|
2172
|
+
}
|
|
2173
|
+
return false;
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
// src/lib/localCalendarDay.js
|
|
2177
|
+
function localCalendarDay() {
|
|
2178
|
+
const d = /* @__PURE__ */ new Date();
|
|
2179
|
+
const y = d.getFullYear();
|
|
2180
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
2181
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
2182
|
+
return `${y}-${m}-${day}`;
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
// src/startup.js
|
|
2186
|
+
function printPendingUpdateNotice(currentVersion) {
|
|
2187
|
+
const st = loadUpdateState();
|
|
2188
|
+
const pending = st.pendingNotifyVersion?.trim();
|
|
2189
|
+
if (!pending) return;
|
|
2190
|
+
if (!semverGt(pending, currentVersion)) {
|
|
2191
|
+
patchUpdateState({ pendingNotifyVersion: "" });
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
console.error("");
|
|
2195
|
+
console.error(
|
|
2196
|
+
`[c456-cli] \u6709\u65B0\u7248\u672C ${pending}\uFF08\u5F53\u524D ${currentVersion}\uFF09\u3002\u53EF\u6267\u884C\uFF1Anpm i -g c456-cli`
|
|
2197
|
+
);
|
|
2198
|
+
console.error("");
|
|
2199
|
+
patchUpdateState({ pendingNotifyVersion: "" });
|
|
2200
|
+
}
|
|
2201
|
+
function scheduleDailyNpmVersionCheck(currentVersion) {
|
|
2202
|
+
const today = localCalendarDay();
|
|
2203
|
+
const st = loadUpdateState();
|
|
2204
|
+
if (st.lastCheckDay === today) return;
|
|
2205
|
+
setImmediate(() => {
|
|
2206
|
+
void (async () => {
|
|
2207
|
+
let latest;
|
|
2208
|
+
try {
|
|
2209
|
+
latest = await fetchNpmLatestVersion("c456-cli");
|
|
2210
|
+
} catch {
|
|
2211
|
+
patchUpdateState({ lastCheckDay: today });
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
const patch = { lastCheckDay: today };
|
|
2215
|
+
if (semverGt(latest, currentVersion)) {
|
|
2216
|
+
patch.pendingNotifyVersion = latest;
|
|
2217
|
+
}
|
|
2218
|
+
patchUpdateState(patch);
|
|
2219
|
+
})();
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
function runCliStartupHooks({ currentVersion }) {
|
|
2223
|
+
if (process.env.C456_SKIP_VERSION_CHECK === "1") return;
|
|
2224
|
+
printPendingUpdateNotice(currentVersion);
|
|
2225
|
+
scheduleDailyNpmVersionCheck(currentVersion);
|
|
2226
|
+
}
|
|
2227
|
+
|
|
1292
2228
|
// src/index.js
|
|
1293
|
-
var program = new
|
|
2229
|
+
var program = new Command13();
|
|
1294
2230
|
program.name("c456").description("C456 CLI - \u5FEB\u901F\u5185\u5BB9\u5F55\u5165\u4E0E\u6574\u7406\u5DE5\u5177").version(package_default.version);
|
|
1295
2231
|
program.addHelpText("before", () => {
|
|
1296
2232
|
const banner = getHelpBanner();
|
|
@@ -1317,7 +2253,7 @@ program.exitOverride((err) => {
|
|
|
1317
2253
|
});
|
|
1318
2254
|
program.option(
|
|
1319
2255
|
"-B, --base-url <url>",
|
|
1320
|
-
"C456 \u7AD9\u70B9\u6839\u5730\u5740\uFF1B\u672A\u4F20\u5219\u4F7F\u7528 C456_URL
|
|
2256
|
+
"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"
|
|
1321
2257
|
);
|
|
1322
2258
|
program.addCommand(signal_default);
|
|
1323
2259
|
program.addCommand(tool_default);
|
|
@@ -1327,8 +2263,11 @@ program.addCommand(search_default);
|
|
|
1327
2263
|
program.addCommand(playbook_default);
|
|
1328
2264
|
program.addCommand(walkthrough_default);
|
|
1329
2265
|
program.addCommand(asset_default);
|
|
2266
|
+
program.addCommand(browser_default);
|
|
2267
|
+
program.addCommand(screenshot_default);
|
|
1330
2268
|
program.addCommand(intake_default);
|
|
1331
2269
|
program.addCommand(config_default);
|
|
2270
|
+
program.addCommand(skill_default);
|
|
1332
2271
|
program.on("--help", () => {
|
|
1333
2272
|
console.log("\n\u793A\u4F8B:");
|
|
1334
2273
|
console.log(" # \u914D\u7F6E API Key");
|
|
@@ -1340,8 +2279,14 @@ program.on("--help", () => {
|
|
|
1340
2279
|
console.log(" # \u641C\u7D22\u6536\u5F55");
|
|
1341
2280
|
console.log(' c456 search signals -q "AI agent"');
|
|
1342
2281
|
console.log("");
|
|
2282
|
+
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");
|
|
2283
|
+
console.log(" c456 skill install --with-wiki");
|
|
2284
|
+
console.log("");
|
|
1343
2285
|
console.log("\u73AF\u5883\u53D8\u91CF:");
|
|
1344
2286
|
console.log(" C456_URL - \u7AD9\u70B9\u6839\u5730\u5740\uFF08\u4E0E -B / --base-url \u4E00\u81F4\uFF09");
|
|
1345
2287
|
console.log(" C456_API_KEY - API Key");
|
|
2288
|
+
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");
|
|
2289
|
+
console.log(" C456_SKIP_VERSION_CHECK=1 - \u8DF3\u8FC7\u6BCF\u65E5 npm \u7248\u672C\u68C0\u67E5\u4E0E\u66F4\u65B0\u63D0\u793A");
|
|
1346
2290
|
});
|
|
2291
|
+
runCliStartupHooks({ currentVersion: package_default.version });
|
|
1347
2292
|
program.parse();
|