siluzan-tso-cli 1.1.8-beta.6 → 1.1.9-beta.2
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 +30 -1
- package/dist/index.js +157 -345
- package/dist/skill/SKILL.md +64 -6
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/setup.md +9 -3
- package/dist/skill/report-templates/report-template-academic.html +3 -0
- package/dist/skill/report-templates/report-template-dark.html +3 -0
- package/dist/skill/report-templates/report-template-formal.html +3 -0
- package/dist/skill/report-templates/report-template-mobile.html +3 -0
- package/dist/skill/report-templates/report-template-onepager.html +3 -0
- package/dist/skill/report-templates/report-template-print.html +3 -0
- package/dist/skill/report-templates/report-template.html +6 -3
- package/package.json +26 -2
package/README.md
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
当用户发来本页面的 **URL** 时,请按顺序协助用户完成安装。
|
|
4
4
|
|
|
5
|
+
## 0. 安全与透明性说明
|
|
6
|
+
|
|
7
|
+
本 Skill 是 **instruction-only**(纯指令型),不包含可执行代码文件。它通过指导 AI 助手调用 `siluzan-tso` CLI 来操作丝路赞广告管理平台。
|
|
8
|
+
|
|
9
|
+
### 运行时依赖
|
|
10
|
+
| 项目 | 说明 |
|
|
11
|
+
|------|------|
|
|
12
|
+
| **Node.js 18+** | CLI 运行时 |
|
|
13
|
+
| **npm 8+** 或 **pnpm** | 安装 CLI 全局包 |
|
|
14
|
+
| **siluzan-tso** CLI | 由 `npm install -g siluzan-tso-cli` 安装 |
|
|
15
|
+
|
|
16
|
+
### 凭据与网络
|
|
17
|
+
| 项目 | 说明 |
|
|
18
|
+
|------|------|
|
|
19
|
+
| API Key / Token | 存储在 `~/.siluzan/config.json`,用于所有 API 调用鉴权 |
|
|
20
|
+
| TSO API | `tso-api.siluzan.com`(生产)/ `tso-api-ci.siluzan.com`(测试) |
|
|
21
|
+
| Google API 网关 | `googleapi.mysiluzan.com` / `googleapi-ci.mysiluzan.com` |
|
|
22
|
+
| 前端页面 | `www.siluzan.com`(充值、激活等浏览器操作) |
|
|
23
|
+
| 错误追踪 | `o605862.ingest.us.sentry.io`(匿名崩溃日志) |
|
|
24
|
+
| 版本检查 | `registry.npmjs.org` |
|
|
25
|
+
|
|
26
|
+
### 可执行的操作范围
|
|
27
|
+
- **只读**:查询账户列表、余额、消耗数据、报告、预警记录等
|
|
28
|
+
- **写入**(需用户确认):创建/修改/发布广告、开户申请、创建预警规则、报告推送配置
|
|
29
|
+
- **破坏性**(必须用户确认):账户解绑/关闭、MCC/BC 解绑、删除预警/报告/广告、发票申请
|
|
30
|
+
|
|
31
|
+
### 报告模板外部资源
|
|
32
|
+
HTML 报告模板引用以下 CDN:`cdn.tailwindcss.com`、`cdnjs.cloudflare.com`、`cdn.jsdelivr.net`、`fonts.googleapis.com`。渲染报告时浏览器会发出对应网络请求。
|
|
33
|
+
|
|
5
34
|
## 1. 环境
|
|
6
35
|
|
|
7
36
|
- Node.js **18+**
|
|
@@ -20,7 +49,7 @@ siluzan-tso init -d /path/to/skills # 写入自定义目录
|
|
|
20
49
|
siluzan-tso init --force # 强制覆盖已存在文件
|
|
21
50
|
```
|
|
22
51
|
|
|
23
|
-
> **注意**:当前为测试版(1.1.
|
|
52
|
+
> **注意**:当前为测试版(1.1.9-beta.2),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
24
53
|
|
|
25
54
|
| 助手 | 建议 `--ai` |
|
|
26
55
|
|------|-------------|
|
package/dist/index.js
CHANGED
|
@@ -362,8 +362,8 @@ var require_semver = __commonJS({
|
|
|
362
362
|
}
|
|
363
363
|
// preminor will bump the version up to the next minor release, and immediately
|
|
364
364
|
// down to pre-release. premajor and prepatch work the same way.
|
|
365
|
-
inc(
|
|
366
|
-
if (
|
|
365
|
+
inc(release, identifier, identifierBase) {
|
|
366
|
+
if (release.startsWith("pre")) {
|
|
367
367
|
if (!identifier && identifierBase === false) {
|
|
368
368
|
throw new Error("invalid increment argument: identifier is empty");
|
|
369
369
|
}
|
|
@@ -374,7 +374,7 @@ var require_semver = __commonJS({
|
|
|
374
374
|
}
|
|
375
375
|
}
|
|
376
376
|
}
|
|
377
|
-
switch (
|
|
377
|
+
switch (release) {
|
|
378
378
|
case "premajor":
|
|
379
379
|
this.prerelease.length = 0;
|
|
380
380
|
this.patch = 0;
|
|
@@ -465,7 +465,7 @@ var require_semver = __commonJS({
|
|
|
465
465
|
break;
|
|
466
466
|
}
|
|
467
467
|
default:
|
|
468
|
-
throw new Error(`invalid increment argument: ${
|
|
468
|
+
throw new Error(`invalid increment argument: ${release}`);
|
|
469
469
|
}
|
|
470
470
|
this.raw = this.format();
|
|
471
471
|
if (this.build.length) {
|
|
@@ -531,7 +531,7 @@ var require_inc = __commonJS({
|
|
|
531
531
|
"../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/inc.js"(exports, module) {
|
|
532
532
|
"use strict";
|
|
533
533
|
var SemVer = require_semver();
|
|
534
|
-
var inc = (version,
|
|
534
|
+
var inc = (version, release, options, identifier, identifierBase) => {
|
|
535
535
|
if (typeof options === "string") {
|
|
536
536
|
identifierBase = identifier;
|
|
537
537
|
identifier = options;
|
|
@@ -541,7 +541,7 @@ var require_inc = __commonJS({
|
|
|
541
541
|
return new SemVer(
|
|
542
542
|
version instanceof SemVer ? version.version : version,
|
|
543
543
|
options
|
|
544
|
-
).inc(
|
|
544
|
+
).inc(release, identifier, identifierBase).version;
|
|
545
545
|
} catch (er) {
|
|
546
546
|
return null;
|
|
547
547
|
}
|
|
@@ -1974,7 +1974,6 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
1974
1974
|
import * as fs2 from "fs";
|
|
1975
1975
|
import * as path2 from "path";
|
|
1976
1976
|
import { fileURLToPath } from "url";
|
|
1977
|
-
import * as os2 from "os";
|
|
1978
1977
|
var SILUZAN_DIR = path.join(os.homedir(), ".siluzan");
|
|
1979
1978
|
var CONFIG_FILE = path.join(SILUZAN_DIR, "config.json");
|
|
1980
1979
|
function readStr(raw, key) {
|
|
@@ -2055,21 +2054,33 @@ function validateBaseUrl(raw) {
|
|
|
2055
2054
|
}
|
|
2056
2055
|
return null;
|
|
2057
2056
|
}
|
|
2057
|
+
var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
2058
|
+
var MAX_RESPONSE_BYTES = 50 * 1024 * 1024;
|
|
2058
2059
|
function rawRequest(url, options) {
|
|
2059
2060
|
return new Promise((resolve2, reject) => {
|
|
2060
2061
|
const parsed = new URL(url);
|
|
2061
2062
|
const transport = parsed.protocol === "https:" ? https : http;
|
|
2063
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
2064
|
+
const maxBytes = options.maxResponseBytes ?? MAX_RESPONSE_BYTES;
|
|
2062
2065
|
const reqOpts = {
|
|
2063
2066
|
hostname: parsed.hostname,
|
|
2064
2067
|
port: parsed.port || (parsed.protocol === "https:" ? 443 : 80),
|
|
2065
2068
|
path: parsed.pathname + parsed.search,
|
|
2066
2069
|
method: options.method ?? "GET",
|
|
2067
|
-
headers: options.headers
|
|
2070
|
+
headers: options.headers,
|
|
2071
|
+
timeout: timeoutMs || void 0
|
|
2068
2072
|
};
|
|
2069
2073
|
const req = transport.request(reqOpts, (res) => {
|
|
2070
2074
|
let data = "";
|
|
2075
|
+
let byteLen = 0;
|
|
2071
2076
|
res.setEncoding("utf8");
|
|
2072
2077
|
res.on("data", (chunk) => {
|
|
2078
|
+
byteLen += Buffer.byteLength(chunk, "utf8");
|
|
2079
|
+
if (maxBytes && byteLen > maxBytes) {
|
|
2080
|
+
res.destroy();
|
|
2081
|
+
reject(new Error(`\u54CD\u5E94\u4F53\u8D85\u8FC7\u4E0A\u9650\uFF08${(maxBytes / 1024 / 1024).toFixed(0)} MB\uFF09\uFF0C\u5DF2\u4E2D\u65AD\u8FDE\u63A5`));
|
|
2082
|
+
return;
|
|
2083
|
+
}
|
|
2073
2084
|
data += chunk;
|
|
2074
2085
|
});
|
|
2075
2086
|
res.on("end", () => {
|
|
@@ -2082,6 +2093,10 @@ function rawRequest(url, options) {
|
|
|
2082
2093
|
resolve2({ status: res.statusCode ?? 0, text: data, headers });
|
|
2083
2094
|
});
|
|
2084
2095
|
});
|
|
2096
|
+
req.on("timeout", () => {
|
|
2097
|
+
req.destroy();
|
|
2098
|
+
reject(new Error(`\u8BF7\u6C42\u8D85\u65F6\uFF08${(timeoutMs / 1e3).toFixed(0)} \u79D2\uFF09\uFF1A${options.method ?? "GET"} ${url}`));
|
|
2099
|
+
});
|
|
2085
2100
|
req.on("error", reject);
|
|
2086
2101
|
if (options.body) req.write(options.body);
|
|
2087
2102
|
req.end();
|
|
@@ -2191,308 +2206,6 @@ async function fetchNpmVersion(pkgName, tag, timeoutMs = 4e3) {
|
|
|
2191
2206
|
return null;
|
|
2192
2207
|
}
|
|
2193
2208
|
}
|
|
2194
|
-
var DEFAULT_SENTRY_DSN = "https://bafcf42aab6fe7b485310619ae041b5e@o4510436169285632.ingest.us.sentry.io/4511103054708736";
|
|
2195
|
-
function isSentryDisabled() {
|
|
2196
|
-
return process.env.SILUZAN_SENTRY_DISABLED === "1" || process.env.SILUZAN_SENTRY_DISABLED === "true";
|
|
2197
|
-
}
|
|
2198
|
-
function getDsn() {
|
|
2199
|
-
return process.env.SILUZAN_SENTRY_DSN?.trim() || DEFAULT_SENTRY_DSN;
|
|
2200
|
-
}
|
|
2201
|
-
var cliMeta = { name: "siluzan-cli", version: "unknown" };
|
|
2202
|
-
var cliInvocation = "";
|
|
2203
|
-
function setSiluzanCliInvocation(redactedCommandLine) {
|
|
2204
|
-
cliInvocation = redactedCommandLine;
|
|
2205
|
-
}
|
|
2206
|
-
function redactCliArgvForSentry(argv) {
|
|
2207
|
-
const args = argv.slice(2);
|
|
2208
|
-
const redactNext = /* @__PURE__ */ new Set(["-t", "--token", "--api-key", "--password"]);
|
|
2209
|
-
const out = [];
|
|
2210
|
-
for (let i = 0; i < args.length; i++) {
|
|
2211
|
-
const a = args[i];
|
|
2212
|
-
if (redactNext.has(a)) {
|
|
2213
|
-
out.push(a, "***");
|
|
2214
|
-
i++;
|
|
2215
|
-
continue;
|
|
2216
|
-
}
|
|
2217
|
-
const eq = a.indexOf("=");
|
|
2218
|
-
if (eq > 0) {
|
|
2219
|
-
const key = a.slice(0, eq).toLowerCase();
|
|
2220
|
-
if (key === "--token" || key === "--api-key" || key === "--password") {
|
|
2221
|
-
out.push(`${a.slice(0, eq)}=***`);
|
|
2222
|
-
continue;
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
if (/^-t[^-]/.test(a) && a.length > 3) {
|
|
2226
|
-
out.push("-t***");
|
|
2227
|
-
continue;
|
|
2228
|
-
}
|
|
2229
|
-
out.push(a);
|
|
2230
|
-
}
|
|
2231
|
-
return out.join(" ");
|
|
2232
|
-
}
|
|
2233
|
-
function setSiluzanCliMeta(name, version) {
|
|
2234
|
-
cliMeta = { name, version };
|
|
2235
|
-
if (sentryReady) {
|
|
2236
|
-
void import("@sentry/node").then((Sentry) => {
|
|
2237
|
-
Sentry.setTag("cli", name);
|
|
2238
|
-
Sentry.setTag("cli_version", version);
|
|
2239
|
-
}).catch(() => {
|
|
2240
|
-
});
|
|
2241
|
-
}
|
|
2242
|
-
}
|
|
2243
|
-
var sentryReady = false;
|
|
2244
|
-
var sentryInitPromise = null;
|
|
2245
|
-
var flushHookRegistered = false;
|
|
2246
|
-
function buildRuntimeContext() {
|
|
2247
|
-
const platform = process.platform;
|
|
2248
|
-
const osName = platform === "win32" ? "Windows" : platform === "darwin" ? "macOS" : "Linux";
|
|
2249
|
-
return {
|
|
2250
|
-
os: {
|
|
2251
|
-
name: osName,
|
|
2252
|
-
version: os2.release(),
|
|
2253
|
-
// 内核版本,如 10.0.26100(Win11)、24.3.0(macOS)
|
|
2254
|
-
arch: process.arch
|
|
2255
|
-
// x64 / arm64
|
|
2256
|
-
},
|
|
2257
|
-
runtime: {
|
|
2258
|
-
node_version: process.version,
|
|
2259
|
-
// v18.x.x / v20.x.x
|
|
2260
|
-
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
2261
|
-
}
|
|
2262
|
-
};
|
|
2263
|
-
}
|
|
2264
|
-
async function ensureSentryInitialized(requestUrl) {
|
|
2265
|
-
const dsn = getDsn();
|
|
2266
|
-
if (!dsn || isSentryDisabled()) return false;
|
|
2267
|
-
if (sentryReady) return true;
|
|
2268
|
-
if (!sentryInitPromise) {
|
|
2269
|
-
sentryInitPromise = (async () => {
|
|
2270
|
-
const Sentry = await import("@sentry/node");
|
|
2271
|
-
const envOverride = process.env.SILUZAN_SENTRY_ENV?.trim();
|
|
2272
|
-
const environment = envOverride || (inferSiluzanRuntimeEnvironment(requestUrl) === "test" ? "test" : "production");
|
|
2273
|
-
Sentry.init({
|
|
2274
|
-
dsn,
|
|
2275
|
-
environment,
|
|
2276
|
-
sendDefaultPii: true,
|
|
2277
|
-
tracesSampleRate: 0
|
|
2278
|
-
});
|
|
2279
|
-
const ctx = buildRuntimeContext();
|
|
2280
|
-
Sentry.setContext("os", ctx.os);
|
|
2281
|
-
Sentry.setContext("runtime", ctx.runtime);
|
|
2282
|
-
Sentry.setTag("cli", cliMeta.name);
|
|
2283
|
-
Sentry.setTag("cli_version", cliMeta.version);
|
|
2284
|
-
if (!flushHookRegistered) {
|
|
2285
|
-
flushHookRegistered = true;
|
|
2286
|
-
process.once("beforeExit", () => {
|
|
2287
|
-
void Sentry.flush(2e3).catch(() => {
|
|
2288
|
-
});
|
|
2289
|
-
});
|
|
2290
|
-
}
|
|
2291
|
-
sentryReady = true;
|
|
2292
|
-
})();
|
|
2293
|
-
}
|
|
2294
|
-
await sentryInitPromise;
|
|
2295
|
-
return sentryReady;
|
|
2296
|
-
}
|
|
2297
|
-
function deriveMainApiOriginFromRequestUrl(requestUrl) {
|
|
2298
|
-
try {
|
|
2299
|
-
const u = new URL(requestUrl);
|
|
2300
|
-
const host = u.hostname.toLowerCase();
|
|
2301
|
-
if (!host.endsWith("siluzan.com")) return null;
|
|
2302
|
-
if (host.startsWith("tso-api")) {
|
|
2303
|
-
return `${u.protocol}//${host.replace(/^tso-api/, "api")}`;
|
|
2304
|
-
}
|
|
2305
|
-
if (host === "api.siluzan.com" || host === "api-ci.siluzan.com") {
|
|
2306
|
-
return `${u.protocol}//${host}`;
|
|
2307
|
-
}
|
|
2308
|
-
return null;
|
|
2309
|
-
} catch {
|
|
2310
|
-
return null;
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
function inferSiluzanRuntimeEnvironment(requestUrl) {
|
|
2314
|
-
try {
|
|
2315
|
-
const host = new URL(requestUrl).hostname.toLowerCase();
|
|
2316
|
-
return host.includes("-ci") ? "test" : "production";
|
|
2317
|
-
} catch {
|
|
2318
|
-
return "production";
|
|
2319
|
-
}
|
|
2320
|
-
}
|
|
2321
|
-
function cacheKeyForUser(mainOrigin, config) {
|
|
2322
|
-
const cred = config.apiKey ? `k:${config.apiKey}` : `t:${config.authToken}`;
|
|
2323
|
-
return `${mainOrigin}\0${cred}`;
|
|
2324
|
-
}
|
|
2325
|
-
var userContextDone = /* @__PURE__ */ new Set();
|
|
2326
|
-
var userContextInflight = /* @__PURE__ */ new Map();
|
|
2327
|
-
function pickCompanyIdFromMe(data) {
|
|
2328
|
-
const direct = pickStr(data, "companyId") ?? pickStr(data, "companyID") ?? pickStr(data, "CompanyId");
|
|
2329
|
-
if (direct) return direct;
|
|
2330
|
-
const ci = data["companyInfo"];
|
|
2331
|
-
if (ci && typeof ci === "object") {
|
|
2332
|
-
const o = ci;
|
|
2333
|
-
return pickStr(o, "id") ?? pickStr(o, "companyId") ?? pickStr(o, "companyID");
|
|
2334
|
-
}
|
|
2335
|
-
return void 0;
|
|
2336
|
-
}
|
|
2337
|
-
function parseMeResponse(text) {
|
|
2338
|
-
try {
|
|
2339
|
-
const json = JSON.parse(text);
|
|
2340
|
-
const data = json?.data && typeof json.data === "object" ? json.data : json;
|
|
2341
|
-
const id = pickStr(data, "entityId") ?? pickStr(data, "id") ?? pickStr(data, "userId") ?? pickStr(data, "accountId");
|
|
2342
|
-
const email = pickStr(data, "email");
|
|
2343
|
-
const username = pickStr(data, "userName") ?? pickStr(data, "username") ?? pickStr(data, "name") ?? pickStr(data, "phone");
|
|
2344
|
-
const companyId = pickCompanyIdFromMe(data);
|
|
2345
|
-
if (!id && !email && !username && !companyId) return null;
|
|
2346
|
-
return { id, email, username, companyId };
|
|
2347
|
-
} catch {
|
|
2348
|
-
return null;
|
|
2349
|
-
}
|
|
2350
|
-
}
|
|
2351
|
-
function pickStr(obj, key) {
|
|
2352
|
-
const v = obj[key];
|
|
2353
|
-
return typeof v === "string" && v.trim() ? v.trim() : void 0;
|
|
2354
|
-
}
|
|
2355
|
-
async function fetchAndSetUser(mainOrigin, config) {
|
|
2356
|
-
const meUrl = `${mainOrigin.replace(/\/$/, "")}/query/account/me`;
|
|
2357
|
-
const authHeaders = config.apiKey ? { "x-api-key": config.apiKey } : { Authorization: `Bearer ${config.authToken}` };
|
|
2358
|
-
const res = await rawRequest(meUrl, {
|
|
2359
|
-
method: "GET",
|
|
2360
|
-
headers: {
|
|
2361
|
-
"Content-Type": "application/json",
|
|
2362
|
-
"Accept-Language": "zh-CN",
|
|
2363
|
-
...authHeaders
|
|
2364
|
-
}
|
|
2365
|
-
});
|
|
2366
|
-
if (res.status < 200 || res.status >= 300) return;
|
|
2367
|
-
const parsed = parseMeResponse(res.text);
|
|
2368
|
-
if (!parsed) return;
|
|
2369
|
-
const Sentry = await import("@sentry/node");
|
|
2370
|
-
const user = {};
|
|
2371
|
-
if (parsed.id) user.id = parsed.id;
|
|
2372
|
-
if (parsed.email) user.email = parsed.email;
|
|
2373
|
-
if (parsed.username) user.username = parsed.username;
|
|
2374
|
-
Sentry.setUser(user);
|
|
2375
|
-
}
|
|
2376
|
-
function scheduleUserContext(mainOrigin, config) {
|
|
2377
|
-
const key = cacheKeyForUser(mainOrigin, config);
|
|
2378
|
-
if (userContextDone.has(key)) return;
|
|
2379
|
-
let p = userContextInflight.get(key);
|
|
2380
|
-
if (!p) {
|
|
2381
|
-
p = (async () => {
|
|
2382
|
-
try {
|
|
2383
|
-
await fetchAndSetUser(mainOrigin, config);
|
|
2384
|
-
} catch {
|
|
2385
|
-
} finally {
|
|
2386
|
-
userContextInflight.delete(key);
|
|
2387
|
-
userContextDone.add(key);
|
|
2388
|
-
}
|
|
2389
|
-
})();
|
|
2390
|
-
userContextInflight.set(key, p);
|
|
2391
|
-
}
|
|
2392
|
-
}
|
|
2393
|
-
function refreshSiluzanUser(apiBase, config) {
|
|
2394
|
-
if (isSentryDisabled()) return;
|
|
2395
|
-
void (async () => {
|
|
2396
|
-
try {
|
|
2397
|
-
const ok = await ensureSentryInitialized(apiBase);
|
|
2398
|
-
if (!ok) return;
|
|
2399
|
-
const mainOrigin = deriveMainApiOriginFromRequestUrl(apiBase) ?? (apiBase.includes("-ci") ? "https://api-ci.siluzan.com" : "https://api.siluzan.com");
|
|
2400
|
-
const key = cacheKeyForUser(mainOrigin, config);
|
|
2401
|
-
userContextDone.delete(key);
|
|
2402
|
-
userContextInflight.delete(key);
|
|
2403
|
-
scheduleUserContext(mainOrigin, config);
|
|
2404
|
-
} catch {
|
|
2405
|
-
}
|
|
2406
|
-
})();
|
|
2407
|
-
}
|
|
2408
|
-
|
|
2409
|
-
// src/utils/version.ts
|
|
2410
|
-
import * as fs3 from "fs";
|
|
2411
|
-
import * as path3 from "path";
|
|
2412
|
-
import * as os3 from "os";
|
|
2413
|
-
var PKG_NAME = "siluzan-tso-cli";
|
|
2414
|
-
var CONFIG_FILE2 = path3.join(os3.homedir(), ".siluzan", "config.json");
|
|
2415
|
-
function getCurrentVersion2() {
|
|
2416
|
-
return getCurrentVersion(import.meta.url);
|
|
2417
|
-
}
|
|
2418
|
-
function readConfigRaw() {
|
|
2419
|
-
try {
|
|
2420
|
-
return JSON.parse(fs3.readFileSync(CONFIG_FILE2, "utf8"));
|
|
2421
|
-
} catch {
|
|
2422
|
-
return {};
|
|
2423
|
-
}
|
|
2424
|
-
}
|
|
2425
|
-
function writeConfigRaw(data) {
|
|
2426
|
-
try {
|
|
2427
|
-
fs3.mkdirSync(path3.dirname(CONFIG_FILE2), { recursive: true });
|
|
2428
|
-
fs3.writeFileSync(CONFIG_FILE2, JSON.stringify(data, null, 2), "utf8");
|
|
2429
|
-
if (process.platform !== "win32") {
|
|
2430
|
-
fs3.chmodSync(CONFIG_FILE2, 384);
|
|
2431
|
-
}
|
|
2432
|
-
} catch {
|
|
2433
|
-
}
|
|
2434
|
-
}
|
|
2435
|
-
async function fetchVersionByTag(tag, cacheKey, cfg) {
|
|
2436
|
-
const hours24 = 24 * 60 * 60 * 1e3;
|
|
2437
|
-
if (cfg._tsoLastVersionCheck && cfg[cacheKey]) {
|
|
2438
|
-
const lastCheck = new Date(cfg._tsoLastVersionCheck).getTime();
|
|
2439
|
-
if (Date.now() - lastCheck < hours24) {
|
|
2440
|
-
return cfg[cacheKey];
|
|
2441
|
-
}
|
|
2442
|
-
}
|
|
2443
|
-
return fetchNpmVersion(PKG_NAME, tag);
|
|
2444
|
-
}
|
|
2445
|
-
async function notifyIfOutdated() {
|
|
2446
|
-
try {
|
|
2447
|
-
const current = getCurrentVersion2();
|
|
2448
|
-
const tag = npmDistTagForCurrentVersion(current);
|
|
2449
|
-
const isBeta = tag === "beta";
|
|
2450
|
-
const latestCacheKey = isBeta ? "_tsoLatestBeta" : "_tsoLatestStable";
|
|
2451
|
-
const minCacheKey = isBeta ? "_tsoMinRequiredBeta" : "_tsoMinRequiredStable";
|
|
2452
|
-
const minTag = npmMinRequiredTagForBuildEnv(isBeta ? "test" : "production");
|
|
2453
|
-
const cfg = readConfigRaw();
|
|
2454
|
-
const [latest, minRequired] = await Promise.all([
|
|
2455
|
-
fetchVersionByTag(tag, latestCacheKey, cfg),
|
|
2456
|
-
fetchVersionByTag(minTag, minCacheKey, cfg)
|
|
2457
|
-
]);
|
|
2458
|
-
writeConfigRaw({
|
|
2459
|
-
...cfg,
|
|
2460
|
-
_tsoLastVersionCheck: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2461
|
-
...latest ? { [latestCacheKey]: latest } : {},
|
|
2462
|
-
...minRequired ? { [minCacheKey]: minRequired } : {}
|
|
2463
|
-
});
|
|
2464
|
-
const lastNotified = cfg._tsoLastNotified ? new Date(cfg._tsoLastNotified).getTime() : 0;
|
|
2465
|
-
if (Date.now() - lastNotified < 24 * 60 * 60 * 1e3) return;
|
|
2466
|
-
const tagLabel = isBeta ? "\uFF08\u6D4B\u8BD5\u7248\uFF09" : "\uFF08\u6B63\u5F0F\u7248\uFF09";
|
|
2467
|
-
if (minRequired && isNewer(current, minRequired)) {
|
|
2468
|
-
writeConfigRaw({ ...cfg, _tsoLastNotified: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2469
|
-
console.log(
|
|
2470
|
-
`
|
|
2471
|
-
[SILUZAN_FORCE_UPDATE] \u5F53\u524D\u7248\u672C ${current} \u5DF2\u8FC7\u671F\uFF0C\u6700\u4F4E\u8981\u6C42\u7248\u672C ${minRequired}${tagLabel}
|
|
2472
|
-
\u26D4 \u5FC5\u987B\u5148\u5347\u7EA7\u624D\u80FD\u7EE7\u7EED\u4F7F\u7528\uFF0CAgent \u8BF7\u7ACB\u5373\u6267\u884C\uFF1Asiluzan-tso update
|
|
2473
|
-
\u6216\u624B\u52A8\uFF1Anpm install -g ${PKG_NAME}@${tag}
|
|
2474
|
-
`
|
|
2475
|
-
);
|
|
2476
|
-
return;
|
|
2477
|
-
}
|
|
2478
|
-
if (latest && isNewer(current, latest)) {
|
|
2479
|
-
writeConfigRaw({ ...cfg, _tsoLastNotified: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2480
|
-
console.log(
|
|
2481
|
-
`
|
|
2482
|
-
[SILUZAN_UPDATE_AVAILABLE] \u53D1\u73B0\u65B0\u7248\u672C ${latest}${tagLabel}\uFF08\u5F53\u524D ${current}\uFF09
|
|
2483
|
-
\u{1F4A1} Agent \u8BF7\u7ACB\u5373\u6267\u884C\uFF1Asiluzan-tso update
|
|
2484
|
-
\u6216\u624B\u52A8\uFF1Anpm install -g ${PKG_NAME}@${tag}
|
|
2485
|
-
`
|
|
2486
|
-
);
|
|
2487
|
-
}
|
|
2488
|
-
} catch {
|
|
2489
|
-
}
|
|
2490
|
-
}
|
|
2491
|
-
async function fetchLatestVersion() {
|
|
2492
|
-
const current = getCurrentVersion2();
|
|
2493
|
-
const tag = npmDistTagForCurrentVersion(current);
|
|
2494
|
-
return fetchNpmVersion(PKG_NAME, tag);
|
|
2495
|
-
}
|
|
2496
2209
|
|
|
2497
2210
|
// src/utils/auth.ts
|
|
2498
2211
|
function deriveMainApiUrl(tsoApiBaseUrl) {
|
|
@@ -2579,7 +2292,6 @@ function loadConfig(tokenArg) {
|
|
|
2579
2292
|
\u274C googleApiUrl \u4E0D\u5408\u6CD5\uFF1A${googleApiErr}`);
|
|
2580
2293
|
process.exit(1);
|
|
2581
2294
|
}
|
|
2582
|
-
setSiluzanCliMeta("siluzan-tso", getCurrentVersion2());
|
|
2583
2295
|
return {
|
|
2584
2296
|
apiBaseUrl,
|
|
2585
2297
|
authToken,
|
|
@@ -2696,26 +2408,26 @@ function cmdConfigClear() {
|
|
|
2696
2408
|
}
|
|
2697
2409
|
|
|
2698
2410
|
// src/commands/init.ts
|
|
2699
|
-
import * as
|
|
2411
|
+
import * as fs4 from "fs/promises";
|
|
2700
2412
|
import * as fsSync from "fs";
|
|
2701
|
-
import * as
|
|
2702
|
-
import * as
|
|
2413
|
+
import * as os2 from "os";
|
|
2414
|
+
import * as path4 from "path";
|
|
2703
2415
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2704
2416
|
|
|
2705
2417
|
// src/templates/load-templates.ts
|
|
2706
|
-
import * as
|
|
2707
|
-
import * as
|
|
2418
|
+
import * as fs3 from "fs/promises";
|
|
2419
|
+
import * as path3 from "path";
|
|
2708
2420
|
async function getSkillFiles(skillDir) {
|
|
2709
2421
|
const out = {};
|
|
2710
2422
|
async function walk(dir, prefix) {
|
|
2711
|
-
const entries = await
|
|
2423
|
+
const entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
2712
2424
|
for (const ent of entries) {
|
|
2713
2425
|
const rel = prefix ? `${prefix}/${ent.name}` : ent.name;
|
|
2714
|
-
const full =
|
|
2426
|
+
const full = path3.join(dir, ent.name);
|
|
2715
2427
|
if (ent.isDirectory()) {
|
|
2716
2428
|
await walk(full, rel);
|
|
2717
2429
|
} else {
|
|
2718
|
-
out[rel] = await
|
|
2430
|
+
out[rel] = await fs3.readFile(full, "utf8");
|
|
2719
2431
|
}
|
|
2720
2432
|
}
|
|
2721
2433
|
}
|
|
@@ -2724,14 +2436,14 @@ async function getSkillFiles(skillDir) {
|
|
|
2724
2436
|
}
|
|
2725
2437
|
|
|
2726
2438
|
// src/commands/init.ts
|
|
2727
|
-
var __dirname =
|
|
2439
|
+
var __dirname = path4.dirname(fileURLToPath2(import.meta.url));
|
|
2728
2440
|
var TARGET_DIRS = {
|
|
2729
|
-
cursor: (cwd) =>
|
|
2730
|
-
claude: (cwd) =>
|
|
2731
|
-
"openclaw-workspace": (cwd) =>
|
|
2732
|
-
"openclaw-global": (_cwd, home) =>
|
|
2733
|
-
"workbuddy-workspace": (cwd) =>
|
|
2734
|
-
"workbuddy-global": (_cwd, home) =>
|
|
2441
|
+
cursor: (cwd) => path4.join(cwd, ".cursor", "skills", "siluzan-tso"),
|
|
2442
|
+
claude: (cwd) => path4.join(cwd, ".claude", "skills", "siluzan-tso"),
|
|
2443
|
+
"openclaw-workspace": (cwd) => path4.join(cwd, "skills", "siluzan-tso"),
|
|
2444
|
+
"openclaw-global": (_cwd, home) => path4.join(home, ".openclaw", "skills", "siluzan-tso"),
|
|
2445
|
+
"workbuddy-workspace": (cwd) => path4.join(cwd, ".workbuddy", "skills", "siluzan-tso"),
|
|
2446
|
+
"workbuddy-global": (_cwd, home) => path4.join(home, ".workbuddy", "skills", "siluzan-tso")
|
|
2735
2447
|
};
|
|
2736
2448
|
function parseTargets(raw) {
|
|
2737
2449
|
const normalized = raw.trim().toLowerCase();
|
|
@@ -2772,32 +2484,32 @@ function parseTargets(raw) {
|
|
|
2772
2484
|
return [...new Set(result)];
|
|
2773
2485
|
}
|
|
2774
2486
|
function skillRoot() {
|
|
2775
|
-
return
|
|
2487
|
+
return path4.join(__dirname, "skill");
|
|
2776
2488
|
}
|
|
2777
2489
|
async function writeSkillFilesToDir(destDir, skillFiles, force) {
|
|
2778
|
-
await
|
|
2490
|
+
await fs4.mkdir(destDir, { recursive: true });
|
|
2779
2491
|
let anyWritten = false;
|
|
2780
2492
|
for (const [relativePath, content] of Object.entries(skillFiles)) {
|
|
2781
|
-
const fullPath =
|
|
2782
|
-
await
|
|
2493
|
+
const fullPath = path4.join(destDir, relativePath);
|
|
2494
|
+
await fs4.mkdir(path4.dirname(fullPath), { recursive: true });
|
|
2783
2495
|
try {
|
|
2784
|
-
await
|
|
2496
|
+
await fs4.access(fullPath);
|
|
2785
2497
|
if (!force) {
|
|
2786
2498
|
console.warn(`\u8DF3\u8FC7\uFF08\u5DF2\u5B58\u5728\uFF0C\u4F7F\u7528 --force \u8986\u76D6\uFF09: ${fullPath}`);
|
|
2787
2499
|
continue;
|
|
2788
2500
|
}
|
|
2789
2501
|
} catch {
|
|
2790
2502
|
}
|
|
2791
|
-
await
|
|
2503
|
+
await fs4.writeFile(fullPath, content, "utf8");
|
|
2792
2504
|
console.log(`\u5DF2\u5199\u5165: ${fullPath}`);
|
|
2793
2505
|
anyWritten = true;
|
|
2794
2506
|
}
|
|
2795
2507
|
return anyWritten;
|
|
2796
2508
|
}
|
|
2797
2509
|
function saveInstalledTargets(entries) {
|
|
2798
|
-
const CONFIG_FILE3 =
|
|
2510
|
+
const CONFIG_FILE3 = path4.join(os2.homedir(), ".siluzan", "config.json");
|
|
2799
2511
|
try {
|
|
2800
|
-
fsSync.mkdirSync(
|
|
2512
|
+
fsSync.mkdirSync(path4.dirname(CONFIG_FILE3), { recursive: true });
|
|
2801
2513
|
let existing = {};
|
|
2802
2514
|
if (fsSync.existsSync(CONFIG_FILE3)) {
|
|
2803
2515
|
existing = JSON.parse(fsSync.readFileSync(CONFIG_FILE3, "utf8"));
|
|
@@ -2819,11 +2531,11 @@ function saveInstalledTargets(entries) {
|
|
|
2819
2531
|
}
|
|
2820
2532
|
}
|
|
2821
2533
|
async function runInit(options) {
|
|
2822
|
-
const home =
|
|
2534
|
+
const home = os2.homedir();
|
|
2823
2535
|
const skillFiles = await getSkillFiles(skillRoot());
|
|
2824
2536
|
const installedEntries = [];
|
|
2825
2537
|
if (options.dir) {
|
|
2826
|
-
const destDir =
|
|
2538
|
+
const destDir = path4.resolve(options.cwd, options.dir);
|
|
2827
2539
|
console.log(`\u5B89\u88C5\u76EE\u6807\u76EE\u5F55\uFF1A${destDir}`);
|
|
2828
2540
|
const anyWritten = await writeSkillFilesToDir(destDir, skillFiles, options.force);
|
|
2829
2541
|
if (anyWritten) installedEntries.push({ target: "custom", cwd: "", dir: destDir });
|
|
@@ -2854,6 +2566,96 @@ async function runInit(options) {
|
|
|
2854
2566
|
import * as path6 from "path";
|
|
2855
2567
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2856
2568
|
import { spawnSync } from "child_process";
|
|
2569
|
+
|
|
2570
|
+
// src/utils/version.ts
|
|
2571
|
+
import * as fs5 from "fs";
|
|
2572
|
+
import * as path5 from "path";
|
|
2573
|
+
import * as os3 from "os";
|
|
2574
|
+
var PKG_NAME = "siluzan-tso-cli";
|
|
2575
|
+
var CONFIG_FILE2 = path5.join(os3.homedir(), ".siluzan", "config.json");
|
|
2576
|
+
function getCurrentVersion2() {
|
|
2577
|
+
return getCurrentVersion(import.meta.url);
|
|
2578
|
+
}
|
|
2579
|
+
function readConfigRaw() {
|
|
2580
|
+
try {
|
|
2581
|
+
return JSON.parse(fs5.readFileSync(CONFIG_FILE2, "utf8"));
|
|
2582
|
+
} catch {
|
|
2583
|
+
return {};
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
function writeConfigRaw(data) {
|
|
2587
|
+
try {
|
|
2588
|
+
fs5.mkdirSync(path5.dirname(CONFIG_FILE2), { recursive: true });
|
|
2589
|
+
fs5.writeFileSync(CONFIG_FILE2, JSON.stringify(data, null, 2), "utf8");
|
|
2590
|
+
if (process.platform !== "win32") {
|
|
2591
|
+
fs5.chmodSync(CONFIG_FILE2, 384);
|
|
2592
|
+
}
|
|
2593
|
+
} catch {
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
async function fetchVersionByTag(tag, cacheKey, cfg) {
|
|
2597
|
+
const hours24 = 24 * 60 * 60 * 1e3;
|
|
2598
|
+
if (cfg._tsoLastVersionCheck && cfg[cacheKey]) {
|
|
2599
|
+
const lastCheck = new Date(cfg._tsoLastVersionCheck).getTime();
|
|
2600
|
+
if (Date.now() - lastCheck < hours24) {
|
|
2601
|
+
return cfg[cacheKey];
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
return fetchNpmVersion(PKG_NAME, tag);
|
|
2605
|
+
}
|
|
2606
|
+
async function notifyIfOutdated() {
|
|
2607
|
+
try {
|
|
2608
|
+
const current = getCurrentVersion2();
|
|
2609
|
+
const tag = npmDistTagForCurrentVersion(current);
|
|
2610
|
+
const isBeta = tag === "beta";
|
|
2611
|
+
const latestCacheKey = isBeta ? "_tsoLatestBeta" : "_tsoLatestStable";
|
|
2612
|
+
const minCacheKey = isBeta ? "_tsoMinRequiredBeta" : "_tsoMinRequiredStable";
|
|
2613
|
+
const minTag = npmMinRequiredTagForBuildEnv(isBeta ? "test" : "production");
|
|
2614
|
+
const cfg = readConfigRaw();
|
|
2615
|
+
const [latest, minRequired] = await Promise.all([
|
|
2616
|
+
fetchVersionByTag(tag, latestCacheKey, cfg),
|
|
2617
|
+
fetchVersionByTag(minTag, minCacheKey, cfg)
|
|
2618
|
+
]);
|
|
2619
|
+
writeConfigRaw({
|
|
2620
|
+
...cfg,
|
|
2621
|
+
_tsoLastVersionCheck: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2622
|
+
...latest ? { [latestCacheKey]: latest } : {},
|
|
2623
|
+
...minRequired ? { [minCacheKey]: minRequired } : {}
|
|
2624
|
+
});
|
|
2625
|
+
const lastNotified = cfg._tsoLastNotified ? new Date(cfg._tsoLastNotified).getTime() : 0;
|
|
2626
|
+
if (Date.now() - lastNotified < 24 * 60 * 60 * 1e3) return;
|
|
2627
|
+
const tagLabel = isBeta ? "\uFF08\u6D4B\u8BD5\u7248\uFF09" : "\uFF08\u6B63\u5F0F\u7248\uFF09";
|
|
2628
|
+
if (minRequired && isNewer(current, minRequired)) {
|
|
2629
|
+
writeConfigRaw({ ...cfg, _tsoLastNotified: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2630
|
+
console.log(
|
|
2631
|
+
`
|
|
2632
|
+
[SILUZAN_FORCE_UPDATE] \u5F53\u524D\u7248\u672C ${current} \u5DF2\u8FC7\u671F\uFF0C\u6700\u4F4E\u8981\u6C42\u7248\u672C ${minRequired}${tagLabel}
|
|
2633
|
+
\u26D4 \u5FC5\u987B\u5148\u5347\u7EA7\u624D\u80FD\u7EE7\u7EED\u4F7F\u7528\uFF0CAgent \u8BF7\u7ACB\u5373\u6267\u884C\uFF1Asiluzan-tso update
|
|
2634
|
+
\u6216\u624B\u52A8\uFF1Anpm install -g ${PKG_NAME}@${tag}
|
|
2635
|
+
`
|
|
2636
|
+
);
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
if (latest && isNewer(current, latest)) {
|
|
2640
|
+
writeConfigRaw({ ...cfg, _tsoLastNotified: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2641
|
+
console.log(
|
|
2642
|
+
`
|
|
2643
|
+
[SILUZAN_UPDATE_AVAILABLE] \u53D1\u73B0\u65B0\u7248\u672C ${latest}${tagLabel}\uFF08\u5F53\u524D ${current}\uFF09
|
|
2644
|
+
\u{1F4A1} Agent \u8BF7\u7ACB\u5373\u6267\u884C\uFF1Asiluzan-tso update
|
|
2645
|
+
\u6216\u624B\u52A8\uFF1Anpm install -g ${PKG_NAME}@${tag}
|
|
2646
|
+
`
|
|
2647
|
+
);
|
|
2648
|
+
}
|
|
2649
|
+
} catch {
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
async function fetchLatestVersion() {
|
|
2653
|
+
const current = getCurrentVersion2();
|
|
2654
|
+
const tag = npmDistTagForCurrentVersion(current);
|
|
2655
|
+
return fetchNpmVersion(PKG_NAME, tag);
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
// src/commands/update.ts
|
|
2857
2659
|
var __dirname2 = path6.dirname(fileURLToPath3(import.meta.url));
|
|
2858
2660
|
var PKG_NAME2 = "siluzan-tso-cli";
|
|
2859
2661
|
function getSkillSourceDir() {
|
|
@@ -6247,7 +6049,7 @@ function adgroupListUrl(googleApiUrl, account, startDate, endDate) {
|
|
|
6247
6049
|
function requireGoogleApi(config) {
|
|
6248
6050
|
if (!config.googleApiUrl) {
|
|
6249
6051
|
console.error(
|
|
6250
|
-
"\n\u274C \u672A\u627E\u5230 Google API \u5730\u5740\uFF0C\
|
|
6052
|
+
"\n\u274C \u672A\u627E\u5230 Google API \u5730\u5740\u3002\n Google API \u5730\u5740\u4ECE TSO API \u5730\u5740\u81EA\u52A8\u63A8\u5BFC\uFF0C\u4E5F\u53EF\u901A\u8FC7\u73AF\u5883\u53D8\u91CF SILUZAN_GOOGLE_API \u8986\u76D6\u3002\n"
|
|
6251
6053
|
);
|
|
6252
6054
|
process.exit(1);
|
|
6253
6055
|
}
|
|
@@ -8320,7 +8122,7 @@ async function runOptimizeChildren(opts) {
|
|
|
8320
8122
|
}
|
|
8321
8123
|
|
|
8322
8124
|
// src/commands/forewarning.ts
|
|
8323
|
-
import
|
|
8125
|
+
import os4 from "os";
|
|
8324
8126
|
import path7 from "path";
|
|
8325
8127
|
import QRCode from "qrcode";
|
|
8326
8128
|
import open from "open";
|
|
@@ -8529,7 +8331,7 @@ async function runForewarningNotifyAccounts(opts) {
|
|
|
8529
8331
|
console.log(" \u901A\u77E5\u6E20\u9053\uFF1A\u4E1D\u8DEF\u8D5E\u5E73\u53F0\u5FAE\u4FE1\u670D\u52A1\u53F7\uFF08\u9700\u626B\u7801\u5173\u6CE8\u540E\u624D\u80FD\u6536\u5230\u9884\u8B66\u901A\u77E5\uFF09\n");
|
|
8530
8332
|
if (qrLink) {
|
|
8531
8333
|
try {
|
|
8532
|
-
const imgPath = path7.join(
|
|
8334
|
+
const imgPath = path7.join(os4.tmpdir(), "siluzan-wechat-qr.png");
|
|
8533
8335
|
await QRCode.toFile(imgPath, qrLink, { width: 300 });
|
|
8534
8336
|
await open(imgPath);
|
|
8535
8337
|
console.log(` \u{1F4F7} \u4E8C\u7EF4\u7801\u5DF2\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\uFF0C\u8BF7\u7528\u624B\u673A\u5FAE\u4FE1\u626B\u7801\u5173\u6CE8"\u4E1D\u8DEF\u8D5E\u5E73\u53F0"\u670D\u52A1\u53F7
|
|
@@ -8764,7 +8566,7 @@ Meta \u7EBF\u7D22\u8868\u5355\uFF08\u8D26\u6237 ${opts.account}\uFF0C\u7B2C 1 \u
|
|
|
8764
8566
|
function requireGoogleApi2(config) {
|
|
8765
8567
|
if (!config.googleApiUrl) {
|
|
8766
8568
|
console.error(
|
|
8767
|
-
"\n\u274C \u672A\u627E\u5230 Google API \u5730\u5740\uFF0C\
|
|
8569
|
+
"\n\u274C \u672A\u627E\u5230 Google API \u5730\u5740\u3002\n Google API \u5730\u5740\u4ECE TSO API \u5730\u5740\u81EA\u52A8\u63A8\u5BFC\uFF0C\u4E5F\u53EF\u901A\u8FC7\u73AF\u5883\u53D8\u91CF SILUZAN_GOOGLE_API \u8986\u76D6\u3002\n"
|
|
8768
8570
|
);
|
|
8769
8571
|
process.exit(1);
|
|
8770
8572
|
}
|
|
@@ -10943,7 +10745,6 @@ async function runLogin(opts = {}) {
|
|
|
10943
10745
|
process.exit(1);
|
|
10944
10746
|
}
|
|
10945
10747
|
writeSharedConfig({ apiKey: key });
|
|
10946
|
-
refreshSiluzanUser(DEFAULT_API_BASE, { authToken: "", apiKey: key });
|
|
10947
10748
|
console.log(`
|
|
10948
10749
|
\u2705 API Key \u5DF2\u4FDD\u5B58\uFF08${maskSecret(key)}\uFF09`);
|
|
10949
10750
|
console.log(` \u914D\u7F6E\u6587\u4EF6\uFF1A${CONFIG_FILE}`);
|
|
@@ -10995,7 +10796,6 @@ async function runLogin(opts = {}) {
|
|
|
10995
10796
|
process.exit(1);
|
|
10996
10797
|
}
|
|
10997
10798
|
writeSharedConfig({ apiKey });
|
|
10998
|
-
refreshSiluzanUser(DEFAULT_API_BASE, { authToken: "", apiKey });
|
|
10999
10799
|
console.log(`
|
|
11000
10800
|
\u2705 API Key \u5DF2\u4FDD\u5B58\uFF08${maskSecret(apiKey)}\uFF09`);
|
|
11001
10801
|
console.log(` \u914D\u7F6E\u6587\u4EF6\uFF1A${CONFIG_FILE}`);
|
|
@@ -11005,6 +10805,19 @@ async function runLogin(opts = {}) {
|
|
|
11005
10805
|
}
|
|
11006
10806
|
|
|
11007
10807
|
// src/index.ts
|
|
10808
|
+
process.on("uncaughtException", (err) => {
|
|
10809
|
+
console.error(`
|
|
10810
|
+
\u274C \u672A\u6355\u83B7\u7684\u5F02\u5E38\uFF1A${err.message}`);
|
|
10811
|
+
if (process.argv.includes("--verbose")) console.error(err.stack);
|
|
10812
|
+
process.exit(1);
|
|
10813
|
+
});
|
|
10814
|
+
process.on("unhandledRejection", (reason) => {
|
|
10815
|
+
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
10816
|
+
console.error(`
|
|
10817
|
+
\u274C \u672A\u5904\u7406\u7684\u5F02\u6B65\u9519\u8BEF\uFF1A${msg}`);
|
|
10818
|
+
if (process.argv.includes("--verbose") && reason instanceof Error) console.error(reason.stack);
|
|
10819
|
+
process.exit(1);
|
|
10820
|
+
});
|
|
11008
10821
|
function getVersion() {
|
|
11009
10822
|
try {
|
|
11010
10823
|
const __dirname3 = path9.dirname(fileURLToPath4(import.meta.url));
|
|
@@ -11020,7 +10833,6 @@ program.name("siluzan-tso").description(
|
|
|
11020
10833
|
"Siluzan \u5E7F\u544A\u8D26\u6237\u7BA1\u7406\uFF1A\u8D26\u6237\u67E5\u8BE2\u3001\u4F59\u989D\u3001\u6295\u653E\u6570\u636E\u3001\u5F00\u6237\u7533\u8BF7\uFF08Google/TikTok/Yandex/Bing/Kwai\uFF09\u3001\n\u8D26\u53F7\u5206\u4EAB/\u89E3\u7ED1\u3001\u4F18\u5316\u62A5\u544A\u3001Google \u8D26\u6237\u5206\u6790\u7F51\u5173\uFF08google-analysis\uFF09\u3001\u5145\u503C\u8F6C\u8D26\u3001\u5F00\u7968\u3001\u667A\u80FD\u9884\u8B66\u3001Google \u5E7F\u544A\u7BA1\u7406\uFF08\u542B\u5F02\u6B65\u6279\u91CF\uFF09\u3002"
|
|
11021
10834
|
).version(getVersion());
|
|
11022
10835
|
program.hook("preAction", async () => {
|
|
11023
|
-
setSiluzanCliInvocation(redactCliArgvForSentry(process.argv));
|
|
11024
10836
|
const activeCmd = process.argv[2];
|
|
11025
10837
|
if (activeCmd !== "update" && activeCmd !== "login") {
|
|
11026
10838
|
await notifyIfOutdated().catch(() => {
|
|
@@ -11582,7 +11394,7 @@ program.command("clue").description("\u67E5\u8BE2\u5E7F\u544A\u7EBF\u7D22\u8868\
|
|
|
11582
11394
|
});
|
|
11583
11395
|
});
|
|
11584
11396
|
var adCmd = program.command("ad").description(
|
|
11585
|
-
"Google \u5E7F\u544A\u7BA1\u7406\
|
|
11397
|
+
"Google \u5E7F\u544A\u7BA1\u7406\uFF08Google API \u5730\u5740\u4ECE TSO API \u81EA\u52A8\u63A8\u5BFC\uFF0C\u53EF\u901A\u8FC7 SILUZAN_GOOGLE_API \u73AF\u5883\u53D8\u91CF\u8986\u76D6\uFF09\n \u542B\uFF1A\u7CFB\u5217/\u7EC4/\u521B\u610F/\u5173\u952E\u8BCD\u7B49\u5E38\u89C4\u5E7F\u544A\u6295\u653E\u4E0E\u7BA1\u7406\u80FD\u529B"
|
|
11586
11398
|
);
|
|
11587
11399
|
var adBatchCmd = adCmd.command("batch").description("\u5F02\u6B65\u6279\u91CF\u521B\u5EFA\u4EFB\u52A1\u4E0E\u8349\u7A3F\uFF08\u67E5\u8BE2\u8BE6\u60C5\u3001\u53D1\u5E03\u3001\u66F4\u65B0\uFF1B\u4E0E ad campaign-create \u540C\u6E90\uFF09");
|
|
11588
11400
|
adBatchCmd.command("list").description("\u67E5\u8BE2\u6279\u91CF\u521B\u5EFA / \u667A\u6295\u8BB0\u5F55\u5217\u8868").option("-s, --state <state>", "\u72B6\u6001\uFF1ACreating | Successfully | Failed | HasFailed | Unpublished").option("--customer-id <id>", "\u5BA2\u6237 ID").option("--customer-name <name>", "\u5BA2\u6237\u540D\u79F0").option("-k, --keyword <text>", "\u5173\u952E\u5B57\u641C\u7D22").option("--start <date>", "\u521B\u5EFA\u5F00\u59CB\u65E5\u671F YYYY-MM-DD").option("--end <date>", "\u521B\u5EFA\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("-p, --page <n>", "\u9875\u7801", parseInt).option("--page-size <n>", "\u6BCF\u9875\u6570\u91CF", parseInt).option("-t, --token <token>", "Auth Token").option("--json", "JSON \u683C\u5F0F\u8F93\u51FA", false).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
|
|
@@ -12190,7 +12002,7 @@ geoCmd.command("remove").description("\u79FB\u9664\u5730\u7406\u4F4D\u7F6E\u5B9A
|
|
|
12190
12002
|
verbose: opts.verbose
|
|
12191
12003
|
});
|
|
12192
12004
|
});
|
|
12193
|
-
program.command("keyword").description("Google \u5173\u952E\u5B57\u63A8\u8350\
|
|
12005
|
+
program.command("keyword").description("Google \u5173\u952E\u5B57\u63A8\u8350\uFF08Google API \u5730\u5740\u4ECE TSO API \u81EA\u52A8\u63A8\u5BFC\uFF0C\u53EF\u901A\u8FC7 SILUZAN_GOOGLE_API \u73AF\u5883\u53D8\u91CF\u8986\u76D6\uFF09").requiredOption("-k, --keyword <words>", "\u641C\u7D22\u8BCD\uFF0C\u591A\u4E2A\u9017\u53F7\u5206\u9694").option("--url <url>", "\u516C\u53F8/\u4EA7\u54C1\u7F51\u5740\uFF08\u586B\u5199\u540E\u89E6\u53D1\u7F51\u5740\u62D3\u8BCD\uFF09").option("--include <words>", "\u7ED3\u679C\u5FC5\u987B\u5305\u542B\u7684\u8BCD\uFF0C\u9017\u53F7\u6216\u7A7A\u683C\u5206\u9694").option("--exclude <words>", "\u7ED3\u679C\u4E0D\u5305\u542B\u7684\u8BCD\uFF0C\u9017\u53F7\u6216\u7A7A\u683C\u5206\u9694").option("-t, --token <token>", "Auth Token").option("--json", "JSON \u683C\u5F0F\u8F93\u51FA", false).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
|
|
12194
12006
|
await runKeywordSuggest({
|
|
12195
12007
|
token: opts.token,
|
|
12196
12008
|
keywords: opts.keyword.split(",").map((s) => s.trim()).filter(Boolean),
|
package/dist/skill/SKILL.md
CHANGED
|
@@ -5,8 +5,47 @@ description: 当判断用户可能需要以下功能时可以使用siluzan-tso
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
# Siluzan TSO Skill
|
|
8
|
+
|
|
8
9
|
本 Skill 只保留任务边界、文档路由与执行规则。具体业务细节、参数、模板、流程与示例均以下方 references 文档为准。遇到具体业务时,先读对应 references
|
|
9
10
|
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 前置条件与运行时依赖
|
|
14
|
+
|
|
15
|
+
使用本 Skill 前,以下组件必须已安装并就绪:
|
|
16
|
+
|
|
17
|
+
### 必需二进制 / 运行时
|
|
18
|
+
| 依赖 | 最低版本 | 用途 |
|
|
19
|
+
|------|---------|------|
|
|
20
|
+
| **Node.js** | 18+ | CLI 执行运行时以及 `node -e` 数据过滤 |
|
|
21
|
+
| **npm** 或 **pnpm** | npm 8+ / pnpm 8+ | 安装 `siluzan-tso-cli` 全局包 |
|
|
22
|
+
| **siluzan-tso** CLI | 与 Skill 同版本 | 所有业务操作的执行入口(由 `npm install -g siluzan-tso-cli` 安装) |
|
|
23
|
+
|
|
24
|
+
### 凭据与配置文件
|
|
25
|
+
| 项目 | 位置 | 说明 |
|
|
26
|
+
|------|------|------|
|
|
27
|
+
| API Key 或 Token | `~/.siluzan/config.json` 中的 `apiKey` 或 `token` 字段 | 用于所有 TSO API 调用的鉴权。API Key 在丝路赞控制台「个人设置 → API Key 管理」创建。**请勿将此文件提交到 Git。** |
|
|
28
|
+
| tsoApiBaseUrl | `~/.siluzan/config.json` 中的 `apiBaseUrl` | TSO 后端 API 地址,默认 `https://tso-api.siluzan.com`(生产)或 `https://tso-api-ci.siluzan.com`(测试) |
|
|
29
|
+
| googleApiUrl | `~/.siluzan/config.json` 中的 `googleApiUrl` | Google 广告 API 网关,默认 `https://googleapi.mysiluzan.com`(从 tsoApiBaseUrl 自动推导) |
|
|
30
|
+
|
|
31
|
+
### 网络端点
|
|
32
|
+
本 Skill 通过 CLI 访问以下远程服务:
|
|
33
|
+
| 端点 | 用途 |
|
|
34
|
+
|------|------|
|
|
35
|
+
| `tso-api.siluzan.com` / `tso-api-ci.siluzan.com` | TSO 核心 API(账户、报告、预警、开票等) |
|
|
36
|
+
| `googleapi.mysiluzan.com` / `googleapi-ci.mysiluzan.com` | Google Ads API 网关(广告 CRUD、关键词、地理定位) |
|
|
37
|
+
| `www.siluzan.com` / `www-ci.siluzan.com` | 前端页面(充值/激活等需浏览器操作时引导用户打开) |
|
|
38
|
+
| `o605862.ingest.us.sentry.io` | 错误追踪(Sentry,仅发送匿名崩溃日志,不含业务数据) |
|
|
39
|
+
| `registry.npmjs.org` | 版本更新检查 |
|
|
40
|
+
|
|
41
|
+
### 可选环境变量
|
|
42
|
+
| 变量 | 说明 |
|
|
43
|
+
|------|------|
|
|
44
|
+
| `SILUZAN_GOOGLE_API` | 覆盖默认 Google API 网关地址 |
|
|
45
|
+
| `SILUZAN_API_KEY` | 从环境变量读取 API Key(优先级高于 config.json) |
|
|
46
|
+
|
|
47
|
+
如果上述依赖缺失,请先参照 `references/setup.md` 完成安装与配置。
|
|
48
|
+
|
|
10
49
|
## 功能以及对应文档
|
|
11
50
|
|
|
12
51
|
本skill包含以下功能,实现用户要求时,请先阅读对应功能文档:
|
|
@@ -54,9 +93,9 @@ description: 当判断用户可能需要以下功能时可以使用siluzan-tso
|
|
|
54
93
|
1. 计划阶段:
|
|
55
94
|
- 根据功能以及对应文档读取对应references
|
|
56
95
|
- 根据references文件中的内容配合命令行工具提供的-h参数,来确认命令行的正确调用方式
|
|
57
|
-
-
|
|
58
|
-
2.
|
|
59
|
-
3.
|
|
96
|
+
- 向用户输出一份操作计划,简要说明每一步将做什么
|
|
97
|
+
2. 确认阶段:与用户确认关键信息(尤其是涉及写入/修改/删除的操作)
|
|
98
|
+
3. 执行阶段:按计划执行,向用户说明每步操作意图
|
|
60
99
|
4. 验证阶段:
|
|
61
100
|
- 一般情况下,读取/写入的命令都是成对的。通过这两种命令的配合来进行结果验证
|
|
62
101
|
- 如果是异步任务,需要你轮询读取命令,每5s一次查看任务状态,直到确认所有异步任务都有一个结果
|
|
@@ -72,15 +111,25 @@ description: 当判断用户可能需要以下功能时可以使用siluzan-tso
|
|
|
72
111
|
- **使用 --json 处理数据**:需对返回数据做计算或筛选时,加 `--json`,再用 `node -e` 过滤提取(见 `references/tips.md`)。
|
|
73
112
|
- **不要猜测账户 ID**:`entityId` ≠ `mediaCustomerId`,两者均来自 `list-accounts`。
|
|
74
113
|
- **媒体类型区分大小写**:`Google`、`TikTok`、`MetaAd`、`BingV2`、`Kwai`。
|
|
75
|
-
-
|
|
114
|
+
- **命令透明性**:以简洁的方式向用户说明即将执行的操作意图(如「正在查询您的 Google 账户列表」「正在为账户 xxx 创建预警规则」),让用户了解操作进度。用户主动要求查看执行细节时,应如实提供完整命令。
|
|
76
115
|
- **具体业务的额外规范**:开户、优化、报告、Google 广告创建等场景的详细约束,请分别在执行前阅读对应的 `references/*.md` 文档。
|
|
77
116
|
- **完成写/修改/编辑/更新等操作后需要确认数据是否正确**
|
|
78
117
|
|
|
79
|
-
###
|
|
118
|
+
### 必须一定要遵守的规范
|
|
80
119
|
|
|
81
|
-
- 在使用本skill输出的任何内容中,包括但不限于报告,计划,总结,严禁让用户执行本skill提供的命令行。(setup.md)中的命令除外
|
|
82
120
|
- 主动更新(详情请读取`references/setup.md`)
|
|
83
121
|
- 所有使用本skill获取的数据输出的报告,总结,分析性的内容,都需要通过skill中内置模板的格式来输出
|
|
122
|
+
- **破坏性操作必须用户确认**:执行以下操作前,必须明确告知用户操作内容并获得确认:
|
|
123
|
+
- 账户解绑(`account delink`)
|
|
124
|
+
- 账户关闭(`account close`)— 不可恢复
|
|
125
|
+
- 取消分享(`account unshare`)
|
|
126
|
+
- BC/MCC 解绑(`account bc-unbind` / `account mcc-unbind`)
|
|
127
|
+
- 删除预警规则(`forewarning delete`)
|
|
128
|
+
- 删除报告(`report delete`)
|
|
129
|
+
- 删除广告/关键词(`ad ad-delete` / `ad keyword-negative-delete`)
|
|
130
|
+
- 发票申请(`invoice apply`)— 涉及财务
|
|
131
|
+
- 广告发布(`ad batch publish` / `ad campaign-create`)— 涉及预算消耗
|
|
132
|
+
- **只读操作可自主执行**:查询类命令(`list-accounts`、`balance`、`stats`、`report list`、`config show` 等)可直接执行,无需额外确认
|
|
84
133
|
|
|
85
134
|
## 一些tips
|
|
86
135
|
|
|
@@ -93,6 +142,15 @@ Bing: 138xxx763, 1882xxx80
|
|
|
93
142
|
Yandex: porg-uthxxxrk
|
|
94
143
|
Kwai: act_1716030xxx734076
|
|
95
144
|
|
|
145
|
+
### 报告模板外部资源声明
|
|
146
|
+
HTML 报告模板(`report-templates/*.html`)引用了以下外部 CDN 资源,渲染 HTML 报告时浏览器会发出对应的网络请求:
|
|
147
|
+
- `cdn.tailwindcss.com` — Tailwind CSS(布局样式)
|
|
148
|
+
- `cdnjs.cloudflare.com` — Font Awesome 6(图标)
|
|
149
|
+
- `cdn.jsdelivr.net` — ECharts 5(图表)
|
|
150
|
+
- `fonts.googleapis.com` / `fonts.gstatic.com` — Google Fonts(仅 academic 模板)
|
|
151
|
+
|
|
152
|
+
如果处于离线环境或需要避免外部网络请求,请将上述资源下载到本地后修改模板中的引用路径。
|
|
153
|
+
|
|
96
154
|
### 容易出错http状态码
|
|
97
155
|
- 400 Bad Request
|
|
98
156
|
参数错误,请你查看对应功能reference或使用-h了解命令行如何使用
|
package/dist/skill/_meta.json
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
# 安装与配置
|
|
2
2
|
|
|
3
|
+
## 前置条件
|
|
4
|
+
|
|
5
|
+
| 依赖 | 版本 | 用途 |
|
|
6
|
+
|------|------|------|
|
|
7
|
+
| Node.js | 18+ | CLI 运行时及 `node -e` 数据过滤 |
|
|
8
|
+
| npm 或 pnpm | npm 8+ / pnpm 8+ | 安装全局包 |
|
|
9
|
+
|
|
3
10
|
## 安装 CLI
|
|
4
11
|
|
|
5
12
|
```bash
|
|
6
13
|
npm install -g siluzan-tso-cli@beta
|
|
7
14
|
```
|
|
8
15
|
|
|
9
|
-
环境要求:Node.js 18+
|
|
10
|
-
|
|
11
16
|
---
|
|
12
17
|
|
|
13
18
|
## 初始化 Skill 文件
|
|
@@ -80,7 +85,8 @@ siluzan-tso config show
|
|
|
80
85
|
|
|
81
86
|
```bash
|
|
82
87
|
siluzan-tso config set --api-base <url> # 切换 TSO API 地址
|
|
83
|
-
|
|
88
|
+
# Google API 地址从 TSO API 自动推导,如需覆盖可设置环境变量:
|
|
89
|
+
# export SILUZAN_GOOGLE_API=<url>
|
|
84
90
|
siluzan-tso config clear # 清空所有凭据
|
|
85
91
|
```
|
|
86
92
|
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
风格:专业学术(论文式层级、衬线标题、低饱和配色、弱装饰、表格偏「期刊」)
|
|
4
4
|
与 report-template.html 区块结构、data-section-id 一致,可互换填充数据。
|
|
5
5
|
另见:report-template.html(商务)、report-template-formal.html(正式公文)
|
|
6
|
+
|
|
7
|
+
⚠ 外部网络声明:本模板引用 cdn.tailwindcss.com、cdnjs.cloudflare.com、cdn.jsdelivr.net、fonts.googleapis.com 加载样式/脚本/字体。
|
|
8
|
+
如需离线使用,请将对应资源下载到本地并替换 <script>/<link> 的 src/href。
|
|
6
9
|
-->
|
|
7
10
|
<html lang="zh-CN">
|
|
8
11
|
<head>
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
<!--
|
|
3
3
|
风格:演示 / 深色屏显(会议室投屏、深色控制台并列)
|
|
4
4
|
与 report-template.html 的 data-section-id 一致。另见 README 中其它模板。
|
|
5
|
+
|
|
6
|
+
⚠ 外部网络声明:本模板引用 cdn.tailwindcss.com、cdnjs.cloudflare.com、cdn.jsdelivr.net 加载样式/脚本。
|
|
7
|
+
如需离线使用,请将对应资源下载到本地并替换 <script>/<link> 的 src/href。
|
|
5
8
|
-->
|
|
6
9
|
<html lang="zh-CN">
|
|
7
10
|
<head>
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
风格:正式文件 / 公文式(红头线、居中标题、黑框表格、中文序号「一、二、」、少彩色装饰)
|
|
4
4
|
与 report-template.html 区块结构、data-section-id 一致,可互换填充数据。
|
|
5
5
|
另见:report-template.html(商务)、report-template-academic.html(学术)
|
|
6
|
+
|
|
7
|
+
⚠ 外部网络声明:本模板引用 cdn.tailwindcss.com、cdnjs.cloudflare.com、cdn.jsdelivr.net 加载样式/脚本。
|
|
8
|
+
如需离线使用,请将对应资源下载到本地并替换 <script>/<link> 的 src/href。
|
|
6
9
|
-->
|
|
7
10
|
<html lang="zh-CN">
|
|
8
11
|
<head>
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
<!--
|
|
3
3
|
风格:移动端优先(窄屏单列、大触控留白、表格横向滑动提示)
|
|
4
4
|
与 report-template.html 的 data-section-id 一致;桌面端随 sm: 断点渐进为多列。
|
|
5
|
+
|
|
6
|
+
⚠ 外部网络声明:本模板引用 cdn.tailwindcss.com、cdnjs.cloudflare.com、cdn.jsdelivr.net 加载样式/脚本。
|
|
7
|
+
如需离线使用,请将对应资源下载到本地并替换 <script>/<link> 的 src/href。
|
|
5
8
|
-->
|
|
6
9
|
<html lang="zh-CN">
|
|
7
10
|
<head>
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
<!--
|
|
3
3
|
风格:对客营销单页(Executive one-pager)
|
|
4
4
|
首屏叙事 + 主 KPI + 主图;第 2–6 节默认折叠(<details>),展开后结构与 report-template.html 一致、data-section-id 不变。
|
|
5
|
+
|
|
6
|
+
⚠ 外部网络声明:本模板引用 cdn.tailwindcss.com、cdnjs.cloudflare.com、cdn.jsdelivr.net 加载样式/脚本。
|
|
7
|
+
如需离线使用,请将对应资源下载到本地并替换 <script>/<link> 的 src/href。
|
|
5
8
|
-->
|
|
6
9
|
<html lang="zh-CN">
|
|
7
10
|
<head>
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
<!--
|
|
3
3
|
风格:极简黑白 / 打印优先(灰阶、无阴影、无圆角装饰、状态不完全依赖色相)
|
|
4
4
|
与 report-template.html 的 data-section-id 一致。打印时图表仍为灰度;正式印刷可导出 PDF 后转灰。
|
|
5
|
+
|
|
6
|
+
⚠ 外部网络声明:本模板引用 cdn.tailwindcss.com、cdnjs.cloudflare.com、cdn.jsdelivr.net 加载样式/脚本。
|
|
7
|
+
如需离线使用,请将对应资源下载到本地并替换 <script>/<link> 的 src/href。
|
|
5
8
|
-->
|
|
6
9
|
<html lang="zh-CN">
|
|
7
10
|
<head>
|
|
@@ -22,9 +22,12 @@
|
|
|
22
22
|
- 区块 ID(如 section-kpi-snapshot) → section 上 data-section-id,便于锚点与自动化测试
|
|
23
23
|
|
|
24
24
|
技术栈(均支持 CDN,单文件可离线打开需网络加载资源):
|
|
25
|
-
- Tailwind CSS:布局、间距、响应式、打印友好浅色底
|
|
26
|
-
- Font Awesome 6:列表与状态图标(类名稳定,便于 AI 输出)
|
|
27
|
-
- ECharts 5:时序、构成、对比(与广告报表 JSON 数组结构天然契合)
|
|
25
|
+
- Tailwind CSS (cdn.tailwindcss.com):布局、间距、响应式、打印友好浅色底
|
|
26
|
+
- Font Awesome 6 (cdnjs.cloudflare.com):列表与状态图标(类名稳定,便于 AI 输出)
|
|
27
|
+
- ECharts 5 (cdn.jsdelivr.net):时序、构成、对比(与广告报表 JSON 数组结构天然契合)
|
|
28
|
+
|
|
29
|
+
⚠ 外部网络声明:在浏览器中打开本 HTML 文件时,会向上述 CDN 域名发出网络请求加载样式/脚本。
|
|
30
|
+
如需离线使用,请将对应资源下载到本地并替换下方 <script>/<link> 的 src/href。
|
|
28
31
|
-->
|
|
29
32
|
<html lang="zh-CN">
|
|
30
33
|
<head>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "siluzan-tso-cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.9-beta.2",
|
|
4
4
|
"description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -40,7 +40,31 @@
|
|
|
40
40
|
"siluzan-cli-common": "1.0.0"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
|
43
|
-
"node": ">=18"
|
|
43
|
+
"node": ">=18",
|
|
44
|
+
"npm": ">=8"
|
|
45
|
+
},
|
|
46
|
+
"skillRequirements": {
|
|
47
|
+
"binaries": [
|
|
48
|
+
"node",
|
|
49
|
+
"npm"
|
|
50
|
+
],
|
|
51
|
+
"configFiles": [
|
|
52
|
+
"~/.siluzan/config.json"
|
|
53
|
+
],
|
|
54
|
+
"credentials": [
|
|
55
|
+
"apiKey or token in ~/.siluzan/config.json"
|
|
56
|
+
],
|
|
57
|
+
"networkEndpoints": [
|
|
58
|
+
"tso-api.siluzan.com",
|
|
59
|
+
"googleapi.mysiluzan.com",
|
|
60
|
+
"www.siluzan.com",
|
|
61
|
+
"o605862.ingest.us.sentry.io",
|
|
62
|
+
"registry.npmjs.org"
|
|
63
|
+
],
|
|
64
|
+
"optionalEnvVars": [
|
|
65
|
+
"SILUZAN_GOOGLE_API",
|
|
66
|
+
"SILUZAN_API_KEY"
|
|
67
|
+
]
|
|
44
68
|
},
|
|
45
69
|
"scripts": {
|
|
46
70
|
"postinstall": "node scripts/postinstall.mjs",
|