@moda-ai/cli 1.55.28 → 1.55.29
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.js +358 -39
- package/package.json +1 -1
- package/skills/integration/index.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -91,6 +91,7 @@ import {
|
|
|
91
91
|
createCliOutput,
|
|
92
92
|
embeddedTenantIdFromApiKey,
|
|
93
93
|
errorToAgentError,
|
|
94
|
+
getActiveProfile,
|
|
94
95
|
getApiRequestCount,
|
|
95
96
|
getModaDir,
|
|
96
97
|
isPlatformApiKey,
|
|
@@ -128,7 +129,7 @@ import {
|
|
|
128
129
|
} from "./cli-0v6na3yp.js";
|
|
129
130
|
|
|
130
131
|
// src/cli.ts
|
|
131
|
-
import { randomUUID as
|
|
132
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
132
133
|
import { z } from "zod";
|
|
133
134
|
|
|
134
135
|
// src/replays.ts
|
|
@@ -387,10 +388,223 @@ function spawnDetachedNode(script, options = {}, spawnImpl = spawn) {
|
|
|
387
388
|
}
|
|
388
389
|
|
|
389
390
|
// src/telemetry.ts
|
|
390
|
-
import { randomUUID } from "node:crypto";
|
|
391
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
392
|
+
|
|
393
|
+
// src/telemetry-spool.ts
|
|
394
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
395
|
+
import {
|
|
396
|
+
closeSync,
|
|
397
|
+
constants as fsConstants,
|
|
398
|
+
fchmodSync,
|
|
399
|
+
mkdirSync,
|
|
400
|
+
openSync,
|
|
401
|
+
readFileSync,
|
|
402
|
+
renameSync,
|
|
403
|
+
rmSync,
|
|
404
|
+
statSync,
|
|
405
|
+
unlinkSync,
|
|
406
|
+
writeSync
|
|
407
|
+
} from "node:fs";
|
|
408
|
+
import { join } from "node:path";
|
|
409
|
+
var SPOOL_MAX_BYTES = 256 * 1024;
|
|
410
|
+
var FLUSH_MIN_EVENTS = 20;
|
|
411
|
+
var FLUSH_MAX_AGE_MS = 30000;
|
|
412
|
+
var SPOOL_MAX_EVENT_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
413
|
+
var FLUSH_LOCK_STALE_MS = 30000;
|
|
414
|
+
var MAX_BATCH_EVENTS = 50;
|
|
415
|
+
var SPOOL_LOCK_STALE_MS = 2000;
|
|
416
|
+
var SPOOL_LOCK_WAIT_MS = 200;
|
|
417
|
+
var SPOOL_FILE = "usage-spool.jsonl";
|
|
418
|
+
var LOCK_FILE = "flush.lock";
|
|
419
|
+
var SPOOL_LOCK_FILE = "spool.lock";
|
|
420
|
+
var O_NOFOLLOW = fsConstants.O_NOFOLLOW ?? 0;
|
|
421
|
+
function telemetrySpoolDir() {
|
|
422
|
+
return join(getModaDir(), "telemetry");
|
|
423
|
+
}
|
|
424
|
+
function keyFingerprint(apiKey) {
|
|
425
|
+
return createHash("sha256").update(`moda-cli-usage-spool\x00${apiKey}`).digest("hex").slice(0, 16);
|
|
426
|
+
}
|
|
427
|
+
function ensureDir(dir) {
|
|
428
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
429
|
+
}
|
|
430
|
+
function appendLines(path, text) {
|
|
431
|
+
const fd = openSync(path, fsConstants.O_WRONLY | fsConstants.O_APPEND | fsConstants.O_CREAT | O_NOFOLLOW, 384);
|
|
432
|
+
try {
|
|
433
|
+
try {
|
|
434
|
+
fchmodSync(fd, 384);
|
|
435
|
+
} catch {}
|
|
436
|
+
writeSync(fd, text);
|
|
437
|
+
} finally {
|
|
438
|
+
closeSync(fd);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function sleepSync(ms) {
|
|
442
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
443
|
+
}
|
|
444
|
+
function createLockFile(path, staleMs, now) {
|
|
445
|
+
for (let attempt = 0;attempt < 2; attempt += 1) {
|
|
446
|
+
try {
|
|
447
|
+
const fd = openSync(path, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | O_NOFOLLOW, 384);
|
|
448
|
+
try {
|
|
449
|
+
writeSync(fd, String(process.pid));
|
|
450
|
+
} finally {
|
|
451
|
+
closeSync(fd);
|
|
452
|
+
}
|
|
453
|
+
return true;
|
|
454
|
+
} catch (error) {
|
|
455
|
+
if (error.code !== "EEXIST")
|
|
456
|
+
return false;
|
|
457
|
+
try {
|
|
458
|
+
if (now - statSync(path).mtimeMs < staleMs)
|
|
459
|
+
return false;
|
|
460
|
+
unlinkSync(path);
|
|
461
|
+
} catch {}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
function withSpoolLock(dir, fn) {
|
|
467
|
+
const path = join(dir, SPOOL_LOCK_FILE);
|
|
468
|
+
const deadline = Date.now() + SPOOL_LOCK_WAIT_MS;
|
|
469
|
+
while (!createLockFile(path, SPOOL_LOCK_STALE_MS, Date.now())) {
|
|
470
|
+
if (Date.now() >= deadline)
|
|
471
|
+
throw new Error("telemetry spool is locked");
|
|
472
|
+
sleepSync(5);
|
|
473
|
+
}
|
|
474
|
+
try {
|
|
475
|
+
return fn();
|
|
476
|
+
} finally {
|
|
477
|
+
try {
|
|
478
|
+
unlinkSync(path);
|
|
479
|
+
} catch {}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function fileSize(path) {
|
|
483
|
+
try {
|
|
484
|
+
return statSync(path).size;
|
|
485
|
+
} catch {
|
|
486
|
+
return 0;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function parseEntries(text) {
|
|
490
|
+
const entries = [];
|
|
491
|
+
for (const line of text.split(`
|
|
492
|
+
`)) {
|
|
493
|
+
if (!line)
|
|
494
|
+
continue;
|
|
495
|
+
try {
|
|
496
|
+
const entry = JSON.parse(line);
|
|
497
|
+
if (entry && entry.v === 1 && typeof entry.url === "string" && typeof entry.profile === "string" && typeof entry.key === "string" && typeof entry.t === "number" && entry.event && typeof entry.event === "object") {
|
|
498
|
+
entries.push(entry);
|
|
499
|
+
}
|
|
500
|
+
} catch {}
|
|
501
|
+
}
|
|
502
|
+
return entries;
|
|
503
|
+
}
|
|
504
|
+
function sameGroup(a, b) {
|
|
505
|
+
return a.url === b.url && a.profile === b.profile && a.key === b.key;
|
|
506
|
+
}
|
|
507
|
+
function appendSpoolEntry(entry, dir = telemetrySpoolDir()) {
|
|
508
|
+
ensureDir(dir);
|
|
509
|
+
const path = join(dir, SPOOL_FILE);
|
|
510
|
+
const line = `${JSON.stringify(entry)}
|
|
511
|
+
`;
|
|
512
|
+
return withSpoolLock(dir, () => {
|
|
513
|
+
if (fileSize(path) + Buffer.byteLength(line) > SPOOL_MAX_BYTES)
|
|
514
|
+
return false;
|
|
515
|
+
appendLines(path, line);
|
|
516
|
+
return true;
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
function readSpool(dir = telemetrySpoolDir()) {
|
|
520
|
+
try {
|
|
521
|
+
return parseEntries(readFileSync(join(dir, SPOOL_FILE), "utf8"));
|
|
522
|
+
} catch {
|
|
523
|
+
return [];
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function discardSpool(dir = telemetrySpoolDir()) {
|
|
527
|
+
try {
|
|
528
|
+
rmSync(join(dir, SPOOL_FILE), { force: true });
|
|
529
|
+
} catch {}
|
|
530
|
+
}
|
|
531
|
+
function flushDue(entries, group, now) {
|
|
532
|
+
let count = 0;
|
|
533
|
+
for (const entry of entries) {
|
|
534
|
+
if (!sameGroup(entry, group))
|
|
535
|
+
continue;
|
|
536
|
+
count += 1;
|
|
537
|
+
if (count >= FLUSH_MIN_EVENTS || now - entry.t >= FLUSH_MAX_AGE_MS)
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
function acquireFlushLock(dir = telemetrySpoolDir(), now = Date.now()) {
|
|
543
|
+
ensureDir(dir);
|
|
544
|
+
const path = join(dir, LOCK_FILE);
|
|
545
|
+
return createLockFile(path, FLUSH_LOCK_STALE_MS, now) ? path : undefined;
|
|
546
|
+
}
|
|
547
|
+
function releaseFlushLock(path) {
|
|
548
|
+
if (!path)
|
|
549
|
+
return;
|
|
550
|
+
try {
|
|
551
|
+
unlinkSync(path);
|
|
552
|
+
} catch {}
|
|
553
|
+
}
|
|
554
|
+
function claimSpoolGroup(group, dir = telemetrySpoolDir(), now = Date.now(), limit = Number.POSITIVE_INFINITY) {
|
|
555
|
+
try {
|
|
556
|
+
return withSpoolLock(dir, () => claimLocked(group, dir, now, limit));
|
|
557
|
+
} catch {
|
|
558
|
+
return [];
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
function claimLocked(group, dir, now, limit) {
|
|
562
|
+
const path = join(dir, SPOOL_FILE);
|
|
563
|
+
const claim = join(dir, `${SPOOL_FILE}.${process.pid}.${randomUUID()}.claim`);
|
|
564
|
+
try {
|
|
565
|
+
renameSync(path, claim);
|
|
566
|
+
} catch {
|
|
567
|
+
return [];
|
|
568
|
+
}
|
|
569
|
+
let entries = [];
|
|
570
|
+
try {
|
|
571
|
+
entries = parseEntries(readFileSync(claim, "utf8"));
|
|
572
|
+
} catch {} finally {
|
|
573
|
+
rmSync(claim, { force: true });
|
|
574
|
+
}
|
|
575
|
+
const mine = [];
|
|
576
|
+
const others = [];
|
|
577
|
+
for (const entry of entries) {
|
|
578
|
+
const age = now - entry.t;
|
|
579
|
+
if (age > SPOOL_MAX_EVENT_AGE_MS || age < -FLUSH_LOCK_STALE_MS)
|
|
580
|
+
continue;
|
|
581
|
+
(sameGroup(entry, group) && mine.length < limit ? mine : others).push(entry);
|
|
582
|
+
}
|
|
583
|
+
if (others.length > 0) {
|
|
584
|
+
try {
|
|
585
|
+
const text = others.map((entry) => `${JSON.stringify(entry)}
|
|
586
|
+
`).join("");
|
|
587
|
+
if (fileSize(path) + Buffer.byteLength(text) <= SPOOL_MAX_BYTES)
|
|
588
|
+
appendLines(path, text);
|
|
589
|
+
} catch {}
|
|
590
|
+
}
|
|
591
|
+
return mine;
|
|
592
|
+
}
|
|
593
|
+
function batchBodies(entries, now = Date.now()) {
|
|
594
|
+
const bodies = [];
|
|
595
|
+
for (let start = 0;start < entries.length; start += MAX_BATCH_EVENTS) {
|
|
596
|
+
const events = entries.slice(start, start + MAX_BATCH_EVENTS).map((entry) => ({ ...entry.event, age_ms: Math.min(SPOOL_MAX_EVENT_AGE_MS, Math.max(0, Math.round(now - entry.t))) }));
|
|
597
|
+
bodies.push(JSON.stringify(events));
|
|
598
|
+
}
|
|
599
|
+
return bodies;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// src/telemetry.ts
|
|
391
603
|
var INLINE_TELEMETRY_TIMEOUT_MS = 500;
|
|
392
604
|
var DETACHED_TELEMETRY_TIMEOUT_MS = 5000;
|
|
393
|
-
var
|
|
605
|
+
var DETACHED_TELEMETRY_HARD_STOP_MS = 20000;
|
|
606
|
+
var MAX_FLUSH_BATCHES = Math.floor(DETACHED_TELEMETRY_HARD_STOP_MS / (2 * DETACHED_TELEMETRY_TIMEOUT_MS));
|
|
607
|
+
var INVOCATION_ID = randomUUID2();
|
|
394
608
|
var TELEMETRY_FLAG = "no-telemetry";
|
|
395
609
|
var TELEMETRY_TRANSPORT_ENV = "MODA_CLI_TELEMETRY_TRANSPORT";
|
|
396
610
|
var MAX_TELEMETRY_DURATION_MS = 86400000;
|
|
@@ -560,48 +774,103 @@ function safeCode(value) {
|
|
|
560
774
|
var TELEMETRY_SENDER_SCRIPT = [
|
|
561
775
|
"try {",
|
|
562
776
|
" let raw = '';",
|
|
563
|
-
|
|
564
|
-
" const
|
|
777
|
+
" let lock;",
|
|
778
|
+
" const release = () => { if (typeof lock === 'string') { try { require('node:fs').unlinkSync(lock); } catch {} lock = undefined; } };",
|
|
779
|
+
` const quit = setTimeout(() => { release(); process.exit(0); }, ${DETACHED_TELEMETRY_HARD_STOP_MS});`,
|
|
780
|
+
" const done = () => { release(); clearTimeout(quit); };",
|
|
781
|
+
" const post = (r, body) => new Promise((resolve) => {",
|
|
782
|
+
" try {",
|
|
783
|
+
" const u = new URL(r.url);",
|
|
784
|
+
" const lib = require(u.protocol === 'https:' ? 'node:https' : 'node:http');",
|
|
785
|
+
" const headers = Object.assign({}, r.headers, { 'Content-Length': Buffer.byteLength(body) });",
|
|
786
|
+
` const req = lib.request(u, { method: 'POST', headers, agent: false, timeout: ${DETACHED_TELEMETRY_TIMEOUT_MS} }, (res) => {`,
|
|
787
|
+
" res.on('error', () => resolve(0));",
|
|
788
|
+
" res.on('end', () => resolve(res.statusCode || 0));",
|
|
789
|
+
" res.resume();",
|
|
790
|
+
" });",
|
|
791
|
+
" req.on('timeout', () => req.destroy());",
|
|
792
|
+
" req.on('error', () => resolve(0));",
|
|
793
|
+
" req.end(body);",
|
|
794
|
+
" } catch { resolve(0); }",
|
|
795
|
+
" });",
|
|
796
|
+
" const run = async (requests) => {",
|
|
797
|
+
" for (const r of requests) {",
|
|
798
|
+
" if (!r || typeof r.url !== 'string' || typeof r.body !== 'string' || !r.headers || typeof r.headers !== 'object') continue;",
|
|
799
|
+
" const status = await post(r, r.body);",
|
|
800
|
+
" if (status !== 400 || r.body[0] !== '[') continue;",
|
|
801
|
+
" let events = [];",
|
|
802
|
+
" try { events = JSON.parse(r.body); } catch {}",
|
|
803
|
+
" await Promise.all((Array.isArray(events) ? events : []).map((event) => post(r, JSON.stringify(event))));",
|
|
804
|
+
" }",
|
|
805
|
+
" };",
|
|
565
806
|
" process.stdin.setEncoding('utf8');",
|
|
566
807
|
" process.stdin.on('error', () => {});",
|
|
567
808
|
" process.stdin.on('data', (chunk) => { raw += chunk; });",
|
|
568
809
|
" process.stdin.on('end', () => {",
|
|
569
|
-
"
|
|
570
|
-
"
|
|
571
|
-
"
|
|
572
|
-
"
|
|
573
|
-
"
|
|
574
|
-
|
|
575
|
-
" fetch(r.url, { method: 'POST', headers: r.headers, body: r.body, signal: c.signal })",
|
|
576
|
-
" .then((res) => (res.body && res.body.cancel ? res.body.cancel() : undefined))",
|
|
577
|
-
" .catch(() => {})",
|
|
578
|
-
" .finally(() => { clearTimeout(t); done(); });",
|
|
579
|
-
" } catch { done(); }",
|
|
810
|
+
" let job = null;",
|
|
811
|
+
" try { job = raw ? JSON.parse(raw) : null; } catch {}",
|
|
812
|
+
" raw = '';",
|
|
813
|
+
" if (job && typeof job.lock === 'string') lock = job.lock;",
|
|
814
|
+
" const requests = job && Array.isArray(job.requests) ? job.requests : [];",
|
|
815
|
+
" run(requests).catch(() => {}).finally(done);",
|
|
580
816
|
" });",
|
|
581
817
|
"} catch {}"
|
|
582
818
|
].join(`
|
|
583
819
|
`);
|
|
584
|
-
function spawnDetachedTelemetrySender(
|
|
585
|
-
return spawnDetachedNode(TELEMETRY_SENDER_SCRIPT, { stdin: JSON.stringify(
|
|
820
|
+
function spawnDetachedTelemetrySender(job, spawnImpl = spawn2) {
|
|
821
|
+
return spawnDetachedNode(TELEMETRY_SENDER_SCRIPT, { stdin: JSON.stringify(job), dropEnv: ["MODA_API_KEY"] }, spawnImpl);
|
|
822
|
+
}
|
|
823
|
+
async function sendTelemetryJobInline(job) {
|
|
824
|
+
try {
|
|
825
|
+
for (const pending of job.requests)
|
|
826
|
+
await sendInline(pending);
|
|
827
|
+
} finally {
|
|
828
|
+
releaseFlushLock(job.lock);
|
|
829
|
+
}
|
|
586
830
|
}
|
|
587
831
|
async function sendInline(request) {
|
|
832
|
+
const status = await postInline(request);
|
|
833
|
+
if (status !== 400 || !request.body.startsWith("["))
|
|
834
|
+
return;
|
|
835
|
+
let events = [];
|
|
836
|
+
try {
|
|
837
|
+
events = JSON.parse(request.body);
|
|
838
|
+
} catch {}
|
|
839
|
+
if (!Array.isArray(events))
|
|
840
|
+
return;
|
|
841
|
+
await Promise.all(events.map((event) => postInline({ ...request, body: JSON.stringify(event) })));
|
|
842
|
+
}
|
|
843
|
+
async function postInline(request) {
|
|
588
844
|
const controller = new AbortController;
|
|
589
845
|
const timeout = setTimeout(() => controller.abort(), INLINE_TELEMETRY_TIMEOUT_MS);
|
|
590
846
|
if (typeof timeout.unref === "function") {
|
|
591
847
|
timeout.unref();
|
|
592
848
|
}
|
|
593
849
|
try {
|
|
594
|
-
await fetch(request.url, {
|
|
850
|
+
const response = await fetch(request.url, {
|
|
595
851
|
method: "POST",
|
|
596
852
|
headers: request.headers,
|
|
597
853
|
body: request.body,
|
|
598
854
|
signal: controller.signal
|
|
599
855
|
});
|
|
600
|
-
|
|
856
|
+
return response.status;
|
|
857
|
+
} catch {
|
|
858
|
+
return 0;
|
|
859
|
+
} finally {
|
|
601
860
|
clearTimeout(timeout);
|
|
602
861
|
}
|
|
603
862
|
}
|
|
604
|
-
function
|
|
863
|
+
function usageRequest(baseUrl, apiKey, body) {
|
|
864
|
+
return {
|
|
865
|
+
url: `${baseUrl}/api/v1/data/cli-usage`,
|
|
866
|
+
headers: {
|
|
867
|
+
...apiKeyHeaders(apiKey),
|
|
868
|
+
"Content-Type": "application/json"
|
|
869
|
+
},
|
|
870
|
+
body
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
function buildCliUsageEvent(input) {
|
|
605
874
|
const baseUrl = resolveModaBaseUrlStrict(input.profile ? { profile: input.profile } : {});
|
|
606
875
|
if (!baseUrl)
|
|
607
876
|
return;
|
|
@@ -613,12 +882,8 @@ function buildCliUsageRequest(input) {
|
|
|
613
882
|
delete adoption[key];
|
|
614
883
|
}
|
|
615
884
|
return {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
...apiKeyHeaders(input.apiKey),
|
|
619
|
-
"Content-Type": "application/json"
|
|
620
|
-
},
|
|
621
|
-
body: JSON.stringify({
|
|
885
|
+
baseUrl,
|
|
886
|
+
event: {
|
|
622
887
|
invocation_id: INVOCATION_ID,
|
|
623
888
|
command: safeCode(input.command.slice(0, 64)) ?? "unknown",
|
|
624
889
|
status: input.status,
|
|
@@ -637,27 +902,79 @@ function buildCliUsageRequest(input) {
|
|
|
637
902
|
...((alias) => alias ? { legacy_alias: alias } : {})(safeCode(sanitizeReasonCode(input.legacyAlias?.replace(/ /g, ":"), 64)))
|
|
638
903
|
},
|
|
639
904
|
...adoption
|
|
640
|
-
}
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
function telemetryProfileName(profile) {
|
|
909
|
+
try {
|
|
910
|
+
return getActiveProfile(profile ? { profile } : {}).name;
|
|
911
|
+
} catch {
|
|
912
|
+
return "";
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
function queueCliUsageEvent(request, group, event, now) {
|
|
916
|
+
const dir = telemetrySpoolDir();
|
|
917
|
+
const entry = { v: 1, ...group, t: now, event };
|
|
918
|
+
let unspooled = [];
|
|
919
|
+
try {
|
|
920
|
+
if (!appendSpoolEntry(entry, dir))
|
|
921
|
+
unspooled = [entry];
|
|
922
|
+
} catch {
|
|
923
|
+
return { requests: [request] };
|
|
924
|
+
}
|
|
925
|
+
const due = unspooled.length > 0 || envOptOutSet(process.env.CI) || flushDue(readSpool(dir), group, now);
|
|
926
|
+
if (!due)
|
|
927
|
+
return;
|
|
928
|
+
const lock = acquireFlushLock(dir, now);
|
|
929
|
+
if (!lock)
|
|
930
|
+
return unspooled.length > 0 ? { requests: [request] } : undefined;
|
|
931
|
+
const limit = MAX_FLUSH_BATCHES * MAX_BATCH_EVENTS - unspooled.length;
|
|
932
|
+
const entries = [...claimSpoolGroup(group, dir, now, limit), ...unspooled];
|
|
933
|
+
if (entries.length === 0) {
|
|
934
|
+
releaseFlushLock(lock);
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
return {
|
|
938
|
+
requests: batchBodies(entries, now).map((body) => ({ url: request.url, headers: request.headers, body })),
|
|
939
|
+
lock
|
|
641
940
|
};
|
|
642
941
|
}
|
|
942
|
+
function discardSpoolOnEnvOptOut() {
|
|
943
|
+
if (!isTelemetryDisabled({}))
|
|
944
|
+
return false;
|
|
945
|
+
discardSpool();
|
|
946
|
+
return true;
|
|
947
|
+
}
|
|
643
948
|
async function sendCliUsageTelemetry(input) {
|
|
644
949
|
try {
|
|
950
|
+
if (discardSpoolOnEnvOptOut())
|
|
951
|
+
return;
|
|
645
952
|
if (!input.apiKey || isTelemetryDisabled(input.flags)) {
|
|
646
953
|
return;
|
|
647
954
|
}
|
|
648
|
-
const
|
|
649
|
-
if (!
|
|
955
|
+
const built = buildCliUsageEvent(input);
|
|
956
|
+
if (!built)
|
|
650
957
|
return;
|
|
651
|
-
|
|
958
|
+
const request = usageRequest(built.baseUrl, input.apiKey, JSON.stringify(built.event));
|
|
959
|
+
if (process.env[TELEMETRY_TRANSPORT_ENV] === "inline") {
|
|
960
|
+
await sendInline(request);
|
|
652
961
|
return;
|
|
653
962
|
}
|
|
654
|
-
|
|
963
|
+
const group = {
|
|
964
|
+
url: built.baseUrl,
|
|
965
|
+
profile: telemetryProfileName(input.profile),
|
|
966
|
+
key: keyFingerprint(input.apiKey)
|
|
967
|
+
};
|
|
968
|
+
const job = queueCliUsageEvent(request, group, built.event, Date.now());
|
|
969
|
+
if (!job || spawnDetachedTelemetrySender(job))
|
|
970
|
+
return;
|
|
971
|
+
await sendTelemetryJobInline(job);
|
|
655
972
|
} catch {}
|
|
656
973
|
}
|
|
657
974
|
|
|
658
975
|
// src/update-check.ts
|
|
659
|
-
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
660
|
-
import { join } from "node:path";
|
|
976
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
977
|
+
import { join as join2 } from "node:path";
|
|
661
978
|
var UPDATE_CHECK_FLAG = "no-update-check";
|
|
662
979
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
663
980
|
var REGISTRY_TIMEOUT_MS = 1500;
|
|
@@ -670,11 +987,11 @@ function isUpdateCheckDisabled(flags, env = process.env) {
|
|
|
670
987
|
return optOutFlagSet(flags, UPDATE_CHECK_FLAG) || envSwitchedOff(env.MODA_CLI_UPDATE_CHECK) || envOptOutSet(env.DO_NOT_TRACK) || envOptOutSet(env.CI);
|
|
671
988
|
}
|
|
672
989
|
function cachePath(options = {}) {
|
|
673
|
-
return
|
|
990
|
+
return join2(getModaDir(options), "update-check.json");
|
|
674
991
|
}
|
|
675
992
|
function readCache(options = {}) {
|
|
676
993
|
try {
|
|
677
|
-
const parsed = JSON.parse(
|
|
994
|
+
const parsed = JSON.parse(readFileSync2(cachePath(options), "utf8"));
|
|
678
995
|
if (parsed && typeof parsed === "object" && typeof parsed.checked_at === "number" && typeof parsed.latest === "string") {
|
|
679
996
|
return parsed;
|
|
680
997
|
}
|
|
@@ -683,7 +1000,7 @@ function readCache(options = {}) {
|
|
|
683
1000
|
}
|
|
684
1001
|
function writeCache(cache, options = {}) {
|
|
685
1002
|
try {
|
|
686
|
-
|
|
1003
|
+
mkdirSync2(getModaDir(options), { recursive: true });
|
|
687
1004
|
writeFileSync(cachePath(options), `${JSON.stringify(cache, null, 2)}
|
|
688
1005
|
`, "utf8");
|
|
689
1006
|
} catch {}
|
|
@@ -5445,7 +5762,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
5445
5762
|
}
|
|
5446
5763
|
const data = await callDataAPI(reviewPath, {
|
|
5447
5764
|
method: "POST",
|
|
5448
|
-
body: JSON.stringify({ ...reviewBody, request_nonce:
|
|
5765
|
+
body: JSON.stringify({ ...reviewBody, request_nonce: randomUUID3() }),
|
|
5449
5766
|
headers: { "Content-Type": "application/json" }
|
|
5450
5767
|
});
|
|
5451
5768
|
context.output.writeData(data);
|
|
@@ -5538,7 +5855,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
5538
5855
|
});
|
|
5539
5856
|
break;
|
|
5540
5857
|
}
|
|
5541
|
-
const sentBody = request.body && request.nonce ? { ...request.body, request_nonce:
|
|
5858
|
+
const sentBody = request.body && request.nonce ? { ...request.body, request_nonce: randomUUID3() } : request.body;
|
|
5542
5859
|
const data = await callDataAPI(request.path, {
|
|
5543
5860
|
method: request.method,
|
|
5544
5861
|
...sentBody ? { body: JSON.stringify(sentBody), headers: { "Content-Type": "application/json" } } : {}
|
|
@@ -7100,6 +7417,8 @@ async function dispatchParsedCommandInner(typedParsed, startedAt) {
|
|
|
7100
7417
|
const switchError = normalizeBooleanFlags(flags);
|
|
7101
7418
|
const typedFlags = stripUpdateCheckFlag(stripTelemetryFlag(flags));
|
|
7102
7419
|
const telemetryDisabled = isTelemetryDisabled(flags);
|
|
7420
|
+
if (telemetryDisabled)
|
|
7421
|
+
discardSpoolOnEnvOptOut();
|
|
7103
7422
|
const updateCheckDisabled = isUpdateCheckDisabled(flags);
|
|
7104
7423
|
if (command === "--version" || command === "-V" || flags.version === "true") {
|
|
7105
7424
|
console.log(`moda-cli ${VERSION}`);
|
package/package.json
CHANGED