siluzan-tso-cli 1.0.0-beta.21 → 1.0.0-beta.23
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 +1 -1
- package/dist/index.js +573 -163
- package/dist/skill/SKILL.md +23 -0
- package/dist/skill/_meta.json +2 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ siluzan-tso init -d /path/to/skills # 写入自定义目录
|
|
|
20
20
|
siluzan-tso init --force # 强制覆盖已存在文件
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
> **注意**:当前为测试版(1.0.0-beta.
|
|
23
|
+
> **注意**:当前为测试版(1.0.0-beta.23),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
24
24
|
|
|
25
25
|
| 助手 | 建议 `--ai` |
|
|
26
26
|
|------|-------------|
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import * as path from "path";
|
|
|
17
17
|
import * as os from "os";
|
|
18
18
|
import * as https from "https";
|
|
19
19
|
import * as http from "http";
|
|
20
|
+
import * as os2 from "os";
|
|
20
21
|
import * as fs2 from "fs";
|
|
21
22
|
import * as path2 from "path";
|
|
22
23
|
import { fileURLToPath } from "url";
|
|
@@ -130,6 +131,412 @@ function rawRequest(url, options) {
|
|
|
130
131
|
req.end();
|
|
131
132
|
});
|
|
132
133
|
}
|
|
134
|
+
var DEFAULT_SENTRY_DSN = "https://bafcf42aab6fe7b485310619ae041b5e@o4510436169285632.ingest.us.sentry.io/4511103054708736";
|
|
135
|
+
function isSentryDisabled() {
|
|
136
|
+
return process.env.SILUZAN_SENTRY_DISABLED === "1" || process.env.SILUZAN_SENTRY_DISABLED === "true";
|
|
137
|
+
}
|
|
138
|
+
function getDsn() {
|
|
139
|
+
return process.env.SILUZAN_SENTRY_DSN?.trim() || DEFAULT_SENTRY_DSN;
|
|
140
|
+
}
|
|
141
|
+
var cliMeta = { name: "siluzan-cli", version: "unknown" };
|
|
142
|
+
var cliInvocation = "";
|
|
143
|
+
function setSiluzanCliInvocation(redactedCommandLine) {
|
|
144
|
+
cliInvocation = redactedCommandLine;
|
|
145
|
+
}
|
|
146
|
+
function redactCliArgvForSentry(argv) {
|
|
147
|
+
const args = argv.slice(2);
|
|
148
|
+
const redactNext = /* @__PURE__ */ new Set(["-t", "--token", "--api-key", "--password"]);
|
|
149
|
+
const out = [];
|
|
150
|
+
for (let i = 0; i < args.length; i++) {
|
|
151
|
+
const a = args[i];
|
|
152
|
+
if (redactNext.has(a)) {
|
|
153
|
+
out.push(a, "***");
|
|
154
|
+
i++;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const eq = a.indexOf("=");
|
|
158
|
+
if (eq > 0) {
|
|
159
|
+
const key = a.slice(0, eq).toLowerCase();
|
|
160
|
+
if (key === "--token" || key === "--api-key" || key === "--password") {
|
|
161
|
+
out.push(`${a.slice(0, eq)}=***`);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (/^-t[^-]/.test(a) && a.length > 3) {
|
|
166
|
+
out.push("-t***");
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
out.push(a);
|
|
170
|
+
}
|
|
171
|
+
return out.join(" ");
|
|
172
|
+
}
|
|
173
|
+
function isApiTrackingEnabled() {
|
|
174
|
+
const v = process.env.SILUZAN_SENTRY_TRACKING?.trim().toLowerCase();
|
|
175
|
+
if (v === "0" || v === "false" || v === "off" || v === "no") return false;
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
function setSiluzanCliMeta(name, version) {
|
|
179
|
+
cliMeta = { name, version };
|
|
180
|
+
if (sentryReady) {
|
|
181
|
+
void import("@sentry/node").then((Sentry) => {
|
|
182
|
+
Sentry.setTag("cli", name);
|
|
183
|
+
Sentry.setTag("cli_version", version);
|
|
184
|
+
}).catch(() => {
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
var sentryReady = false;
|
|
189
|
+
var sentryInitPromise = null;
|
|
190
|
+
var flushHookRegistered = false;
|
|
191
|
+
function buildRuntimeContext() {
|
|
192
|
+
const platform = process.platform;
|
|
193
|
+
const osName = platform === "win32" ? "Windows" : platform === "darwin" ? "macOS" : "Linux";
|
|
194
|
+
return {
|
|
195
|
+
os: {
|
|
196
|
+
name: osName,
|
|
197
|
+
version: os2.release(),
|
|
198
|
+
// 内核版本,如 10.0.26100(Win11)、24.3.0(macOS)
|
|
199
|
+
arch: process.arch
|
|
200
|
+
// x64 / arm64
|
|
201
|
+
},
|
|
202
|
+
runtime: {
|
|
203
|
+
node_version: process.version,
|
|
204
|
+
// v18.x.x / v20.x.x
|
|
205
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
async function ensureSentryInitialized(requestUrl) {
|
|
210
|
+
const dsn = getDsn();
|
|
211
|
+
if (!dsn || isSentryDisabled()) return false;
|
|
212
|
+
if (sentryReady) return true;
|
|
213
|
+
if (!sentryInitPromise) {
|
|
214
|
+
sentryInitPromise = (async () => {
|
|
215
|
+
const Sentry = await import("@sentry/node");
|
|
216
|
+
const envOverride = process.env.SILUZAN_SENTRY_ENV?.trim();
|
|
217
|
+
const environment = envOverride || (inferSiluzanRuntimeEnvironment(requestUrl) === "test" ? "test" : "production");
|
|
218
|
+
Sentry.init({
|
|
219
|
+
dsn,
|
|
220
|
+
environment,
|
|
221
|
+
sendDefaultPii: true,
|
|
222
|
+
tracesSampleRate: 0
|
|
223
|
+
});
|
|
224
|
+
const ctx = buildRuntimeContext();
|
|
225
|
+
Sentry.setContext("os", ctx.os);
|
|
226
|
+
Sentry.setContext("runtime", ctx.runtime);
|
|
227
|
+
Sentry.setTag("cli", cliMeta.name);
|
|
228
|
+
Sentry.setTag("cli_version", cliMeta.version);
|
|
229
|
+
if (!flushHookRegistered) {
|
|
230
|
+
flushHookRegistered = true;
|
|
231
|
+
process.once("beforeExit", () => {
|
|
232
|
+
void Sentry.flush(2e3);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
sentryReady = true;
|
|
236
|
+
})();
|
|
237
|
+
}
|
|
238
|
+
await sentryInitPromise;
|
|
239
|
+
return sentryReady;
|
|
240
|
+
}
|
|
241
|
+
function deriveMainApiOriginFromRequestUrl(requestUrl) {
|
|
242
|
+
try {
|
|
243
|
+
const u = new URL(requestUrl);
|
|
244
|
+
const host = u.hostname.toLowerCase();
|
|
245
|
+
if (!host.endsWith("siluzan.com")) return null;
|
|
246
|
+
if (host.startsWith("tso-api")) {
|
|
247
|
+
return `${u.protocol}//${host.replace(/^tso-api/, "api")}`;
|
|
248
|
+
}
|
|
249
|
+
if (host === "api.siluzan.com" || host === "api-ci.siluzan.com") {
|
|
250
|
+
return `${u.protocol}//${host}`;
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
} catch {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function inferSiluzanRuntimeEnvironment(requestUrl) {
|
|
258
|
+
try {
|
|
259
|
+
const host = new URL(requestUrl).hostname.toLowerCase();
|
|
260
|
+
return host.includes("-ci") ? "test" : "production";
|
|
261
|
+
} catch {
|
|
262
|
+
return "production";
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function breadcrumbUrl(requestUrl) {
|
|
266
|
+
try {
|
|
267
|
+
const u = new URL(requestUrl);
|
|
268
|
+
return `${u.origin}${u.pathname}`;
|
|
269
|
+
} catch {
|
|
270
|
+
return requestUrl.slice(0, 120);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function trackingPathParts(requestUrl) {
|
|
274
|
+
try {
|
|
275
|
+
const u = new URL(requestUrl);
|
|
276
|
+
return { host: u.hostname.toLowerCase(), pathname: u.pathname || "/" };
|
|
277
|
+
} catch {
|
|
278
|
+
return { host: "unknown", pathname: "/" };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
var SENSITIVE_QUERY_KEYS = /* @__PURE__ */ new Set([
|
|
282
|
+
"token",
|
|
283
|
+
"password",
|
|
284
|
+
"api_key",
|
|
285
|
+
"apikey",
|
|
286
|
+
"key",
|
|
287
|
+
"secret",
|
|
288
|
+
"authorization",
|
|
289
|
+
"auth"
|
|
290
|
+
]);
|
|
291
|
+
function redactUrlForTracking(url) {
|
|
292
|
+
try {
|
|
293
|
+
const u = new URL(url);
|
|
294
|
+
const q = new URLSearchParams(u.search);
|
|
295
|
+
const out = new URLSearchParams();
|
|
296
|
+
for (const [k, v] of q.entries()) {
|
|
297
|
+
out.set(k, SENSITIVE_QUERY_KEYS.has(k.toLowerCase()) ? "[REDACTED]" : v);
|
|
298
|
+
}
|
|
299
|
+
const qs = out.toString();
|
|
300
|
+
return `${u.origin}${u.pathname}${qs ? `?${qs}` : ""}`;
|
|
301
|
+
} catch {
|
|
302
|
+
return url.slice(0, 800);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function redactTrackingHeaders(h) {
|
|
306
|
+
const out = {};
|
|
307
|
+
for (const [k, v] of Object.entries(h)) {
|
|
308
|
+
const low = k.toLowerCase();
|
|
309
|
+
if (low === "authorization" || low === "x-api-key") {
|
|
310
|
+
out[k] = "[REDACTED]";
|
|
311
|
+
} else if (v.length > 2e3) {
|
|
312
|
+
out[k] = `${v.slice(0, 500)}\u2026[truncated ${v.length} chars]`;
|
|
313
|
+
} else {
|
|
314
|
+
out[k] = v;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return out;
|
|
318
|
+
}
|
|
319
|
+
var SENSITIVE_JSON_KEY = /* @__PURE__ */ new Set([
|
|
320
|
+
"password",
|
|
321
|
+
"token",
|
|
322
|
+
"apikey",
|
|
323
|
+
"api_key",
|
|
324
|
+
"authorization",
|
|
325
|
+
"authtoken",
|
|
326
|
+
"accesstoken",
|
|
327
|
+
"refreshtoken",
|
|
328
|
+
"secret",
|
|
329
|
+
"client_secret",
|
|
330
|
+
"privatekey",
|
|
331
|
+
"private_key"
|
|
332
|
+
]);
|
|
333
|
+
function deepRedactJson(value, depth) {
|
|
334
|
+
if (depth > 14) return "[MAX_DEPTH]";
|
|
335
|
+
if (value === null || typeof value !== "object") return value;
|
|
336
|
+
if (Array.isArray(value)) {
|
|
337
|
+
return value.map((v) => deepRedactJson(v, depth + 1));
|
|
338
|
+
}
|
|
339
|
+
const o = value;
|
|
340
|
+
const out = {};
|
|
341
|
+
for (const [k, v] of Object.entries(o)) {
|
|
342
|
+
const low = k.toLowerCase().replace(/_/g, "");
|
|
343
|
+
if (SENSITIVE_JSON_KEY.has(low) || low.includes("password") || low.includes("secret")) {
|
|
344
|
+
out[k] = "[REDACTED]";
|
|
345
|
+
} else {
|
|
346
|
+
out[k] = deepRedactJson(v, depth + 1);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return out;
|
|
350
|
+
}
|
|
351
|
+
function redactPlainTextSnippet(input) {
|
|
352
|
+
let s = input;
|
|
353
|
+
s = s.replace(/(Bearer\s+)[^\s'",}]+/gi, "$1***");
|
|
354
|
+
s = s.replace(
|
|
355
|
+
/("?(?:apiKey|authToken|accessToken|refreshToken|token|password)"?\s*[:=]\s*"?)([^"\s,}]{4,})/gi,
|
|
356
|
+
"$1***"
|
|
357
|
+
);
|
|
358
|
+
return s;
|
|
359
|
+
}
|
|
360
|
+
function trackingBodyMaxChars() {
|
|
361
|
+
const n = Number(process.env.SILUZAN_SENTRY_BODY_MAX);
|
|
362
|
+
if (Number.isFinite(n) && n > 256) return Math.min(n, 5e5);
|
|
363
|
+
return 12e3;
|
|
364
|
+
}
|
|
365
|
+
function trackingOmitBodies() {
|
|
366
|
+
const v = process.env.SILUZAN_SENTRY_NO_API_BODY?.trim().toLowerCase();
|
|
367
|
+
return v === "1" || v === "true" || v === "yes";
|
|
368
|
+
}
|
|
369
|
+
function formatTrackingBody(raw) {
|
|
370
|
+
if (trackingOmitBodies()) return "[omitted: SILUZAN_SENTRY_NO_API_BODY]";
|
|
371
|
+
if (raw === void 0 || raw === "") return "";
|
|
372
|
+
const max = trackingBodyMaxChars();
|
|
373
|
+
let text;
|
|
374
|
+
try {
|
|
375
|
+
const parsed = JSON.parse(raw);
|
|
376
|
+
text = JSON.stringify(deepRedactJson(parsed, 0));
|
|
377
|
+
} catch {
|
|
378
|
+
text = redactPlainTextSnippet(raw);
|
|
379
|
+
}
|
|
380
|
+
if (text.length > max) {
|
|
381
|
+
return `${text.slice(0, max)}\u2026[truncated ${text.length} chars]`;
|
|
382
|
+
}
|
|
383
|
+
return text;
|
|
384
|
+
}
|
|
385
|
+
async function reportSiluzanApiCall(payload) {
|
|
386
|
+
if (isSentryDisabled() || !getDsn() || !isApiTrackingEnabled()) return;
|
|
387
|
+
try {
|
|
388
|
+
const okInit = await ensureSentryInitialized(payload.url);
|
|
389
|
+
if (!okInit) return;
|
|
390
|
+
const Sentry = await import("@sentry/node");
|
|
391
|
+
const { host, pathname } = trackingPathParts(payload.url);
|
|
392
|
+
const siluzanEnv = inferSiluzanRuntimeEnvironment(payload.url);
|
|
393
|
+
const authType = payload.config.apiKey ? "apiKey" : "token";
|
|
394
|
+
const statusOk = payload.status >= 200 && payload.status < 300;
|
|
395
|
+
Sentry.captureMessage(
|
|
396
|
+
`siluzan.cli.api ${payload.method} ${host}${pathname} \u2192 ${payload.status}`,
|
|
397
|
+
{
|
|
398
|
+
level: statusOk ? "info" : "warning",
|
|
399
|
+
tags: {
|
|
400
|
+
siluzan_tracking: "api",
|
|
401
|
+
cli: cliMeta.name,
|
|
402
|
+
cli_version: cliMeta.version,
|
|
403
|
+
http_method: payload.method,
|
|
404
|
+
http_host: host,
|
|
405
|
+
http_status: String(payload.status),
|
|
406
|
+
siluzan_env: siluzanEnv,
|
|
407
|
+
auth_type: authType
|
|
408
|
+
},
|
|
409
|
+
fingerprint: [
|
|
410
|
+
"siluzan-cli-api",
|
|
411
|
+
cliMeta.name,
|
|
412
|
+
payload.method,
|
|
413
|
+
host,
|
|
414
|
+
pathname,
|
|
415
|
+
statusOk ? "2xx" : payload.status >= 500 ? "5xx" : "4xx"
|
|
416
|
+
],
|
|
417
|
+
contexts: {
|
|
418
|
+
siluzan_cli: {
|
|
419
|
+
command: cliInvocation || "(pre-action \u672A\u6CE8\u518C\u6216\u5148\u4E8E parse \u53D1\u8D77\u8BF7\u6C42)"
|
|
420
|
+
},
|
|
421
|
+
siluzan_request: {
|
|
422
|
+
url: redactUrlForTracking(payload.url),
|
|
423
|
+
method: payload.method,
|
|
424
|
+
headers: redactTrackingHeaders(payload.reqHeaders),
|
|
425
|
+
body: formatTrackingBody(payload.requestBody)
|
|
426
|
+
},
|
|
427
|
+
siluzan_response: {
|
|
428
|
+
status: payload.status,
|
|
429
|
+
body: formatTrackingBody(payload.responseText)
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
);
|
|
434
|
+
} catch {
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
function cacheKeyForUser(mainOrigin, config) {
|
|
438
|
+
const cred = config.apiKey ? `k:${config.apiKey}` : `t:${config.authToken}`;
|
|
439
|
+
return `${mainOrigin}\0${cred}`;
|
|
440
|
+
}
|
|
441
|
+
var userContextDone = /* @__PURE__ */ new Set();
|
|
442
|
+
var userContextInflight = /* @__PURE__ */ new Map();
|
|
443
|
+
function parseMeResponse(text) {
|
|
444
|
+
try {
|
|
445
|
+
const json = JSON.parse(text);
|
|
446
|
+
const data = json?.data && typeof json.data === "object" ? json.data : json;
|
|
447
|
+
const id = pickStr(data, "id") ?? pickStr(data, "userId") ?? pickStr(data, "accountId");
|
|
448
|
+
const email = pickStr(data, "email");
|
|
449
|
+
const username = pickStr(data, "userName") ?? pickStr(data, "username") ?? pickStr(data, "name") ?? pickStr(data, "phone");
|
|
450
|
+
if (!id && !email && !username) return null;
|
|
451
|
+
return { id, email, username };
|
|
452
|
+
} catch {
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function pickStr(obj, key) {
|
|
457
|
+
const v = obj[key];
|
|
458
|
+
return typeof v === "string" && v.trim() ? v.trim() : void 0;
|
|
459
|
+
}
|
|
460
|
+
async function fetchAndSetUser(mainOrigin, config) {
|
|
461
|
+
const meUrl = `${mainOrigin.replace(/\/$/, "")}/query/account/me`;
|
|
462
|
+
const authHeaders = config.apiKey ? { "x-api-key": config.apiKey } : { Authorization: `Bearer ${config.authToken}` };
|
|
463
|
+
const res = await rawRequest(meUrl, {
|
|
464
|
+
method: "GET",
|
|
465
|
+
headers: {
|
|
466
|
+
"Content-Type": "application/json",
|
|
467
|
+
"Accept-Language": "zh-CN",
|
|
468
|
+
...authHeaders
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
if (res.status < 200 || res.status >= 300) return;
|
|
472
|
+
const parsed = parseMeResponse(res.text);
|
|
473
|
+
if (!parsed) return;
|
|
474
|
+
const Sentry = await import("@sentry/node");
|
|
475
|
+
const user = {};
|
|
476
|
+
if (parsed.id) user.id = parsed.id;
|
|
477
|
+
if (parsed.email) user.email = parsed.email;
|
|
478
|
+
if (parsed.username) user.username = parsed.username;
|
|
479
|
+
Sentry.setUser(user);
|
|
480
|
+
}
|
|
481
|
+
function scheduleUserContext(mainOrigin, config) {
|
|
482
|
+
const key = cacheKeyForUser(mainOrigin, config);
|
|
483
|
+
if (userContextDone.has(key)) return;
|
|
484
|
+
let p = userContextInflight.get(key);
|
|
485
|
+
if (!p) {
|
|
486
|
+
p = (async () => {
|
|
487
|
+
try {
|
|
488
|
+
await fetchAndSetUser(mainOrigin, config);
|
|
489
|
+
} catch {
|
|
490
|
+
} finally {
|
|
491
|
+
userContextInflight.delete(key);
|
|
492
|
+
userContextDone.add(key);
|
|
493
|
+
}
|
|
494
|
+
})();
|
|
495
|
+
userContextInflight.set(key, p);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
function refreshSiluzanUser(apiBase, config) {
|
|
499
|
+
if (isSentryDisabled()) return;
|
|
500
|
+
void (async () => {
|
|
501
|
+
try {
|
|
502
|
+
const ok = await ensureSentryInitialized(apiBase);
|
|
503
|
+
if (!ok) return;
|
|
504
|
+
const mainOrigin = deriveMainApiOriginFromRequestUrl(apiBase) ?? (apiBase.includes("-ci") ? "https://api-ci.siluzan.com" : "https://api.siluzan.com");
|
|
505
|
+
const key = cacheKeyForUser(mainOrigin, config);
|
|
506
|
+
userContextDone.delete(key);
|
|
507
|
+
userContextInflight.delete(key);
|
|
508
|
+
scheduleUserContext(mainOrigin, config);
|
|
509
|
+
} catch {
|
|
510
|
+
}
|
|
511
|
+
})();
|
|
512
|
+
}
|
|
513
|
+
async function prepareSiluzanSentryForApiFetch(url, config, options) {
|
|
514
|
+
if (isSentryDisabled()) return;
|
|
515
|
+
if (!getDsn()) return;
|
|
516
|
+
try {
|
|
517
|
+
const ok = await ensureSentryInitialized(url);
|
|
518
|
+
if (!ok) return;
|
|
519
|
+
const Sentry = await import("@sentry/node");
|
|
520
|
+
const method = options.method ?? "GET";
|
|
521
|
+
const siluzanEnv = inferSiluzanRuntimeEnvironment(url);
|
|
522
|
+
const authType = config.apiKey ? "apiKey" : "token";
|
|
523
|
+
Sentry.addBreadcrumb({
|
|
524
|
+
category: "siluzan.api",
|
|
525
|
+
type: "http",
|
|
526
|
+
level: "info",
|
|
527
|
+
message: `${method} ${breadcrumbUrl(url)}`,
|
|
528
|
+
data: {
|
|
529
|
+
siluzan_env: siluzanEnv,
|
|
530
|
+
auth_type: authType
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
const mainOrigin = deriveMainApiOriginFromRequestUrl(url);
|
|
534
|
+
if (mainOrigin) {
|
|
535
|
+
scheduleUserContext(mainOrigin, config);
|
|
536
|
+
}
|
|
537
|
+
} catch {
|
|
538
|
+
}
|
|
539
|
+
}
|
|
133
540
|
function redactSensitive(input) {
|
|
134
541
|
let output = input;
|
|
135
542
|
output = output.replace(/(Bearer\s+)[^\s",]+/gi, "$1***");
|
|
@@ -140,6 +547,8 @@ function redactSensitive(input) {
|
|
|
140
547
|
return output;
|
|
141
548
|
}
|
|
142
549
|
async function apiFetch(url, config, options = {}, verbose = false) {
|
|
550
|
+
await prepareSiluzanSentryForApiFetch(url, config, options);
|
|
551
|
+
const method = options.method ?? "GET";
|
|
143
552
|
const authHeaders = config.apiKey ? { "x-api-key": config.apiKey } : { Authorization: `Bearer ${config.authToken}` };
|
|
144
553
|
const reqHeaders = {
|
|
145
554
|
"Content-Type": "application/json",
|
|
@@ -151,11 +560,20 @@ async function apiFetch(url, config, options = {}, verbose = false) {
|
|
|
151
560
|
};
|
|
152
561
|
const body = typeof options.body === "string" ? options.body : void 0;
|
|
153
562
|
const res = await rawRequest(url, {
|
|
154
|
-
method
|
|
563
|
+
method,
|
|
155
564
|
headers: reqHeaders,
|
|
156
565
|
body
|
|
157
566
|
});
|
|
158
567
|
const text = res.text;
|
|
568
|
+
await reportSiluzanApiCall({
|
|
569
|
+
url,
|
|
570
|
+
config,
|
|
571
|
+
method,
|
|
572
|
+
reqHeaders,
|
|
573
|
+
requestBody: body,
|
|
574
|
+
status: res.status,
|
|
575
|
+
responseText: text
|
|
576
|
+
});
|
|
159
577
|
if (res.status < 200 || res.status >= 300) {
|
|
160
578
|
const detail = verbose ? `\uFF1A${redactSensitive(text).slice(0, 300)}` : "";
|
|
161
579
|
throw new Error(`HTTP ${res.status}${detail}`);
|
|
@@ -218,6 +636,93 @@ async function fetchNpmVersion(pkgName, tag, timeoutMs = 4e3) {
|
|
|
218
636
|
}
|
|
219
637
|
}
|
|
220
638
|
|
|
639
|
+
// src/utils/version.ts
|
|
640
|
+
import * as fs3 from "fs";
|
|
641
|
+
import * as path3 from "path";
|
|
642
|
+
import * as os3 from "os";
|
|
643
|
+
var PKG_NAME = "siluzan-tso-cli";
|
|
644
|
+
var CONFIG_FILE2 = path3.join(os3.homedir(), ".siluzan", "config.json");
|
|
645
|
+
function getCurrentVersion2() {
|
|
646
|
+
return getCurrentVersion(import.meta.url);
|
|
647
|
+
}
|
|
648
|
+
function readConfigRaw() {
|
|
649
|
+
try {
|
|
650
|
+
return JSON.parse(fs3.readFileSync(CONFIG_FILE2, "utf8"));
|
|
651
|
+
} catch {
|
|
652
|
+
return {};
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function writeConfigRaw(data) {
|
|
656
|
+
try {
|
|
657
|
+
fs3.mkdirSync(path3.dirname(CONFIG_FILE2), { recursive: true });
|
|
658
|
+
fs3.writeFileSync(CONFIG_FILE2, JSON.stringify(data, null, 2), "utf8");
|
|
659
|
+
if (process.platform !== "win32") {
|
|
660
|
+
fs3.chmodSync(CONFIG_FILE2, 384);
|
|
661
|
+
}
|
|
662
|
+
} catch {
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
async function fetchVersionByTag(tag, cacheKey, cfg) {
|
|
666
|
+
const hours24 = 24 * 60 * 60 * 1e3;
|
|
667
|
+
if (cfg._tsoLastVersionCheck && cfg[cacheKey]) {
|
|
668
|
+
const lastCheck = new Date(cfg._tsoLastVersionCheck).getTime();
|
|
669
|
+
if (Date.now() - lastCheck < hours24) {
|
|
670
|
+
return cfg[cacheKey];
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return fetchNpmVersion(PKG_NAME, tag);
|
|
674
|
+
}
|
|
675
|
+
async function notifyIfOutdated() {
|
|
676
|
+
try {
|
|
677
|
+
const current = getCurrentVersion2();
|
|
678
|
+
const tag = npmDistTagForCurrentVersion(current);
|
|
679
|
+
const isBeta = tag === "beta";
|
|
680
|
+
const latestCacheKey = isBeta ? "_tsoLatestBeta" : "_tsoLatestStable";
|
|
681
|
+
const minCacheKey = isBeta ? "_tsoMinRequiredBeta" : "_tsoMinRequiredStable";
|
|
682
|
+
const minTag = isBeta ? "min-required-beta" : "min-required";
|
|
683
|
+
const cfg = readConfigRaw();
|
|
684
|
+
const [latest, minRequired] = await Promise.all([
|
|
685
|
+
fetchVersionByTag(tag, latestCacheKey, cfg),
|
|
686
|
+
fetchVersionByTag(minTag, minCacheKey, cfg)
|
|
687
|
+
]);
|
|
688
|
+
writeConfigRaw({
|
|
689
|
+
...cfg,
|
|
690
|
+
_tsoLastVersionCheck: (/* @__PURE__ */ new Date()).toISOString(),
|
|
691
|
+
...latest ? { [latestCacheKey]: latest } : {},
|
|
692
|
+
...minRequired ? { [minCacheKey]: minRequired } : {}
|
|
693
|
+
});
|
|
694
|
+
const lastNotified = cfg._tsoLastNotified ? new Date(cfg._tsoLastNotified).getTime() : 0;
|
|
695
|
+
if (Date.now() - lastNotified < 24 * 60 * 60 * 1e3) return;
|
|
696
|
+
const tagLabel = isBeta ? "\uFF08\u6D4B\u8BD5\u7248\uFF09" : "\uFF08\u6B63\u5F0F\u7248\uFF09";
|
|
697
|
+
if (minRequired && isNewer(current, minRequired)) {
|
|
698
|
+
writeConfigRaw({ ...cfg, _tsoLastNotified: (/* @__PURE__ */ new Date()).toISOString() });
|
|
699
|
+
console.log(
|
|
700
|
+
`
|
|
701
|
+
[SILUZAN_FORCE_UPDATE] \u5F53\u524D\u7248\u672C ${current} \u5DF2\u8FC7\u671F\uFF0C\u6700\u4F4E\u8981\u6C42\u7248\u672C ${minRequired}${tagLabel}
|
|
702
|
+
\u26D4 \u5FC5\u987B\u5148\u5347\u7EA7\u624D\u80FD\u7EE7\u7EED\u4F7F\u7528\uFF0CAgent \u8BF7\u7ACB\u5373\u6267\u884C\uFF1Asiluzan-tso update
|
|
703
|
+
\u6216\u624B\u52A8\uFF1Anpm install -g ${PKG_NAME}@${tag}
|
|
704
|
+
`
|
|
705
|
+
);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
if (latest && isNewer(current, latest)) {
|
|
709
|
+
writeConfigRaw({ ...cfg, _tsoLastNotified: (/* @__PURE__ */ new Date()).toISOString() });
|
|
710
|
+
console.log(
|
|
711
|
+
`
|
|
712
|
+
[SILUZAN_UPDATE_AVAILABLE] \u53D1\u73B0\u65B0\u7248\u672C ${latest}${tagLabel}\uFF08\u5F53\u524D ${current}\uFF09
|
|
713
|
+
\u{1F4A1} Agent \u8BF7\u7ACB\u5373\u6267\u884C\uFF1Asiluzan-tso update
|
|
714
|
+
\u6216\u624B\u52A8\uFF1Anpm install -g ${PKG_NAME}@${tag}
|
|
715
|
+
`
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
} catch {
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
async function fetchLatestVersion() {
|
|
722
|
+
const tag = npmDistTagForCurrentVersion(getCurrentVersion2());
|
|
723
|
+
return fetchNpmVersion(PKG_NAME, tag);
|
|
724
|
+
}
|
|
725
|
+
|
|
221
726
|
// src/utils/auth.ts
|
|
222
727
|
function deriveMainApiUrl(tsoApiBaseUrl) {
|
|
223
728
|
try {
|
|
@@ -286,6 +791,7 @@ function loadConfig(tokenArg) {
|
|
|
286
791
|
\u274C googleApiUrl \u4E0D\u5408\u6CD5\uFF1A${googleApiErr}`);
|
|
287
792
|
process.exit(1);
|
|
288
793
|
}
|
|
794
|
+
setSiluzanCliMeta("siluzan-tso", getCurrentVersion2());
|
|
289
795
|
return {
|
|
290
796
|
apiBaseUrl,
|
|
291
797
|
authToken,
|
|
@@ -390,26 +896,26 @@ function cmdConfigClear() {
|
|
|
390
896
|
}
|
|
391
897
|
|
|
392
898
|
// src/commands/init.ts
|
|
393
|
-
import * as
|
|
899
|
+
import * as fs5 from "fs/promises";
|
|
394
900
|
import * as fsSync from "fs";
|
|
395
|
-
import * as
|
|
396
|
-
import * as
|
|
901
|
+
import * as os4 from "os";
|
|
902
|
+
import * as path5 from "path";
|
|
397
903
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
398
904
|
|
|
399
905
|
// src/templates/load-templates.ts
|
|
400
|
-
import * as
|
|
401
|
-
import * as
|
|
906
|
+
import * as fs4 from "fs/promises";
|
|
907
|
+
import * as path4 from "path";
|
|
402
908
|
async function getSkillFiles(skillDir) {
|
|
403
909
|
const out = {};
|
|
404
910
|
async function walk(dir, prefix) {
|
|
405
|
-
const entries = await
|
|
911
|
+
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
406
912
|
for (const ent of entries) {
|
|
407
913
|
const rel = prefix ? `${prefix}/${ent.name}` : ent.name;
|
|
408
|
-
const full =
|
|
914
|
+
const full = path4.join(dir, ent.name);
|
|
409
915
|
if (ent.isDirectory()) {
|
|
410
916
|
await walk(full, rel);
|
|
411
917
|
} else {
|
|
412
|
-
out[rel] = await
|
|
918
|
+
out[rel] = await fs4.readFile(full, "utf8");
|
|
413
919
|
}
|
|
414
920
|
}
|
|
415
921
|
}
|
|
@@ -418,14 +924,14 @@ async function getSkillFiles(skillDir) {
|
|
|
418
924
|
}
|
|
419
925
|
|
|
420
926
|
// src/commands/init.ts
|
|
421
|
-
var __dirname =
|
|
927
|
+
var __dirname = path5.dirname(fileURLToPath2(import.meta.url));
|
|
422
928
|
var TARGET_DIRS = {
|
|
423
|
-
cursor: (cwd) =>
|
|
424
|
-
claude: (cwd) =>
|
|
425
|
-
"openclaw-workspace": (cwd) =>
|
|
426
|
-
"openclaw-global": (_cwd, home) =>
|
|
427
|
-
"workbuddy-workspace": (cwd) =>
|
|
428
|
-
"workbuddy-global": (_cwd, home) =>
|
|
929
|
+
cursor: (cwd) => path5.join(cwd, ".cursor", "skills", "siluzan-tso"),
|
|
930
|
+
claude: (cwd) => path5.join(cwd, ".claude", "skills", "siluzan-tso"),
|
|
931
|
+
"openclaw-workspace": (cwd) => path5.join(cwd, "skills", "siluzan-tso"),
|
|
932
|
+
"openclaw-global": (_cwd, home) => path5.join(home, ".openclaw", "skills", "siluzan-tso"),
|
|
933
|
+
"workbuddy-workspace": (cwd) => path5.join(cwd, ".workbuddy", "skills", "siluzan-tso"),
|
|
934
|
+
"workbuddy-global": (_cwd, home) => path5.join(home, ".workbuddy", "skills", "siluzan-tso")
|
|
429
935
|
};
|
|
430
936
|
function parseTargets(raw) {
|
|
431
937
|
const normalized = raw.trim().toLowerCase();
|
|
@@ -466,32 +972,32 @@ function parseTargets(raw) {
|
|
|
466
972
|
return [...new Set(result)];
|
|
467
973
|
}
|
|
468
974
|
function skillRoot() {
|
|
469
|
-
return
|
|
975
|
+
return path5.join(__dirname, "..", "skill");
|
|
470
976
|
}
|
|
471
977
|
async function writeSkillFilesToDir(destDir, skillFiles, force) {
|
|
472
|
-
await
|
|
978
|
+
await fs5.mkdir(destDir, { recursive: true });
|
|
473
979
|
let anyWritten = false;
|
|
474
980
|
for (const [relativePath, content] of Object.entries(skillFiles)) {
|
|
475
|
-
const fullPath =
|
|
476
|
-
await
|
|
981
|
+
const fullPath = path5.join(destDir, relativePath);
|
|
982
|
+
await fs5.mkdir(path5.dirname(fullPath), { recursive: true });
|
|
477
983
|
try {
|
|
478
|
-
await
|
|
984
|
+
await fs5.access(fullPath);
|
|
479
985
|
if (!force) {
|
|
480
986
|
console.warn(`\u8DF3\u8FC7\uFF08\u5DF2\u5B58\u5728\uFF0C\u4F7F\u7528 --force \u8986\u76D6\uFF09: ${fullPath}`);
|
|
481
987
|
continue;
|
|
482
988
|
}
|
|
483
989
|
} catch {
|
|
484
990
|
}
|
|
485
|
-
await
|
|
991
|
+
await fs5.writeFile(fullPath, content, "utf8");
|
|
486
992
|
console.log(`\u5DF2\u5199\u5165: ${fullPath}`);
|
|
487
993
|
anyWritten = true;
|
|
488
994
|
}
|
|
489
995
|
return anyWritten;
|
|
490
996
|
}
|
|
491
997
|
function saveInstalledTargets(entries) {
|
|
492
|
-
const CONFIG_FILE4 =
|
|
998
|
+
const CONFIG_FILE4 = path5.join(os4.homedir(), ".siluzan", "config.json");
|
|
493
999
|
try {
|
|
494
|
-
fsSync.mkdirSync(
|
|
1000
|
+
fsSync.mkdirSync(path5.dirname(CONFIG_FILE4), { recursive: true });
|
|
495
1001
|
let existing = {};
|
|
496
1002
|
if (fsSync.existsSync(CONFIG_FILE4)) {
|
|
497
1003
|
existing = JSON.parse(fsSync.readFileSync(CONFIG_FILE4, "utf8"));
|
|
@@ -513,11 +1019,11 @@ function saveInstalledTargets(entries) {
|
|
|
513
1019
|
}
|
|
514
1020
|
}
|
|
515
1021
|
async function runInit(options) {
|
|
516
|
-
const home =
|
|
1022
|
+
const home = os4.homedir();
|
|
517
1023
|
const skillFiles = await getSkillFiles(skillRoot());
|
|
518
1024
|
const installedEntries = [];
|
|
519
1025
|
if (options.dir) {
|
|
520
|
-
const destDir =
|
|
1026
|
+
const destDir = path5.resolve(options.cwd, options.dir);
|
|
521
1027
|
console.log(`\u5B89\u88C5\u76EE\u6807\u76EE\u5F55\uFF1A${destDir}`);
|
|
522
1028
|
const anyWritten = await writeSkillFilesToDir(destDir, skillFiles, options.force);
|
|
523
1029
|
if (anyWritten) installedEntries.push({ target: "custom", cwd: "", dir: destDir });
|
|
@@ -547,79 +1053,9 @@ async function runInit(options) {
|
|
|
547
1053
|
// src/commands/update.ts
|
|
548
1054
|
import * as fs6 from "fs";
|
|
549
1055
|
import * as path6 from "path";
|
|
550
|
-
import * as
|
|
1056
|
+
import * as os5 from "os";
|
|
551
1057
|
import { spawnSync } from "child_process";
|
|
552
|
-
|
|
553
|
-
// src/utils/version.ts
|
|
554
|
-
import * as fs5 from "fs";
|
|
555
|
-
import * as path5 from "path";
|
|
556
|
-
import * as os3 from "os";
|
|
557
|
-
var PKG_NAME = "siluzan-tso-cli";
|
|
558
|
-
var CONFIG_FILE2 = path5.join(os3.homedir(), ".siluzan", "config.json");
|
|
559
|
-
function getCurrentVersion2() {
|
|
560
|
-
return getCurrentVersion(import.meta.url);
|
|
561
|
-
}
|
|
562
|
-
function readConfigRaw() {
|
|
563
|
-
try {
|
|
564
|
-
return JSON.parse(fs5.readFileSync(CONFIG_FILE2, "utf8"));
|
|
565
|
-
} catch {
|
|
566
|
-
return {};
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
function writeConfigRaw(data) {
|
|
570
|
-
try {
|
|
571
|
-
fs5.mkdirSync(path5.dirname(CONFIG_FILE2), { recursive: true });
|
|
572
|
-
fs5.writeFileSync(CONFIG_FILE2, JSON.stringify(data, null, 2), "utf8");
|
|
573
|
-
if (process.platform !== "win32") {
|
|
574
|
-
fs5.chmodSync(CONFIG_FILE2, 384);
|
|
575
|
-
}
|
|
576
|
-
} catch {
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
async function fetchVersionByTag(tag, cfg) {
|
|
580
|
-
const cacheKey = tag === "beta" ? "_tsoLatestBeta" : "_tsoLatestStable";
|
|
581
|
-
const hours24 = 24 * 60 * 60 * 1e3;
|
|
582
|
-
if (cfg._tsoLastVersionCheck && cfg[cacheKey]) {
|
|
583
|
-
const lastCheck = new Date(cfg._tsoLastVersionCheck).getTime();
|
|
584
|
-
if (Date.now() - lastCheck < hours24) {
|
|
585
|
-
return cfg[cacheKey];
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
return fetchNpmVersion(PKG_NAME, tag);
|
|
589
|
-
}
|
|
590
|
-
async function notifyIfOutdated() {
|
|
591
|
-
try {
|
|
592
|
-
const current = getCurrentVersion2();
|
|
593
|
-
const tag = npmDistTagForCurrentVersion(current);
|
|
594
|
-
const cfg = readConfigRaw();
|
|
595
|
-
const latest = await fetchVersionByTag(tag, cfg);
|
|
596
|
-
if (!latest) return;
|
|
597
|
-
const cacheKey = tag === "beta" ? "_tsoLatestBeta" : "_tsoLatestStable";
|
|
598
|
-
writeConfigRaw({
|
|
599
|
-
...cfg,
|
|
600
|
-
_tsoLastVersionCheck: (/* @__PURE__ */ new Date()).toISOString(),
|
|
601
|
-
[cacheKey]: latest
|
|
602
|
-
});
|
|
603
|
-
if (isNewer(current, latest)) {
|
|
604
|
-
const tagLabel = tag === "beta" ? "\uFF08\u6D4B\u8BD5\u7248\uFF09" : "\uFF08\u6B63\u5F0F\u7248\uFF09";
|
|
605
|
-
console.error(
|
|
606
|
-
`
|
|
607
|
-
\u{1F4A1} \u53D1\u73B0\u65B0\u7248\u672C ${latest}${tagLabel}\uFF08\u5F53\u524D ${current}\uFF09\uFF0C\u53EF\u4EFB\u9009\u5176\u4E00\u5347\u7EA7\u5E76\u5237\u65B0 Skill\uFF1A
|
|
608
|
-
siluzan-tso update
|
|
609
|
-
npm install -g ${PKG_NAME}@${tag}
|
|
610
|
-
`
|
|
611
|
-
);
|
|
612
|
-
}
|
|
613
|
-
} catch {
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
async function fetchLatestVersion() {
|
|
617
|
-
const tag = npmDistTagForCurrentVersion(getCurrentVersion2());
|
|
618
|
-
return fetchNpmVersion(PKG_NAME, tag);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
// src/commands/update.ts
|
|
622
|
-
var CONFIG_FILE3 = path6.join(os4.homedir(), ".siluzan", "config.json");
|
|
1058
|
+
var CONFIG_FILE3 = path6.join(os5.homedir(), ".siluzan", "config.json");
|
|
623
1059
|
var PKG_NAME2 = "siluzan-tso-cli";
|
|
624
1060
|
function readInstalledTargets() {
|
|
625
1061
|
try {
|
|
@@ -703,13 +1139,13 @@ async function runUpdate(options) {
|
|
|
703
1139
|
\u{1F504} \u6B63\u5728\u66F4\u65B0 ${targets.length} \u5904 Skill \u5B89\u88C5\u4F4D\u7F6E \u2026`);
|
|
704
1140
|
for (const entry of targets) {
|
|
705
1141
|
const label = entry.target === "custom" ? entry.dir ?? "unknown" : entry.target;
|
|
706
|
-
const cwd = entry.cwd ||
|
|
1142
|
+
const cwd = entry.cwd || os5.homedir();
|
|
707
1143
|
console.log(`
|
|
708
1144
|
[${label}]`);
|
|
709
1145
|
try {
|
|
710
1146
|
if (entry.target === "custom" && entry.dir) {
|
|
711
1147
|
await runInit({
|
|
712
|
-
cwd:
|
|
1148
|
+
cwd: os5.homedir(),
|
|
713
1149
|
aiTargets: "",
|
|
714
1150
|
dir: entry.dir,
|
|
715
1151
|
force: true
|
|
@@ -1638,13 +2074,13 @@ async function runTransferList(opts) {
|
|
|
1638
2074
|
// src/commands/invoice.ts
|
|
1639
2075
|
import * as fs7 from "fs";
|
|
1640
2076
|
import * as path7 from "path";
|
|
1641
|
-
import * as
|
|
2077
|
+
import * as os6 from "os";
|
|
1642
2078
|
async function ensureDataPermission(config) {
|
|
1643
2079
|
if (config.dataPermission) return config;
|
|
1644
2080
|
if (!config.mainApiUrl) return config;
|
|
1645
2081
|
const dp = await fetchDataPermission(config.mainApiUrl, config.authToken);
|
|
1646
2082
|
if (!dp) return config;
|
|
1647
|
-
const configPath = path7.join(
|
|
2083
|
+
const configPath = path7.join(os6.homedir(), ".siluzan", "config.json");
|
|
1648
2084
|
try {
|
|
1649
2085
|
const raw = fs7.existsSync(configPath) ? JSON.parse(fs7.readFileSync(configPath, "utf8")) : {};
|
|
1650
2086
|
raw.dataPermission = dp;
|
|
@@ -3355,7 +3791,7 @@ async function runOptimizeChildren(opts) {
|
|
|
3355
3791
|
}
|
|
3356
3792
|
|
|
3357
3793
|
// src/commands/forewarning.ts
|
|
3358
|
-
import
|
|
3794
|
+
import os7 from "os";
|
|
3359
3795
|
import path8 from "path";
|
|
3360
3796
|
import QRCode from "qrcode";
|
|
3361
3797
|
import open from "open";
|
|
@@ -3563,7 +3999,7 @@ async function runForewarningNotifyAccounts(opts) {
|
|
|
3563
3999
|
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");
|
|
3564
4000
|
if (qrLink) {
|
|
3565
4001
|
try {
|
|
3566
|
-
const imgPath = path8.join(
|
|
4002
|
+
const imgPath = path8.join(os7.tmpdir(), "siluzan-wechat-qr.png");
|
|
3567
4003
|
await QRCode.toFile(imgPath, qrLink, { width: 300 });
|
|
3568
4004
|
await open(imgPath);
|
|
3569
4005
|
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
|
|
@@ -6291,8 +6727,6 @@ async function resolveGoogleMediaAccountGroupId(config, opts) {
|
|
|
6291
6727
|
}
|
|
6292
6728
|
const company = opts.company.trim();
|
|
6293
6729
|
const promotionLink = normalizePromotionLinkForGoogle(opts.promotionLink);
|
|
6294
|
-
const level1 = (opts.industryLevel1 ?? "").trim();
|
|
6295
|
-
const level2 = (opts.industryLevel2 ?? "").trim();
|
|
6296
6730
|
let profile;
|
|
6297
6731
|
try {
|
|
6298
6732
|
profile = await apiFetch2(
|
|
@@ -6315,7 +6749,6 @@ async function resolveGoogleMediaAccountGroupId(config, opts) {
|
|
|
6315
6749
|
const mediaAccountGroupPayload = {
|
|
6316
6750
|
advertiserName: company,
|
|
6317
6751
|
StorageProvider: storageFromGroup ?? "StorageAccount",
|
|
6318
|
-
industry: { level1, level2 },
|
|
6319
6752
|
promotionLink,
|
|
6320
6753
|
promotionType: opts.promotionType
|
|
6321
6754
|
};
|
|
@@ -6479,9 +6912,10 @@ async function runOpenAccountBing(opts) {
|
|
|
6479
6912
|
opts.verbose,
|
|
6480
6913
|
opts.advertiserId
|
|
6481
6914
|
);
|
|
6915
|
+
const accountCount = Math.min(Math.max(opts.accountCount ?? 1, 1), 6);
|
|
6482
6916
|
const body = {
|
|
6483
6917
|
MediaAccountGroupId: magKey,
|
|
6484
|
-
AccountCount:
|
|
6918
|
+
AccountCount: accountCount,
|
|
6485
6919
|
BingCustomerInfo: {
|
|
6486
6920
|
name: opts.advertiserName,
|
|
6487
6921
|
province: opts.province,
|
|
@@ -6808,18 +7242,14 @@ async function runOpenAccountGoogle(opts) {
|
|
|
6808
7242
|
company: opts.company,
|
|
6809
7243
|
promotionLink: opts.promotionLink,
|
|
6810
7244
|
promotionType: opts.promotionType,
|
|
6811
|
-
industryLevel1: opts.industryLevel1,
|
|
6812
|
-
industryLevel2: opts.industryLevel2,
|
|
6813
7245
|
verbose: opts.verbose
|
|
6814
7246
|
});
|
|
6815
|
-
const level1 = (opts.industryLevel1 ?? "").trim();
|
|
6816
|
-
const level2 = (opts.industryLevel2 ?? "").trim();
|
|
6817
|
-
const industryStr = level1 || level2 ? `${level1}-${level2}` : "";
|
|
6818
7247
|
const promotionLinkNorm = normalizePromotionLinkForGoogle(opts.promotionLink);
|
|
6819
7248
|
const body = {
|
|
6820
|
-
accountInfo: Array.from({ length: counts }, () => ({
|
|
7249
|
+
accountInfo: Array.from({ length: counts }, (_, i) => ({
|
|
6821
7250
|
advertiser_info: {
|
|
6822
|
-
|
|
7251
|
+
// 多账户时在名称末尾追加 -1, -2 … 以便区分
|
|
7252
|
+
name: counts > 1 ? `${opts.accountName}-${i + 1}` : opts.accountName,
|
|
6823
7253
|
currency: opts.currency,
|
|
6824
7254
|
timezone: opts.timezone,
|
|
6825
7255
|
accounttype: "Adwords",
|
|
@@ -6827,8 +7257,7 @@ async function runOpenAccountGoogle(opts) {
|
|
|
6827
7257
|
inviteduserrole: opts.inviteRole ?? "Standard"
|
|
6828
7258
|
},
|
|
6829
7259
|
customer_info: {
|
|
6830
|
-
company: opts.company.trim()
|
|
6831
|
-
industry: industryStr
|
|
7260
|
+
company: opts.company.trim()
|
|
6832
7261
|
},
|
|
6833
7262
|
qualification_info: {
|
|
6834
7263
|
promotion_link: promotionLinkNorm,
|
|
@@ -6887,24 +7316,6 @@ async function uploadLicenseToTikTok(filePath, businessCentreType, config, verbo
|
|
|
6887
7316
|
}
|
|
6888
7317
|
return imageId;
|
|
6889
7318
|
}
|
|
6890
|
-
async function checkUnionpayRequired(licenseNo, company, businessCentreType, config, verbose) {
|
|
6891
|
-
try {
|
|
6892
|
-
const params = new URLSearchParams({
|
|
6893
|
-
license_no: licenseNo,
|
|
6894
|
-
company_name: company,
|
|
6895
|
-
businessCentreType
|
|
6896
|
-
});
|
|
6897
|
-
const res = await apiFetch2(
|
|
6898
|
-
`${config.apiBaseUrl}/query/media-account/tiktok/TikTokAdvQuery/CheckUnionpayInfo?${params}`,
|
|
6899
|
-
config,
|
|
6900
|
-
{},
|
|
6901
|
-
verbose
|
|
6902
|
-
);
|
|
6903
|
-
return res?.data?.unionpay_verification_required === true;
|
|
6904
|
-
} catch {
|
|
6905
|
-
return false;
|
|
6906
|
-
}
|
|
6907
|
-
}
|
|
6908
7319
|
async function runOpenAccountTikTok(opts) {
|
|
6909
7320
|
const config = loadConfig(opts.token);
|
|
6910
7321
|
const bcType = opts.businessCentreType ?? "Shop";
|
|
@@ -6914,7 +7325,7 @@ async function runOpenAccountTikTok(opts) {
|
|
|
6914
7325
|
`);
|
|
6915
7326
|
process.exit(1);
|
|
6916
7327
|
}
|
|
6917
|
-
console.log(" [1/
|
|
7328
|
+
console.log(" [1/3] \u6B63\u5728\u4E0A\u4F20\u8425\u4E1A\u6267\u7167\u5230 TikTok...");
|
|
6918
7329
|
let licenseImageId;
|
|
6919
7330
|
try {
|
|
6920
7331
|
licenseImageId = await uploadLicenseToTikTok(opts.licenseFile, bcType, config, opts.verbose);
|
|
@@ -6925,7 +7336,7 @@ async function runOpenAccountTikTok(opts) {
|
|
|
6925
7336
|
process.exit(1);
|
|
6926
7337
|
}
|
|
6927
7338
|
console.log(` TikTok license_image_id: ${licenseImageId}`);
|
|
6928
|
-
console.log(" [2/
|
|
7339
|
+
console.log(" [2/3] \u6B63\u5728\u4E0A\u4F20\u8425\u4E1A\u6267\u7167\u5230 Siluzan \u5B58\u6863...");
|
|
6929
7340
|
let siluzanImageId = "";
|
|
6930
7341
|
try {
|
|
6931
7342
|
const uploaded = await uploadAttachment(opts.licenseFile, config.apiBaseUrl, config, opts.verbose);
|
|
@@ -6934,7 +7345,7 @@ async function runOpenAccountTikTok(opts) {
|
|
|
6934
7345
|
} catch (err) {
|
|
6935
7346
|
console.warn(` \u26A0\uFE0F Siluzan \u5B58\u6863\u4E0A\u4F20\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u5F00\u6237\uFF09\uFF1A${err instanceof Error ? err.message : String(err)}`);
|
|
6936
7347
|
}
|
|
6937
|
-
console.log(" [3/
|
|
7348
|
+
console.log(" [3/3] \u6B63\u5728\u5173\u8054\u5E7F\u544A\u4E3B\u7EC4...");
|
|
6938
7349
|
const magKey = await resolveGenericMagKey(
|
|
6939
7350
|
config,
|
|
6940
7351
|
opts.company,
|
|
@@ -6950,19 +7361,11 @@ async function runOpenAccountTikTok(opts) {
|
|
|
6950
7361
|
opts.verbose,
|
|
6951
7362
|
opts.advertiserId
|
|
6952
7363
|
);
|
|
6953
|
-
console.log(" [4/4] \u6B63\u5728\u68C0\u67E5\u6CD5\u4EBA\u9A8C\u8BC1\u8981\u6C42...");
|
|
6954
|
-
const needUnionpay = await checkUnionpayRequired(opts.licenseNo, opts.company, bcType, config, opts.verbose);
|
|
6955
|
-
if (needUnionpay && (!opts.representativeName || !opts.representativeId)) {
|
|
6956
|
-
console.error(
|
|
6957
|
-
"\n\u274C \u8BE5\u8425\u4E1A\u6267\u7167\u9700\u8981\u586B\u5199\u6CD5\u4EBA\u94F6\u8054\u9A8C\u8BC1\u4FE1\u606F\uFF0C\u8BF7\u8FFD\u52A0\u4EE5\u4E0B\u53C2\u6570\u518D\u91CD\u8BD5\uFF1A\n --representative-name <\u6CD5\u4EBA\u59D3\u540D>\n --representative-id <\u8EAB\u4EFD\u8BC1\u53F7>\n --unionpay-account <\u94F6\u8054\u8D26\u53F7>\n --representative-phone <\u624B\u673A\u53F7>\n"
|
|
6958
|
-
);
|
|
6959
|
-
process.exit(1);
|
|
6960
|
-
}
|
|
6961
7364
|
const counts = Math.min(Math.max(opts.counts ?? 1, 1), 10);
|
|
6962
7365
|
const singleAccountInfo = {
|
|
6963
7366
|
advertiser_info: {
|
|
6964
7367
|
name: opts.accountName,
|
|
6965
|
-
currency:
|
|
7368
|
+
currency: "USD",
|
|
6966
7369
|
timezone: opts.timezone
|
|
6967
7370
|
},
|
|
6968
7371
|
customer_info: {
|
|
@@ -6974,18 +7377,23 @@ async function runOpenAccountTikTok(opts) {
|
|
|
6974
7377
|
promotion_link: opts.promotionLink,
|
|
6975
7378
|
license_no: opts.licenseNo,
|
|
6976
7379
|
license_image_id: licenseImageId
|
|
7380
|
+
},
|
|
7381
|
+
representative_info: {
|
|
7382
|
+
representative_name: opts.representativeName,
|
|
7383
|
+
representative_id: opts.representativeId,
|
|
7384
|
+
unionpay_account: opts.unionpayAccount,
|
|
7385
|
+
representative_phone_number: opts.representativePhone
|
|
6977
7386
|
}
|
|
6978
7387
|
};
|
|
6979
|
-
if (needUnionpay && opts.representativeName) {
|
|
6980
|
-
singleAccountInfo.representative_info = {
|
|
6981
|
-
representative_name: opts.representativeName,
|
|
6982
|
-
representative_id: opts.representativeId ?? "",
|
|
6983
|
-
unionpay_account: opts.unionpayAccount ?? "",
|
|
6984
|
-
representative_phone_number: opts.representativePhone ?? ""
|
|
6985
|
-
};
|
|
6986
|
-
}
|
|
6987
7388
|
const body = {
|
|
6988
|
-
accountInfo: Array.from({ length: counts }, () => ({
|
|
7389
|
+
accountInfo: Array.from({ length: counts }, (_, i) => ({
|
|
7390
|
+
...singleAccountInfo,
|
|
7391
|
+
advertiser_info: {
|
|
7392
|
+
...singleAccountInfo.advertiser_info,
|
|
7393
|
+
// 多账户时在名称末尾追加 -1, -2 … 以便区分
|
|
7394
|
+
name: counts > 1 ? `${opts.accountName}-${i + 1}` : opts.accountName
|
|
7395
|
+
}
|
|
7396
|
+
})),
|
|
6989
7397
|
MediaAccountGroupId: magKey,
|
|
6990
7398
|
IsSaltAdd: false,
|
|
6991
7399
|
ManagerCustomerId: "",
|
|
@@ -7210,6 +7618,7 @@ async function runLogin(opts = {}) {
|
|
|
7210
7618
|
process.exit(1);
|
|
7211
7619
|
}
|
|
7212
7620
|
writeSharedConfig({ apiKey: key });
|
|
7621
|
+
refreshSiluzanUser(DEFAULT_API_BASE, { authToken: "", apiKey: key });
|
|
7213
7622
|
console.log(`
|
|
7214
7623
|
\u2705 API Key \u5DF2\u4FDD\u5B58\uFF08${maskSecret(key)}\uFF09`);
|
|
7215
7624
|
console.log(` \u914D\u7F6E\u6587\u4EF6\uFF1A${CONFIG_FILE}`);
|
|
@@ -7262,6 +7671,7 @@ async function runLogin(opts = {}) {
|
|
|
7262
7671
|
process.exit(1);
|
|
7263
7672
|
}
|
|
7264
7673
|
writeSharedConfig({ apiKey });
|
|
7674
|
+
refreshSiluzanUser(DEFAULT_API_BASE, { authToken: "", apiKey });
|
|
7265
7675
|
console.log(`
|
|
7266
7676
|
\u2705 API Key \u5DF2\u4FDD\u5B58\uFF08${maskSecret(apiKey)}\uFF09`);
|
|
7267
7677
|
console.log(` \u914D\u7F6E\u6587\u4EF6\uFF1A${CONFIG_FILE}`);
|
|
@@ -7285,6 +7695,9 @@ var program = new Command();
|
|
|
7285
7695
|
program.name("siluzan-tso").description(
|
|
7286
7696
|
"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\u3001\u5145\u503C\u8F6C\u8D26\u3001\u5F00\u7968\u3001AI\u667A\u6295\u3001\u667A\u80FD\u9884\u8B66\u3001Google \u5E7F\u544A\u7BA1\u7406\u3002"
|
|
7287
7697
|
).version(getVersion());
|
|
7698
|
+
program.hook("preAction", () => {
|
|
7699
|
+
setSiluzanCliInvocation(redactCliArgvForSentry(process.argv));
|
|
7700
|
+
});
|
|
7288
7701
|
program.command("login").description("\u4FDD\u5B58\u8BA4\u8BC1\u51ED\u636E\u5230 ~/.siluzan/config.json\uFF08Token \u6216 API Key \u4E8C\u9009\u4E00\uFF09").option("--api-key <key>", "\u76F4\u63A5\u4FDD\u5B58 API Key\uFF08\u5728\u4E1D\u8DEF\u8D5E\u300C\u8BBE\u7F6E \u2192 API Key \u7BA1\u7406\u300D\u521B\u5EFA\uFF09\uFF0C\u63A8\u8350\u65B9\u5F0F").option("--manual", "\u4EA4\u4E92\u5F0F\u7C98\u8D34 JWT Token\uFF08\u65E0 API Key \u65F6\u4F7F\u7528\uFF09").action(async (opts) => {
|
|
7289
7702
|
await runLogin({ apiKey: opts.apiKey });
|
|
7290
7703
|
});
|
|
@@ -7320,7 +7733,7 @@ program.command("init").description("\u5C06 siluzan-tso Skill \u6587\u4EF6\u5199
|
|
|
7320
7733
|
});
|
|
7321
7734
|
program.command("list-accounts").description("\u67E5\u8BE2\u5E7F\u544A\u8D26\u6237\u5217\u8868\uFF08\u652F\u6301\u6309\u5A92\u4F53\u7C7B\u578B\u3001\u72B6\u6001\u3001\u5173\u952E\u5B57\u7B5B\u9009\uFF09").option(
|
|
7322
7735
|
"-m, --media <type>",
|
|
7323
|
-
`\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | Yandex | MetaAd | BingV2 | Kwai\uFF08\
|
|
7736
|
+
`\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | Yandex | MetaAd | BingV2 | Kwai\uFF08\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u6CA1\u6709\u4E00\u6B21\u6027\u67E5\u5168\u90E8\u7684\u65B9\u5F0F\uFF09`
|
|
7324
7737
|
).option("-k, --keyword <text>", "\u6309\u8D26\u6237\u540D\u79F0\u6216\u8D26\u6237 ID \u641C\u7D22").option(
|
|
7325
7738
|
"-s, --status <status>",
|
|
7326
7739
|
"\u8D26\u6237\u72B6\u6001\uFF1Anormal\uFF08\u6B63\u5E38\uFF09| invalid\uFF08\u5931\u6548\uFF09| all\uFF08\u5168\u90E8\uFF0C\u9ED8\u8BA4\uFF09",
|
|
@@ -8495,7 +8908,7 @@ openAccountCmd.command("yandex").description("\u63D0\u4EA4 Yandex \u5F00\u6237\u
|
|
|
8495
8908
|
verbose: opts.verbose
|
|
8496
8909
|
});
|
|
8497
8910
|
});
|
|
8498
|
-
openAccountCmd.command("bing").description("\u63D0\u4EA4 Bing/BingV2 \u5F00\u6237\u7533\u8BF7\uFF08\u9700\u4E0A\u4F20\u8425\u4E1A\u6267\u7167\u56FE\u7247\uFF0C\u6309\u516C\u53F8\u540D\u81EA\u52A8\u521B\u5EFA/\u5173\u8054\u5E7F\u544A\u4E3B\u7EC4\uFF09").requiredOption("--advertiser-name <name>", "\u5E7F\u544A\u4E3B\u540D\u79F0\uFF08\u516C\u53F8\u5168\u79F0\uFF0C\u7528\u4E8E\u5339\u914D\u6216\u521B\u5EFA\u5E7F\u544A\u4E3B\u7EC4\uFF09").requiredOption("--name-short <short>", "\u5E7F\u544A\u4E3B\u7B80\u79F0").requiredOption("--province <province>", "\u7701/\u5DDE").requiredOption("--city <city>", "\u57CE\u5E02").requiredOption("--address <address>", "\u8BE6\u7EC6\u5730\u5740").requiredOption("--promotion-link <url>", "\u63A8\u5E7F\u94FE\u63A5").requiredOption("--trade-id <name>", "\u884C\u4E1A\u540D\u79F0\uFF08\u6765\u81EA open-account bing-industries \u7684 name \u5B57\u6BB5\uFF09").requiredOption("--license-file <path>", "\u8425\u4E1A\u6267\u7167\u56FE\u7247\u672C\u5730\u8DEF\u5F84\uFF08JPG/PNG/PDF\uFF09").option("--postcode <code>", "\u90AE\u653F\u7F16\u7801").option("--advertiser-cid <cid>", "\u7ECF\u7406\u8D26\u6237 CID").option("--advertiser-name2 <name>", "\u7ECF\u7406\u8D26\u6237\u540D\u79F0").option("--account-count <n>", "\u5F00\u6237\u6570\u91CF\
|
|
8911
|
+
openAccountCmd.command("bing").description("\u63D0\u4EA4 Bing/BingV2 \u5F00\u6237\u7533\u8BF7\uFF08\u9700\u4E0A\u4F20\u8425\u4E1A\u6267\u7167\u56FE\u7247\uFF0C\u6309\u516C\u53F8\u540D\u81EA\u52A8\u521B\u5EFA/\u5173\u8054\u5E7F\u544A\u4E3B\u7EC4\uFF09").requiredOption("--advertiser-name <name>", "\u5E7F\u544A\u4E3B\u540D\u79F0\uFF08\u516C\u53F8\u5168\u79F0\uFF0C\u7528\u4E8E\u5339\u914D\u6216\u521B\u5EFA\u5E7F\u544A\u4E3B\u7EC4\uFF09").requiredOption("--name-short <short>", "\u5E7F\u544A\u4E3B\u7B80\u79F0").requiredOption("--province <province>", "\u7701/\u5DDE").requiredOption("--city <city>", "\u57CE\u5E02").requiredOption("--address <address>", "\u8BE6\u7EC6\u5730\u5740").requiredOption("--promotion-link <url>", "\u63A8\u5E7F\u94FE\u63A5").requiredOption("--trade-id <name>", "\u884C\u4E1A\u540D\u79F0\uFF08\u6765\u81EA open-account bing-industries \u7684 name \u5B57\u6BB5\uFF09").requiredOption("--license-file <path>", "\u8425\u4E1A\u6267\u7167\u56FE\u7247\u672C\u5730\u8DEF\u5F84\uFF08JPG/PNG/PDF\uFF09").option("--postcode <code>", "\u90AE\u653F\u7F16\u7801").option("--advertiser-cid <cid>", "\u7ECF\u7406\u8D26\u6237 CID").option("--advertiser-name2 <name>", "\u7ECF\u7406\u8D26\u6237\u540D\u79F0").option("--account-count <n>", "\u5F00\u6237\u6570\u91CF\uFF081~6\uFF0C\u9ED8\u8BA4 1\uFF09", parseInt).option("--advertiser-id <magKey>", "\u53EF\u9009\uFF1A\u624B\u52A8\u6307\u5B9A\u5E7F\u544A\u4E3B\u7EC4 magKey\uFF08\u4E00\u822C\u65E0\u9700\u586B\u5199\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
|
|
8499
8912
|
await runOpenAccountBing({
|
|
8500
8913
|
token: opts.token,
|
|
8501
8914
|
advertiserId: opts.advertiserId,
|
|
@@ -8537,7 +8950,7 @@ openAccountCmd.command("google-timezones").description("\u5217\u51FA Google \u5F
|
|
|
8537
8950
|
openAccountCmd.command("google-wizard").description("\u4EA4\u4E92\u5F0F Google \u5F00\u6237\uFF08\u5BF9\u9F50\u7F51\u9875\u4E94\u6B65\u8BF4\u660E + \u4E24\u6B65\u8868\u5355\uFF1B\u9700\u7EC8\u7AEF TTY\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
|
|
8538
8951
|
await runOpenAccountGoogleWizard({ token: opts.token, verbose: opts.verbose });
|
|
8539
8952
|
});
|
|
8540
|
-
openAccountCmd.command("google").description("\u63D0\u4EA4 Google \u5F00\u6237\u7533\u8BF7\uFF08\u4E0E\u7F51\u9875\u8868\u5355\u4E00\u81F4\uFF1A\u6309\u516C\u53F8\u540D\u79F0\u81EA\u52A8\u521B\u5EFA/\u5173\u8054\u5E7F\u544A\u4E3B\u7EC4\uFF0C\u65E0\u9700 magKey\uFF09").option("--advertiser-id <magKey>", "\u53EF\u9009\uFF1A\u624B\u52A8\u6307\u5B9A\u5E7F\u544A\u4E3B\u7EC4 ID\uFF08\u4E00\u822C\u4E0D\u7528\uFF1B\u8C03\u8BD5\u7528\u6216\u7279\u6B8A\u573A\u666F\uFF09").requiredOption("--account-name <name>", "\u5E7F\u544A\u8D26\u6237\u540D\u79F0\uFF08\u5EFA\u8BAE 22 \u4E2A\u5B57\u7B26\u4EE5\u5185\uFF09").requiredOption("--currency <code>", "\u8D27\u5E01\uFF1AUSD | CNY").requiredOption("--timezone <tz>", "\u65F6\u533A\uFF0C\u5982 Asia/Hong_Kong\uFF08\u53EF\u5148\u67E5 open-account google-timezones\uFF09").requiredOption("--invite-email <email>", "\u53D7\u9080\u7528\u6237\u90AE\u7BB1\uFF08\u8D26\u6237\u9080\u8BF7\u5C06\u53D1\u5230\u6B64\u90AE\u7BB1\uFF09").requiredOption("--company <name>", "\u516C\u53F8\u540D\u79F0\uFF08\u7528\u4E8E\u5339\u914D\u6216\u521B\u5EFA\u5E7F\u544A\u4E3B\u7EC4\uFF0C\u4E0E\u7F51\u9875\u7B2C\u4E00\u6B65\u4E00\u81F4\uFF09").
|
|
8953
|
+
openAccountCmd.command("google").description("\u63D0\u4EA4 Google \u5F00\u6237\u7533\u8BF7\uFF08\u4E0E\u7F51\u9875\u8868\u5355\u4E00\u81F4\uFF1A\u6309\u516C\u53F8\u540D\u79F0\u81EA\u52A8\u521B\u5EFA/\u5173\u8054\u5E7F\u544A\u4E3B\u7EC4\uFF0C\u65E0\u9700 magKey\uFF09").option("--advertiser-id <magKey>", "\u53EF\u9009\uFF1A\u624B\u52A8\u6307\u5B9A\u5E7F\u544A\u4E3B\u7EC4 ID\uFF08\u4E00\u822C\u4E0D\u7528\uFF1B\u8C03\u8BD5\u7528\u6216\u7279\u6B8A\u573A\u666F\uFF09").requiredOption("--account-name <name>", "\u5E7F\u544A\u8D26\u6237\u540D\u79F0\uFF08\u5EFA\u8BAE 22 \u4E2A\u5B57\u7B26\u4EE5\u5185\uFF09").requiredOption("--currency <code>", "\u8D27\u5E01\uFF1AUSD | CNY").requiredOption("--timezone <tz>", "\u65F6\u533A\uFF0C\u5982 Asia/Hong_Kong\uFF08\u53EF\u5148\u67E5 open-account google-timezones\uFF09").requiredOption("--invite-email <email>", "\u53D7\u9080\u7528\u6237\u90AE\u7BB1\uFF08\u8D26\u6237\u9080\u8BF7\u5C06\u53D1\u5230\u6B64\u90AE\u7BB1\uFF09").requiredOption("--company <name>", "\u516C\u53F8\u540D\u79F0\uFF08\u7528\u4E8E\u5339\u914D\u6216\u521B\u5EFA\u5E7F\u544A\u4E3B\u7EC4\uFF0C\u4E0E\u7F51\u9875\u7B2C\u4E00\u6B65\u4E00\u81F4\uFF09").requiredOption("--promotion-link <url>", "\u63A8\u5E7F\u94FE\u63A5\uFF08\u516C\u53F8\u5B98\u7F51\u6216\u4EA7\u54C1\u9875\uFF1B\u53EF\u5199\u57DF\u540D\uFF0C\u4F1A\u81EA\u52A8\u8865 https://\uFF09").requiredOption("--promotion-type <type>", "\u63A8\u5E7F\u7C7B\u578B\uFF1Ab2b | b2c | app").option("--invite-role <role>", "\u53D7\u9080\u7528\u6237\u89D2\u8272\uFF1AStandard | Admin\uFF08\u9ED8\u8BA4 Standard\uFF09", "Standard").option("--auto-mailbox", "\u81EA\u52A8\u5206\u914D\u90AE\u7BB1", false).option("--counts <n>", "\u672C\u6B21\u5F00\u6237\u6570\u91CF\uFF081-3\uFF0C\u9ED8\u8BA4 1\uFF09", parseInt).option("--manager-customer-id <id>", "MCC \u7ECF\u7406\u8D26\u6237 ID\uFF08\u53EF\u9009\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
|
|
8541
8954
|
await runOpenAccountGoogle({
|
|
8542
8955
|
token: opts.token,
|
|
8543
8956
|
advertiserId: opts.advertiserId,
|
|
@@ -8546,8 +8959,6 @@ openAccountCmd.command("google").description("\u63D0\u4EA4 Google \u5F00\u6237\u
|
|
|
8546
8959
|
timezone: opts.timezone,
|
|
8547
8960
|
inviteEmail: opts.inviteEmail,
|
|
8548
8961
|
company: opts.company,
|
|
8549
|
-
industryLevel1: opts.industry1,
|
|
8550
|
-
industryLevel2: opts.industry2,
|
|
8551
8962
|
promotionLink: opts.promotionLink,
|
|
8552
8963
|
promotionType: opts.promotionType,
|
|
8553
8964
|
inviteRole: opts.inviteRole,
|
|
@@ -8557,12 +8968,11 @@ openAccountCmd.command("google").description("\u63D0\u4EA4 Google \u5F00\u6237\u
|
|
|
8557
8968
|
verbose: opts.verbose
|
|
8558
8969
|
});
|
|
8559
8970
|
});
|
|
8560
|
-
openAccountCmd.command("tiktok").description("\u63D0\u4EA4 TikTok \u5F00\u6237\u7533\u8BF7\uFF08\u9700\u4E0A\u4F20\u8425\u4E1A\u6267\u7167\u56FE\u7247\uFF0C\u6309\u516C\u53F8\u540D\u81EA\u52A8\u521B\u5EFA/\u5173\u8054\u5E7F\u544A\u4E3B\u7EC4\uFF09").requiredOption("--account-name <name>", "\u5E7F\u544A\u8D26\u6237\u540D\u79F0").requiredOption("--
|
|
8971
|
+
openAccountCmd.command("tiktok").description("\u63D0\u4EA4 TikTok \u5F00\u6237\u7533\u8BF7\uFF08\u9700\u4E0A\u4F20\u8425\u4E1A\u6267\u7167\u56FE\u7247\uFF0C\u6309\u516C\u53F8\u540D\u81EA\u52A8\u521B\u5EFA/\u5173\u8054\u5E7F\u544A\u4E3B\u7EC4\uFF09").requiredOption("--account-name <name>", "\u5E7F\u544A\u8D26\u6237\u540D\u79F0").requiredOption("--timezone <tz>", "\u65F6\u533A\uFF0C\u5982 UTC+8\uFF08\u53EF\u5148\u67E5 open-account tiktok-timezones\uFF09").requiredOption("--company <name>", "\u516C\u53F8\u540D\u79F0").requiredOption("--industry-id <id>", "\u884C\u4E1A ID\uFF08\u6570\u5B57\uFF0CTikTok \u884C\u4E1A\u53F6\u5B50\u8282\u70B9 ID\uFF09", parseInt).requiredOption("--registered-area <code>", "\u6CE8\u518C\u5730\u56FD\u5BB6\u4EE3\u7801\uFF0C\u5982 CN").requiredOption("--promotion-link <url>", "\u63A8\u5E7F\u94FE\u63A5").requiredOption("--license-no <no>", "\u8425\u4E1A\u6267\u7167\u7F16\u7801\uFF08\u793E\u4F1A\u7EDF\u4E00\u4FE1\u7528\u4EE3\u7801\uFF09").requiredOption("--license-file <path>", "\u8425\u4E1A\u6267\u7167\u56FE\u7247\u672C\u5730\u8DEF\u5F84\uFF08JPG/PNG\uFF09").option("--bc-type <type>", "\u4E1A\u52A1\u4E2D\u5FC3\u7C7B\u578B\uFF1AShop | Store | App | B2B | Other\uFF08\u9ED8\u8BA4 Shop\uFF09", "Shop").option("--partner-id <id>", "BC \u5408\u4F5C\u4F19\u4F34 ID\uFF08bc_id\uFF0C\u53EF\u9009\uFF09").option("--counts <n>", "\u672C\u6B21\u5F00\u6237\u6570\u91CF\uFF081-10\uFF0C\u9ED8\u8BA4 1\uFF09", parseInt).requiredOption("--representative-name <name>", "\u6CD5\u4EBA\u59D3\u540D\uFF08\u94F6\u8054\u9A8C\u8BC1\u5FC5\u586B\uFF09").requiredOption("--representative-id <id>", "\u6CD5\u4EBA\u8EAB\u4EFD\u8BC1\u53F7\uFF08\u94F6\u8054\u9A8C\u8BC1\u5FC5\u586B\uFF09").requiredOption("--unionpay-account <no>", "\u6CD5\u4EBA\u94F6\u8054\u8D26\u53F7\uFF08\u94F6\u8054\u9A8C\u8BC1\u5FC5\u586B\uFF09").requiredOption("--representative-phone <phone>", "\u6CD5\u4EBA\u624B\u673A\u53F7\uFF08\u94F6\u8054\u9A8C\u8BC1\u5FC5\u586B\uFF09").option("--advertiser-id <magKey>", "\u53EF\u9009\uFF1A\u624B\u52A8\u6307\u5B9A\u5E7F\u544A\u4E3B\u7EC4 magKey\uFF08\u4E00\u822C\u65E0\u9700\u586B\u5199\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
|
|
8561
8972
|
await runOpenAccountTikTok({
|
|
8562
8973
|
token: opts.token,
|
|
8563
8974
|
advertiserId: opts.advertiserId,
|
|
8564
8975
|
accountName: opts.accountName,
|
|
8565
|
-
currency: opts.currency,
|
|
8566
8976
|
timezone: opts.timezone,
|
|
8567
8977
|
company: opts.company,
|
|
8568
8978
|
industryId: opts.industryId,
|
package/dist/skill/SKILL.md
CHANGED
|
@@ -115,6 +115,29 @@ description: >-
|
|
|
115
115
|
|
|
116
116
|
---
|
|
117
117
|
|
|
118
|
+
## 自动更新规则(必须遵守)
|
|
119
|
+
|
|
120
|
+
CLI 命令执行后可能在输出末尾出现以下两种标记,Agent 必须按对应规则处理:
|
|
121
|
+
|
|
122
|
+
### `[SILUZAN_FORCE_UPDATE]` — 强制更新(必须,不可跳过)
|
|
123
|
+
|
|
124
|
+
当前版本已低于最低要求版本,**必须先升级再继续**:
|
|
125
|
+
|
|
126
|
+
1. 立即执行 `siluzan-tso update`
|
|
127
|
+
2. 告知用户:当前版本过旧,已自动升级,请重新执行刚才的操作
|
|
128
|
+
3. 不得在升级前继续执行任何业务命令
|
|
129
|
+
|
|
130
|
+
### `[SILUZAN_UPDATE_AVAILABLE]` — 可选更新
|
|
131
|
+
|
|
132
|
+
有新版本可用,但当前版本仍可正常使用:
|
|
133
|
+
|
|
134
|
+
1. 执行 `siluzan-tso update`
|
|
135
|
+
2. 更新完成后继续执行原本的用户任务
|
|
136
|
+
|
|
137
|
+
> 两种情况下,`siluzan-tso update` 都会同步刷新 Skill 文件,新版本可能包含参数或流程变更。
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
118
141
|
## AI 行为规范
|
|
119
142
|
|
|
120
143
|
- **先查账户再操作**:对具体账户做操作前,先通过 `list-accounts` 确认 ID。
|
package/dist/skill/_meta.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "siluzan-tso-cli",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.23",
|
|
4
4
|
"description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"access": "public"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
+
"@sentry/node": "9",
|
|
29
30
|
"commander": "^12.1.0",
|
|
30
31
|
"open": "^10.1.0",
|
|
31
32
|
"qrcode": "^1.5.4"
|