siluzan-tso-cli 1.0.0-beta.22 → 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 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.22),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
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: options.method ?? "GET",
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 fs4 from "fs/promises";
899
+ import * as fs5 from "fs/promises";
394
900
  import * as fsSync from "fs";
395
- import * as os2 from "os";
396
- import * as path4 from "path";
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 fs3 from "fs/promises";
401
- import * as path3 from "path";
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 fs3.readdir(dir, { withFileTypes: true });
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 = path3.join(dir, ent.name);
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 fs3.readFile(full, "utf8");
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 = path4.dirname(fileURLToPath2(import.meta.url));
927
+ var __dirname = path5.dirname(fileURLToPath2(import.meta.url));
422
928
  var TARGET_DIRS = {
423
- cursor: (cwd) => path4.join(cwd, ".cursor", "skills", "siluzan-tso"),
424
- claude: (cwd) => path4.join(cwd, ".claude", "skills", "siluzan-tso"),
425
- "openclaw-workspace": (cwd) => path4.join(cwd, "skills", "siluzan-tso"),
426
- "openclaw-global": (_cwd, home) => path4.join(home, ".openclaw", "skills", "siluzan-tso"),
427
- "workbuddy-workspace": (cwd) => path4.join(cwd, ".workbuddy", "skills", "siluzan-tso"),
428
- "workbuddy-global": (_cwd, home) => path4.join(home, ".workbuddy", "skills", "siluzan-tso")
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 path4.join(__dirname, "..", "skill");
975
+ return path5.join(__dirname, "..", "skill");
470
976
  }
471
977
  async function writeSkillFilesToDir(destDir, skillFiles, force) {
472
- await fs4.mkdir(destDir, { recursive: true });
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 = path4.join(destDir, relativePath);
476
- await fs4.mkdir(path4.dirname(fullPath), { recursive: true });
981
+ const fullPath = path5.join(destDir, relativePath);
982
+ await fs5.mkdir(path5.dirname(fullPath), { recursive: true });
477
983
  try {
478
- await fs4.access(fullPath);
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 fs4.writeFile(fullPath, content, "utf8");
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 = path4.join(os2.homedir(), ".siluzan", "config.json");
998
+ const CONFIG_FILE4 = path5.join(os4.homedir(), ".siluzan", "config.json");
493
999
  try {
494
- fsSync.mkdirSync(path4.dirname(CONFIG_FILE4), { recursive: true });
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 = os2.homedir();
1022
+ const home = os4.homedir();
517
1023
  const skillFiles = await getSkillFiles(skillRoot());
518
1024
  const installedEntries = [];
519
1025
  if (options.dir) {
520
- const destDir = path4.resolve(options.cwd, options.dir);
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,98 +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 os4 from "os";
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, cacheKey, cfg) {
580
- const hours24 = 24 * 60 * 60 * 1e3;
581
- if (cfg._tsoLastVersionCheck && cfg[cacheKey]) {
582
- const lastCheck = new Date(cfg._tsoLastVersionCheck).getTime();
583
- if (Date.now() - lastCheck < hours24) {
584
- return cfg[cacheKey];
585
- }
586
- }
587
- return fetchNpmVersion(PKG_NAME, tag);
588
- }
589
- async function notifyIfOutdated() {
590
- try {
591
- const current = getCurrentVersion2();
592
- const tag = npmDistTagForCurrentVersion(current);
593
- const isBeta = tag === "beta";
594
- const latestCacheKey = isBeta ? "_tsoLatestBeta" : "_tsoLatestStable";
595
- const minCacheKey = isBeta ? "_tsoMinRequiredBeta" : "_tsoMinRequiredStable";
596
- const minTag = isBeta ? "min-required-beta" : "min-required";
597
- const cfg = readConfigRaw();
598
- const [latest, minRequired] = await Promise.all([
599
- fetchVersionByTag(tag, latestCacheKey, cfg),
600
- fetchVersionByTag(minTag, minCacheKey, cfg)
601
- ]);
602
- writeConfigRaw({
603
- ...cfg,
604
- _tsoLastVersionCheck: (/* @__PURE__ */ new Date()).toISOString(),
605
- ...latest ? { [latestCacheKey]: latest } : {},
606
- ...minRequired ? { [minCacheKey]: minRequired } : {}
607
- });
608
- const lastNotified = cfg._tsoLastNotified ? new Date(cfg._tsoLastNotified).getTime() : 0;
609
- if (Date.now() - lastNotified < 24 * 60 * 60 * 1e3) return;
610
- const tagLabel = isBeta ? "\uFF08\u6D4B\u8BD5\u7248\uFF09" : "\uFF08\u6B63\u5F0F\u7248\uFF09";
611
- if (minRequired && isNewer(current, minRequired)) {
612
- writeConfigRaw({ ...cfg, _tsoLastNotified: (/* @__PURE__ */ new Date()).toISOString() });
613
- console.log(
614
- `
615
- [SILUZAN_FORCE_UPDATE] \u5F53\u524D\u7248\u672C ${current} \u5DF2\u8FC7\u671F\uFF0C\u6700\u4F4E\u8981\u6C42\u7248\u672C ${minRequired}${tagLabel}
616
- \u26D4 \u5FC5\u987B\u5148\u5347\u7EA7\u624D\u80FD\u7EE7\u7EED\u4F7F\u7528\uFF0CAgent \u8BF7\u7ACB\u5373\u6267\u884C\uFF1Asiluzan-tso update
617
- \u6216\u624B\u52A8\uFF1Anpm install -g ${PKG_NAME}@${tag}
618
- `
619
- );
620
- return;
621
- }
622
- if (latest && isNewer(current, latest)) {
623
- writeConfigRaw({ ...cfg, _tsoLastNotified: (/* @__PURE__ */ new Date()).toISOString() });
624
- console.log(
625
- `
626
- [SILUZAN_UPDATE_AVAILABLE] \u53D1\u73B0\u65B0\u7248\u672C ${latest}${tagLabel}\uFF08\u5F53\u524D ${current}\uFF09
627
- \u{1F4A1} Agent \u8BF7\u7ACB\u5373\u6267\u884C\uFF1Asiluzan-tso update
628
- \u6216\u624B\u52A8\uFF1Anpm install -g ${PKG_NAME}@${tag}
629
- `
630
- );
631
- }
632
- } catch {
633
- }
634
- }
635
- async function fetchLatestVersion() {
636
- const tag = npmDistTagForCurrentVersion(getCurrentVersion2());
637
- return fetchNpmVersion(PKG_NAME, tag);
638
- }
639
-
640
- // src/commands/update.ts
641
- var CONFIG_FILE3 = path6.join(os4.homedir(), ".siluzan", "config.json");
1058
+ var CONFIG_FILE3 = path6.join(os5.homedir(), ".siluzan", "config.json");
642
1059
  var PKG_NAME2 = "siluzan-tso-cli";
643
1060
  function readInstalledTargets() {
644
1061
  try {
@@ -722,13 +1139,13 @@ async function runUpdate(options) {
722
1139
  \u{1F504} \u6B63\u5728\u66F4\u65B0 ${targets.length} \u5904 Skill \u5B89\u88C5\u4F4D\u7F6E \u2026`);
723
1140
  for (const entry of targets) {
724
1141
  const label = entry.target === "custom" ? entry.dir ?? "unknown" : entry.target;
725
- const cwd = entry.cwd || os4.homedir();
1142
+ const cwd = entry.cwd || os5.homedir();
726
1143
  console.log(`
727
1144
  [${label}]`);
728
1145
  try {
729
1146
  if (entry.target === "custom" && entry.dir) {
730
1147
  await runInit({
731
- cwd: os4.homedir(),
1148
+ cwd: os5.homedir(),
732
1149
  aiTargets: "",
733
1150
  dir: entry.dir,
734
1151
  force: true
@@ -1657,13 +2074,13 @@ async function runTransferList(opts) {
1657
2074
  // src/commands/invoice.ts
1658
2075
  import * as fs7 from "fs";
1659
2076
  import * as path7 from "path";
1660
- import * as os5 from "os";
2077
+ import * as os6 from "os";
1661
2078
  async function ensureDataPermission(config) {
1662
2079
  if (config.dataPermission) return config;
1663
2080
  if (!config.mainApiUrl) return config;
1664
2081
  const dp = await fetchDataPermission(config.mainApiUrl, config.authToken);
1665
2082
  if (!dp) return config;
1666
- const configPath = path7.join(os5.homedir(), ".siluzan", "config.json");
2083
+ const configPath = path7.join(os6.homedir(), ".siluzan", "config.json");
1667
2084
  try {
1668
2085
  const raw = fs7.existsSync(configPath) ? JSON.parse(fs7.readFileSync(configPath, "utf8")) : {};
1669
2086
  raw.dataPermission = dp;
@@ -3374,7 +3791,7 @@ async function runOptimizeChildren(opts) {
3374
3791
  }
3375
3792
 
3376
3793
  // src/commands/forewarning.ts
3377
- import os6 from "os";
3794
+ import os7 from "os";
3378
3795
  import path8 from "path";
3379
3796
  import QRCode from "qrcode";
3380
3797
  import open from "open";
@@ -3582,7 +3999,7 @@ async function runForewarningNotifyAccounts(opts) {
3582
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");
3583
4000
  if (qrLink) {
3584
4001
  try {
3585
- const imgPath = path8.join(os6.tmpdir(), "siluzan-wechat-qr.png");
4002
+ const imgPath = path8.join(os7.tmpdir(), "siluzan-wechat-qr.png");
3586
4003
  await QRCode.toFile(imgPath, qrLink, { width: 300 });
3587
4004
  await open(imgPath);
3588
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
@@ -7201,6 +7618,7 @@ async function runLogin(opts = {}) {
7201
7618
  process.exit(1);
7202
7619
  }
7203
7620
  writeSharedConfig({ apiKey: key });
7621
+ refreshSiluzanUser(DEFAULT_API_BASE, { authToken: "", apiKey: key });
7204
7622
  console.log(`
7205
7623
  \u2705 API Key \u5DF2\u4FDD\u5B58\uFF08${maskSecret(key)}\uFF09`);
7206
7624
  console.log(` \u914D\u7F6E\u6587\u4EF6\uFF1A${CONFIG_FILE}`);
@@ -7253,6 +7671,7 @@ async function runLogin(opts = {}) {
7253
7671
  process.exit(1);
7254
7672
  }
7255
7673
  writeSharedConfig({ apiKey });
7674
+ refreshSiluzanUser(DEFAULT_API_BASE, { authToken: "", apiKey });
7256
7675
  console.log(`
7257
7676
  \u2705 API Key \u5DF2\u4FDD\u5B58\uFF08${maskSecret(apiKey)}\uFF09`);
7258
7677
  console.log(` \u914D\u7F6E\u6587\u4EF6\uFF1A${CONFIG_FILE}`);
@@ -7276,6 +7695,9 @@ var program = new Command();
7276
7695
  program.name("siluzan-tso").description(
7277
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"
7278
7697
  ).version(getVersion());
7698
+ program.hook("preAction", () => {
7699
+ setSiluzanCliInvocation(redactCliArgvForSentry(process.argv));
7700
+ });
7279
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) => {
7280
7702
  await runLogin({ apiKey: opts.apiKey });
7281
7703
  });
@@ -7311,7 +7733,7 @@ program.command("init").description("\u5C06 siluzan-tso Skill \u6587\u4EF6\u5199
7311
7733
  });
7312
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(
7313
7735
  "-m, --media <type>",
7314
- `\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | Yandex | MetaAd | BingV2 | Kwai\uFF08\u7559\u7A7A\u5219\u67E5\u5168\u90E8\uFF09`
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`
7315
7737
  ).option("-k, --keyword <text>", "\u6309\u8D26\u6237\u540D\u79F0\u6216\u8D26\u6237 ID \u641C\u7D22").option(
7316
7738
  "-s, --status <status>",
7317
7739
  "\u8D26\u6237\u72B6\u6001\uFF1Anormal\uFF08\u6B63\u5E38\uFF09| invalid\uFF08\u5931\u6548\uFF09| all\uFF08\u5168\u90E8\uFF0C\u9ED8\u8BA4\uFF09",
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.0.0-beta.22",
4
- "publishedAt": 1774420324671
3
+ "version": "1.0.0-beta.23",
4
+ "publishedAt": 1774425204857
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.0.0-beta.22",
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"