@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/README.en.md +207 -0
- package/README.md +109 -79
- package/dist/cli.mjs +560 -16
- package/dist/cli.mjs.map +1 -1
- package/package.json +2 -3
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$
|
|
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$
|
|
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 {};
|