@ait-co/console-cli 0.1.28 → 0.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -8,7 +8,8 @@ import { unzipSync } from "fflate";
8
8
  import { checkbox, confirm, editor, input, password, select } from "@inquirer/prompts";
9
9
  import { imageSize } from "image-size";
10
10
  import { execFile, spawn } from "node:child_process";
11
- import { constants, createReadStream } from "node:fs";
11
+ import { constants, createReadStream, existsSync } from "node:fs";
12
+ import { createInterface } from "node:readline/promises";
12
13
  import { promisify } from "node:util";
13
14
  import { createHash } from "node:crypto";
14
15
  //#region src/api/http.ts
@@ -3601,7 +3602,7 @@ function serviceStatusFor(entry) {
3601
3602
  }
3602
3603
  const POLL_MIN_INTERVAL_SEC = 30;
3603
3604
  const POLL_MAX_INTERVAL_SEC = 3600;
3604
- const statusCommand$1 = defineCommand({
3605
+ const statusCommand$2 = defineCommand({
3605
3606
  meta: {
3606
3607
  name: "status",
3607
3608
  description: "Show the derived review state of a mini-app (under-review / rejected / approved)."
@@ -5702,7 +5703,7 @@ const appCommand = defineCommand({
5702
5703
  }),
5703
5704
  ls: lsCommand$4,
5704
5705
  show: showCommand$2,
5705
- status: statusCommand$1,
5706
+ status: statusCommand$2,
5706
5707
  ratings: ratingsCommand,
5707
5708
  reports: reportsCommand,
5708
5709
  bundles: bundlesCommand,
@@ -9191,6 +9192,557 @@ const noticesCommand = defineCommand({
9191
9192
  }
9192
9193
  });
9193
9194
  //#endregion
9195
+ //#region src/version.ts
9196
+ function resolveVersion() {
9197
+ try {
9198
+ const injected = globalThis.AITCC_VERSION;
9199
+ if (typeof injected === "string" && injected.length > 0) return injected;
9200
+ } catch {}
9201
+ try {
9202
+ return "0.1.30";
9203
+ } catch {}
9204
+ return "0.0.0-dev";
9205
+ }
9206
+ const VERSION = resolveVersion();
9207
+ //#endregion
9208
+ //#region src/telemetry/state.ts
9209
+ /**
9210
+ * Telemetry consent state + anon_id I/O for console-cli.
9211
+ *
9212
+ * Storage: ~/.config/aitcc/telemetry.json (0600, XDG-aware via configDir())
9213
+ * Consistent with devtools' localStorage schema names where applicable.
9214
+ */
9215
+ /** Current policy version. Bump whenever the privacy policy changes. */
9216
+ const CURRENT_POLICY_VERSION = "2026-05-18";
9217
+ function telemetryFilePath() {
9218
+ return join(configDir(), "telemetry.json");
9219
+ }
9220
+ async function readStateFile() {
9221
+ let raw;
9222
+ try {
9223
+ raw = await readFile(telemetryFilePath(), "utf8");
9224
+ } catch {
9225
+ return null;
9226
+ }
9227
+ let parsed;
9228
+ try {
9229
+ parsed = JSON.parse(raw);
9230
+ } catch {
9231
+ return null;
9232
+ }
9233
+ if (!parsed || typeof parsed !== "object") return null;
9234
+ const obj = parsed;
9235
+ if (obj.schemaVersion !== 1) return null;
9236
+ if (obj.consent !== "granted" && obj.consent !== "denied" && obj.consent !== "undecided") return null;
9237
+ if (typeof obj.policyVersion !== "string") return null;
9238
+ return {
9239
+ schemaVersion: 1,
9240
+ consent: obj.consent,
9241
+ policyVersion: obj.policyVersion,
9242
+ ...typeof obj.anonId === "string" ? { anonId: obj.anonId } : {},
9243
+ ...typeof obj.tier0LastSent === "string" ? { tier0LastSent: obj.tier0LastSent } : {},
9244
+ ...obj.tier0OptOut === true ? { tier0OptOut: true } : {}
9245
+ };
9246
+ }
9247
+ async function writeStateFile(state) {
9248
+ const path = telemetryFilePath();
9249
+ await mkdir(dirname(path), {
9250
+ recursive: true,
9251
+ mode: 448
9252
+ });
9253
+ const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
9254
+ try {
9255
+ await writeFile(tmp, JSON.stringify(state, null, 2), { mode: 384 });
9256
+ await rename(tmp, path);
9257
+ } catch (err) {
9258
+ await unlink(tmp).catch(() => {});
9259
+ throw err;
9260
+ }
9261
+ }
9262
+ /** Read the raw consent from disk. Returns 'undecided' if no file. */
9263
+ async function readConsentState() {
9264
+ const s = await readStateFile();
9265
+ if (!s) return "undecided";
9266
+ return s.consent;
9267
+ }
9268
+ /**
9269
+ * Resolve effective consent with policy-version bump rule:
9270
+ * - Previously 'granted' but on an old policy version → revert to 'undecided'
9271
+ * - Previously 'denied' on any version → stay 'denied'
9272
+ */
9273
+ async function resolveEffectiveConsent() {
9274
+ const s = await readStateFile();
9275
+ if (!s) return "undecided";
9276
+ if (s.consent === "granted") {
9277
+ if (s.policyVersion !== "2026-05-18") return "undecided";
9278
+ return "granted";
9279
+ }
9280
+ return s.consent;
9281
+ }
9282
+ /**
9283
+ * Returns the stored anon_id, or generates + persists a new UUID v4.
9284
+ * Once generated it is never overwritten except after a successful deleteMyData call.
9285
+ */
9286
+ async function getOrCreateAnonId() {
9287
+ const s = await readStateFile();
9288
+ if (s?.anonId) return s.anonId;
9289
+ const id = crypto.randomUUID();
9290
+ await writeStateFile({
9291
+ ...s ?? {
9292
+ schemaVersion: 1,
9293
+ consent: "undecided",
9294
+ policyVersion: "2026-05-18"
9295
+ },
9296
+ anonId: id
9297
+ });
9298
+ return id;
9299
+ }
9300
+ async function acceptConsent() {
9301
+ await writeStateFile({
9302
+ schemaVersion: 1,
9303
+ consent: "granted",
9304
+ policyVersion: CURRENT_POLICY_VERSION,
9305
+ anonId: (await readStateFile())?.anonId ?? crypto.randomUUID()
9306
+ });
9307
+ }
9308
+ async function denyConsent() {
9309
+ const s = await readStateFile();
9310
+ await writeStateFile({
9311
+ schemaVersion: 1,
9312
+ consent: "denied",
9313
+ policyVersion: CURRENT_POLICY_VERSION,
9314
+ ...s?.anonId ? { anonId: s.anonId } : {}
9315
+ });
9316
+ }
9317
+ /** Returns true if Tier 0 pings are permanently opted out. */
9318
+ async function isTier0OptedOut() {
9319
+ return (await readStateFile())?.tier0OptOut === true;
9320
+ }
9321
+ /** Permanently opt out of Tier 0 pings (sets tier0OptOut: true). */
9322
+ async function setTier0OptOut(optOut) {
9323
+ const current = await readStateFile() ?? {
9324
+ schemaVersion: 1,
9325
+ consent: "undecided",
9326
+ policyVersion: "2026-05-18"
9327
+ };
9328
+ if (optOut) await writeStateFile({
9329
+ ...current,
9330
+ tier0OptOut: true
9331
+ });
9332
+ else {
9333
+ const { tier0OptOut: _removed, ...rest } = current;
9334
+ await writeStateFile(rest);
9335
+ }
9336
+ }
9337
+ /**
9338
+ * Returns the ISO date (YYYY-MM-DD) of the last sent Tier 0 ping, or null.
9339
+ */
9340
+ async function getTier0LastSent() {
9341
+ return (await readStateFile())?.tier0LastSent ?? null;
9342
+ }
9343
+ /**
9344
+ * Record that a Tier 0 ping was sent today (ISO date marker).
9345
+ */
9346
+ async function markTier0Sent(date) {
9347
+ await writeStateFile({
9348
+ ...await readStateFile() ?? {
9349
+ schemaVersion: 1,
9350
+ consent: "undecided",
9351
+ policyVersion: "2026-05-18"
9352
+ },
9353
+ tier0LastSent: date
9354
+ });
9355
+ }
9356
+ /**
9357
+ * Delete data: send DELETE /e?anon_id=... to the server (if we have an id),
9358
+ * then rotate local anon_id so subsequent events are unlinkable.
9359
+ */
9360
+ async function deleteMyData(endpoint) {
9361
+ const s = await readStateFile();
9362
+ if (!s?.anonId) return false;
9363
+ try {
9364
+ if (!(await fetch(`${endpoint}/e?anon_id=${encodeURIComponent(s.anonId)}`, { method: "DELETE" })).ok) return false;
9365
+ await writeStateFile({
9366
+ ...s,
9367
+ anonId: crypto.randomUUID()
9368
+ });
9369
+ return true;
9370
+ } catch {
9371
+ return false;
9372
+ }
9373
+ }
9374
+ //#endregion
9375
+ //#region src/telemetry/send.ts
9376
+ /**
9377
+ * Telemetry send — fire-and-forget with one retry.
9378
+ *
9379
+ * Tier 0 rules:
9380
+ * 1. No consent check — fires regardless of opt-in state (unless opted out via tier0OptOut).
9381
+ * 2. No anon_id — server generates a daily hash.
9382
+ * 3. 5 s timeout, no retry. Drops silently on any failure.
9383
+ *
9384
+ * Tier 1 rules:
9385
+ * 1. If consent ≠ "granted" — drop silently.
9386
+ * 2. POST event as JSON with 5 s timeout.
9387
+ * 3. On network error or non-2xx: retry ONCE after 2 s. On second failure: drop.
9388
+ * 4. Meta is capped at 256 bytes (JSON-serialized); oversized meta is dropped.
9389
+ * 5. All calls are non-blocking — caller never awaits send().
9390
+ */
9391
+ /** Meta size cap per server contract (JSON bytes). */
9392
+ const META_BYTE_CAP = 256;
9393
+ function sanitizeMeta(meta) {
9394
+ if (meta === void 0) return void 0;
9395
+ const serialized = JSON.stringify(meta);
9396
+ if (new TextEncoder().encode(serialized).byteLength > META_BYTE_CAP) return void 0;
9397
+ return meta;
9398
+ }
9399
+ async function doFetch(endpoint, payload) {
9400
+ const controller = new AbortController();
9401
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
9402
+ try {
9403
+ return (await fetch(`${endpoint}/e`, {
9404
+ method: "POST",
9405
+ headers: { "Content-Type": "application/json" },
9406
+ body: JSON.stringify(payload),
9407
+ signal: controller.signal
9408
+ })).ok;
9409
+ } catch {
9410
+ return false;
9411
+ } finally {
9412
+ clearTimeout(timeoutId);
9413
+ }
9414
+ }
9415
+ function delay(ms) {
9416
+ return new Promise((resolve) => setTimeout(resolve, ms));
9417
+ }
9418
+ /** Retry delay in ms — injectable for tests. */
9419
+ let RETRY_DELAY_MS = 2e3;
9420
+ /**
9421
+ * Send a Tier 1 telemetry event. Drops silently if consent is not 'granted'.
9422
+ * Returns a Promise but callers should NOT await it — fire-and-forget only.
9423
+ */
9424
+ async function send(endpoint, event, version, meta) {
9425
+ if (await readConsentState() !== "granted") return;
9426
+ const sanitized = sanitizeMeta(meta);
9427
+ const payload = {
9428
+ tier: 1,
9429
+ source: "console-cli",
9430
+ event,
9431
+ anon_id: await getOrCreateAnonId(),
9432
+ version,
9433
+ ts: Date.now(),
9434
+ ...sanitized !== void 0 ? { meta: sanitized } : {}
9435
+ };
9436
+ if (await doFetch(endpoint, payload)) return;
9437
+ await delay(RETRY_DELAY_MS);
9438
+ await doFetch(endpoint, payload);
9439
+ }
9440
+ /**
9441
+ * Send a Tier 0 anonymous daily ping.
9442
+ * - No anon_id (server generates a daily hash from IP+UA).
9443
+ * - No retry. 5 s timeout. Fire-and-forget.
9444
+ * - Callers should NOT await this.
9445
+ */
9446
+ async function sendTier0Ping(endpoint, version) {
9447
+ const payload = {
9448
+ tier: 0,
9449
+ source: "console-cli",
9450
+ version,
9451
+ platform: process.platform
9452
+ };
9453
+ const controller = new AbortController();
9454
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
9455
+ try {
9456
+ await fetch(`${endpoint}/e`, {
9457
+ method: "POST",
9458
+ headers: { "Content-Type": "application/json" },
9459
+ body: JSON.stringify(payload),
9460
+ signal: controller.signal
9461
+ });
9462
+ } catch {} finally {
9463
+ clearTimeout(timeoutId);
9464
+ }
9465
+ }
9466
+ //#endregion
9467
+ //#region src/telemetry/index.ts
9468
+ /**
9469
+ * Telemetry client — internal to @ait-co/console-cli.
9470
+ *
9471
+ * Usage: import { trackInvocation, trackTier0Ping } from './telemetry/index.js'
9472
+ *
9473
+ * Tier 0 (opt-out): anonymous daily ping. Fires on every invocation; client-side
9474
+ * daily dedupe via tier0LastSent. Respects AITC_TELEMETRY=off, --no-telemetry,
9475
+ * and permanent tier0OptOut flag.
9476
+ *
9477
+ * Tier 1 (opt-in): detailed events. First invocation on a TTY prompts the user;
9478
+ * non-TTY (CI) defaults to deny.
9479
+ *
9480
+ * Endpoint override for staging: AITCC_TELEMETRY_ENV=staging
9481
+ * (or automatically when VERSION contains '-dev').
9482
+ */
9483
+ function resolveEndpoint() {
9484
+ if (process.env.AITCC_TELEMETRY_ENV === "staging") return "https://t-staging.aitc.dev";
9485
+ if (VERSION.includes("-dev")) return "https://t-staging.aitc.dev";
9486
+ return "https://t.aitc.dev";
9487
+ }
9488
+ const TELEMETRY_ENDPOINT = resolveEndpoint();
9489
+ /** Returns true if this is the very first run (telemetry.json does not exist). */
9490
+ function isFirstRun() {
9491
+ return !existsSync(telemetryFilePath());
9492
+ }
9493
+ /**
9494
+ * Prompt for consent on TTY. Defaults to deny on any non-TTY or error.
9495
+ * Called once per install (no file yet) when stdin/stdout are both TTYs.
9496
+ */
9497
+ async function promptConsent() {
9498
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
9499
+ await denyConsent();
9500
+ return;
9501
+ }
9502
+ const rl = createInterface({
9503
+ input: process.stdin,
9504
+ output: process.stdout
9505
+ });
9506
+ try {
9507
+ process.stderr.write([
9508
+ "",
9509
+ "aitcc 사용 개선을 위해 익명 사용 통계를 수집해도 될까요?",
9510
+ " · 개인 식별 정보 없음 · 랜덤 익명 ID만 사용 · 언제든 off 가능",
9511
+ " 자세한 내용: https://docs.aitc.dev/privacy",
9512
+ ""
9513
+ ].join("\n"));
9514
+ if ((await rl.question("보내도 될까요? [y/N] ")).trim().toLowerCase() === "y") await acceptConsent();
9515
+ else await denyConsent();
9516
+ } catch {
9517
+ await denyConsent();
9518
+ } finally {
9519
+ rl.close();
9520
+ }
9521
+ }
9522
+ /**
9523
+ * Check whether telemetry is globally disabled via environment or CLI flag.
9524
+ * Accepts the parsed --no-telemetry flag value from argv.
9525
+ */
9526
+ function isTelemetryGloballyDisabled(noTelemetryFlag) {
9527
+ if (noTelemetryFlag) return true;
9528
+ const env = process.env.AITC_TELEMETRY;
9529
+ if (env !== void 0 && env.toLowerCase() === "off") return true;
9530
+ return false;
9531
+ }
9532
+ /**
9533
+ * Send a Tier 0 anonymous daily ping (fire-and-forget).
9534
+ *
9535
+ * Skips if:
9536
+ * - AITC_TELEMETRY=off or --no-telemetry flag
9537
+ * - tier0OptOut === true in the state file
9538
+ * - already sent today (tier0LastSent === today's ISO date)
9539
+ *
9540
+ * On success, records today's date in tier0LastSent for client-side daily dedupe.
9541
+ * The server also deduplicates server-side via KV, so this is an extra client guard.
9542
+ */
9543
+ async function trackTier0Ping(noTelemetryFlag = false) {
9544
+ try {
9545
+ if (isTelemetryGloballyDisabled(noTelemetryFlag)) return;
9546
+ if (await isTier0OptedOut()) return;
9547
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
9548
+ if (await getTier0LastSent() === today) return;
9549
+ sendTier0Ping(TELEMETRY_ENDPOINT, VERSION);
9550
+ await markTier0Sent(today);
9551
+ } catch {}
9552
+ }
9553
+ /** True only on the first invocation after a fresh install. */
9554
+ async function isNewInstall() {
9555
+ const markerPath = `${telemetryFilePath()}.install`;
9556
+ if (existsSync(markerPath)) return false;
9557
+ try {
9558
+ await writeFile(markerPath, "1", { mode: 384 });
9559
+ return true;
9560
+ } catch {
9561
+ return false;
9562
+ }
9563
+ }
9564
+ /**
9565
+ * Called at CLI entry point with the resolved top-level command name.
9566
+ * Handles first-run Tier 1 consent prompt, install detection, and Tier 1 event send.
9567
+ * Fire-and-forget: do NOT await this.
9568
+ *
9569
+ * Note: Tier 0 ping is sent separately via trackTier0Ping() before this call.
9570
+ */
9571
+ async function trackInvocation(command, noTelemetryFlag = false) {
9572
+ try {
9573
+ if (isTelemetryGloballyDisabled(noTelemetryFlag)) return;
9574
+ if (isFirstRun()) await promptConsent();
9575
+ if (await resolveEffectiveConsent() !== "granted") return;
9576
+ if (await isNewInstall()) send(TELEMETRY_ENDPOINT, "cli_install", VERSION, {
9577
+ platform: process.platform,
9578
+ arch: process.arch
9579
+ });
9580
+ send(TELEMETRY_ENDPOINT, "cli_invoked", VERSION, { command });
9581
+ } catch {}
9582
+ }
9583
+ const telemetryCommand = defineCommand({
9584
+ meta: {
9585
+ name: "telemetry",
9586
+ description: "Manage anonymous usage telemetry."
9587
+ },
9588
+ subCommands: {
9589
+ status: defineCommand({
9590
+ meta: {
9591
+ name: "status",
9592
+ description: "Show current telemetry status for both Tier 0 and Tier 1."
9593
+ },
9594
+ args: { json: {
9595
+ type: "boolean",
9596
+ description: "Emit machine-readable JSON.",
9597
+ default: false
9598
+ } },
9599
+ async run({ args }) {
9600
+ const [tier1Consent, tier0OptOut, tier0LastSent, anonId] = await Promise.all([
9601
+ resolveEffectiveConsent(),
9602
+ isTier0OptedOut(),
9603
+ getTier0LastSent(),
9604
+ resolveEffectiveConsent().then((c) => c === "granted" ? getOrCreateAnonId() : null)
9605
+ ]);
9606
+ const filePath = telemetryFilePath();
9607
+ const tier0Status = tier0OptOut ? "off (opted out)" : "on";
9608
+ const tier0Display = tier0LastSent ? `${tier0Status} (last sent: ${tier0LastSent})` : tier0Status;
9609
+ if (args.json) {
9610
+ process.stdout.write(`${JSON.stringify({
9611
+ ok: true,
9612
+ tier0: {
9613
+ status: tier0OptOut ? "opted-out" : "on",
9614
+ lastSent: tier0LastSent ?? null
9615
+ },
9616
+ tier1: {
9617
+ consent: tier1Consent,
9618
+ policyVersion: CURRENT_POLICY_VERSION,
9619
+ ...anonId ? { anonId } : {}
9620
+ },
9621
+ endpoint: TELEMETRY_ENDPOINT,
9622
+ filePath
9623
+ })}\n`);
9624
+ return exitAfterFlush(ExitCode.Ok);
9625
+ }
9626
+ process.stdout.write("Telemetry status\n");
9627
+ process.stdout.write(` Tier 0 (anonymous daily ping): ${tier0Display}\n`);
9628
+ process.stdout.write(` Tier 1 (opt-in events): ${tier1Consent} (policyVersion: ${CURRENT_POLICY_VERSION})\n`);
9629
+ process.stdout.write(`\nEndpoint: ${TELEMETRY_ENDPOINT}\n`);
9630
+ if (anonId) process.stdout.write(`Anon ID: ${anonId}\n`);
9631
+ process.stdout.write(`State file: ${filePath}\n`);
9632
+ return exitAfterFlush(ExitCode.Ok);
9633
+ }
9634
+ }),
9635
+ enable: defineCommand({
9636
+ meta: {
9637
+ name: "enable",
9638
+ description: "Enable anonymous usage telemetry (opt-in)."
9639
+ },
9640
+ args: { json: {
9641
+ type: "boolean",
9642
+ description: "Emit machine-readable JSON.",
9643
+ default: false
9644
+ } },
9645
+ async run({ args }) {
9646
+ await acceptConsent();
9647
+ const anonId = await getOrCreateAnonId();
9648
+ if (args.json) process.stdout.write(`${JSON.stringify({
9649
+ ok: true,
9650
+ consent: "granted",
9651
+ anonId
9652
+ })}\n`);
9653
+ else {
9654
+ process.stdout.write("Telemetry enabled. Thank you!\n");
9655
+ process.stdout.write(`Anon ID: ${anonId}\n`);
9656
+ }
9657
+ return exitAfterFlush(ExitCode.Ok);
9658
+ }
9659
+ }),
9660
+ disable: defineCommand({
9661
+ meta: {
9662
+ name: "disable",
9663
+ description: "Disable anonymous usage telemetry."
9664
+ },
9665
+ args: { json: {
9666
+ type: "boolean",
9667
+ description: "Emit machine-readable JSON.",
9668
+ default: false
9669
+ } },
9670
+ async run({ args }) {
9671
+ await denyConsent();
9672
+ if (args.json) process.stdout.write(`${JSON.stringify({
9673
+ ok: true,
9674
+ consent: "denied"
9675
+ })}\n`);
9676
+ else process.stdout.write("Telemetry disabled.\n");
9677
+ return exitAfterFlush(ExitCode.Ok);
9678
+ }
9679
+ }),
9680
+ delete: defineCommand({
9681
+ meta: {
9682
+ name: "delete",
9683
+ description: "Delete your telemetry data from the server and rotate the local anon_id."
9684
+ },
9685
+ args: { json: {
9686
+ type: "boolean",
9687
+ description: "Emit machine-readable JSON.",
9688
+ default: false
9689
+ } },
9690
+ async run({ args }) {
9691
+ const beforeConsent = await readConsentState();
9692
+ const ok = await deleteMyData(TELEMETRY_ENDPOINT);
9693
+ if (args.json) process.stdout.write(`${JSON.stringify({
9694
+ ok,
9695
+ ...ok ? {} : { reason: beforeConsent === "undecided" ? "no-data" : "server-error" }
9696
+ })}\n`);
9697
+ else if (ok) process.stdout.write("Deletion request sent. Your data has been removed and a new anon ID assigned.\n");
9698
+ else if (beforeConsent === "undecided") process.stdout.write("No telemetry data to delete (telemetry was never enabled).\n");
9699
+ else process.stderr.write("Deletion request failed. Please try again or contact the maintainers.\n");
9700
+ return exitAfterFlush(ok || beforeConsent === "undecided" ? ExitCode.Ok : ExitCode.NetworkError);
9701
+ }
9702
+ }),
9703
+ "tier0-off": defineCommand({
9704
+ meta: {
9705
+ name: "tier0-off",
9706
+ description: "Permanently opt out of the Tier 0 anonymous daily ping."
9707
+ },
9708
+ args: { json: {
9709
+ type: "boolean",
9710
+ description: "Emit machine-readable JSON.",
9711
+ default: false
9712
+ } },
9713
+ async run({ args }) {
9714
+ await setTier0OptOut(true);
9715
+ if (args.json) process.stdout.write(`${JSON.stringify({
9716
+ ok: true,
9717
+ tier0: { status: "opted-out" }
9718
+ })}\n`);
9719
+ else process.stdout.write("Tier 0 anonymous ping disabled. No daily pings will be sent.\n");
9720
+ return exitAfterFlush(ExitCode.Ok);
9721
+ }
9722
+ }),
9723
+ "tier0-on": defineCommand({
9724
+ meta: {
9725
+ name: "tier0-on",
9726
+ description: "Re-enable the Tier 0 anonymous daily ping after a previous tier0-off."
9727
+ },
9728
+ args: { json: {
9729
+ type: "boolean",
9730
+ description: "Emit machine-readable JSON.",
9731
+ default: false
9732
+ } },
9733
+ async run({ args }) {
9734
+ await setTier0OptOut(false);
9735
+ if (args.json) process.stdout.write(`${JSON.stringify({
9736
+ ok: true,
9737
+ tier0: { status: "on" }
9738
+ })}\n`);
9739
+ else process.stdout.write("Tier 0 anonymous ping re-enabled.\n");
9740
+ return exitAfterFlush(ExitCode.Ok);
9741
+ }
9742
+ })
9743
+ }
9744
+ });
9745
+ //#endregion
9194
9746
  //#region src/github.ts
9195
9747
  const REPO_OWNER = "apps-in-toss-community";
9196
9748
  const REPO_NAME = "console-cli";
@@ -9324,19 +9876,6 @@ function sha256OfFile(path) {
9324
9876
  });
9325
9877
  }
9326
9878
  //#endregion
9327
- //#region src/version.ts
9328
- function resolveVersion() {
9329
- try {
9330
- const injected = globalThis.AITCC_VERSION;
9331
- if (typeof injected === "string" && injected.length > 0) return injected;
9332
- } catch {}
9333
- try {
9334
- return "0.1.28";
9335
- } catch {}
9336
- return "0.0.0-dev";
9337
- }
9338
- const VERSION = resolveVersion();
9339
- //#endregion
9340
9879
  //#region src/commands/upgrade.ts
9341
9880
  const execFileP = promisify(execFile);
9342
9881
  function isStandaloneBinary() {
@@ -10484,10 +11023,15 @@ const main = defineCommand({
10484
11023
  keys: keysCommand,
10485
11024
  notices: noticesCommand,
10486
11025
  me: meCommand,
11026
+ telemetry: telemetryCommand,
10487
11027
  completion: completionCommand
10488
11028
  }
10489
11029
  });
10490
11030
  cleanupStaleUpgradeArtifacts().catch(() => {});
11031
+ const _telemetryCmd = process.argv.slice(2).find((a) => !a.startsWith("-")) ?? "(none)";
11032
+ const _noTelemetry = process.argv.includes("--no-telemetry");
11033
+ trackTier0Ping(_noTelemetry);
11034
+ trackInvocation(_telemetryCmd, _noTelemetry);
10491
11035
  runMain(main);
10492
11036
  //#endregion
10493
11037
  export {};