@mauricode/token-derby 3.1.0 → 3.1.2
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 +5 -0
- package/dist/bin.js +178 -44
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -103,4 +103,9 @@ at usage you didn't produce.
|
|
|
103
103
|
- `TOKEN_DERBY_API_BASE` — override the API base URL (default: `https://token-derby.mauricode.co.uk/api`)
|
|
104
104
|
- `TOKEN_DERBY_HOME` — override the data directory (default: `~/.token-derby`)
|
|
105
105
|
- `TOKEN_DERBY_CLAUDE_DIR` — override the transcripts directory (default: `~/.claude/projects`)
|
|
106
|
+
- `CLAUDE_CONFIG_DIR` — Claude Code's own config override. When set, transcripts are read from `$CLAUDE_CONFIG_DIR/projects`. `TOKEN_DERBY_CLAUDE_DIR` still wins.
|
|
107
|
+
|
|
108
|
+
Token Derby counts usage from this machine's filesystem only. If Claude Code runs
|
|
109
|
+
in a container, over SSH, or on another machine, join the race from there — `join`
|
|
110
|
+
warns before entering a race whose primary model has no transcripts to read.
|
|
106
111
|
- **Top-5 conversations (primary):** a race can be created so that only each racer's **5 most-active conversations per heartbeat** count toward their **primary** model's score (secondaries unaffected). The race creator opts in at `token-derby create` (prompt) or, for organisation-scheduled races, via the "Primary top-5 cap" option on the schedule tab of `token-derby web`. Off by default (every conversation counts).
|
package/dist/bin.js
CHANGED
|
@@ -857,12 +857,13 @@ function apiBase() {
|
|
|
857
857
|
var HEARTBEAT_INTERVAL_MS = 6e4;
|
|
858
858
|
var SCAN_TIMEOUT_MS = HEARTBEAT_INTERVAL_MS * 0.75;
|
|
859
859
|
var HEARTBEAT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
|
|
860
|
+
var PRIMARY_SILENT_THRESHOLD = 10;
|
|
860
861
|
|
|
861
862
|
// src/version.ts
|
|
862
863
|
import { createRequire } from "module";
|
|
863
864
|
function readVersion() {
|
|
864
|
-
if ("3.1.
|
|
865
|
-
return "3.1.
|
|
865
|
+
if ("3.1.2".length > 0) {
|
|
866
|
+
return "3.1.2";
|
|
866
867
|
}
|
|
867
868
|
try {
|
|
868
869
|
const req = createRequire(import.meta.url);
|
|
@@ -897,7 +898,11 @@ function activeRacesDir() {
|
|
|
897
898
|
return path2.join(homeDir(), "active-races");
|
|
898
899
|
}
|
|
899
900
|
function claudeProjectsDir() {
|
|
900
|
-
|
|
901
|
+
const override = process.env.TOKEN_DERBY_CLAUDE_DIR;
|
|
902
|
+
if (override) return override;
|
|
903
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
904
|
+
if (configDir) return path2.join(configDir, "projects");
|
|
905
|
+
return path2.join(os2.homedir(), ".claude", "projects");
|
|
901
906
|
}
|
|
902
907
|
function codexSessionsDir() {
|
|
903
908
|
return process.env.TOKEN_DERBY_CODEX_DIR ?? path2.join(os2.homedir(), ".codex");
|
|
@@ -1559,7 +1564,7 @@ function ModelList(props) {
|
|
|
1559
1564
|
] }) });
|
|
1560
1565
|
}
|
|
1561
1566
|
function StatusScreen(props) {
|
|
1562
|
-
const { race, ownHorseId, ownHorseName, ownColors, ownUserName, lastHeartbeatAgoSec, lastHeartbeatOk, stalled, stallReason, primaryModel } = props;
|
|
1567
|
+
const { race, ownHorseId, ownHorseName, ownColors, ownUserName, lastHeartbeatAgoSec, lastHeartbeatOk, stalled, stallReason, primarySilent, primarySourceDir, primaryModel } = props;
|
|
1563
1568
|
if (!race) {
|
|
1564
1569
|
return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsx7(Text7, { children: "Joining race\u2026" }) });
|
|
1565
1570
|
}
|
|
@@ -1630,6 +1635,15 @@ function StatusScreen(props) {
|
|
|
1630
1635
|
"\u26A0 ",
|
|
1631
1636
|
stallReason ?? "Can't read token usage",
|
|
1632
1637
|
". Your race continues."
|
|
1638
|
+
] }),
|
|
1639
|
+
!stalled && primarySilent && /* @__PURE__ */ jsxs5(Text7, { color: "yellow", children: [
|
|
1640
|
+
"\u26A0 No ",
|
|
1641
|
+
MODEL_LABELS[primaryModel ?? "claude"],
|
|
1642
|
+
" transcripts in ",
|
|
1643
|
+
PRIMARY_SILENT_THRESHOLD,
|
|
1644
|
+
" beats",
|
|
1645
|
+
primarySourceDir ? ` \u2014 nothing under ${primarySourceDir}` : "",
|
|
1646
|
+
". Your race continues, but your horse cannot move until they can be read."
|
|
1633
1647
|
] })
|
|
1634
1648
|
] }),
|
|
1635
1649
|
primaryModel && /* @__PURE__ */ jsx7(ModelList, { primaryModel }),
|
|
@@ -1869,6 +1883,24 @@ async function readCompleteLines(file, start, end) {
|
|
|
1869
1883
|
}
|
|
1870
1884
|
}
|
|
1871
1885
|
|
|
1886
|
+
// src/tokens/source-root.ts
|
|
1887
|
+
var SourceRootMissing = class extends Error {
|
|
1888
|
+
constructor(dir) {
|
|
1889
|
+
super(`No history directory at ${dir}`);
|
|
1890
|
+
this.dir = dir;
|
|
1891
|
+
this.name = "SourceRootMissing";
|
|
1892
|
+
}
|
|
1893
|
+
dir;
|
|
1894
|
+
};
|
|
1895
|
+
async function readRoot(dir, read) {
|
|
1896
|
+
try {
|
|
1897
|
+
return await read();
|
|
1898
|
+
} catch (e) {
|
|
1899
|
+
if (e?.code === "ENOENT") throw new SourceRootMissing(dir);
|
|
1900
|
+
throw e;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1872
1904
|
// src/tokens/transcripts.ts
|
|
1873
1905
|
var MAX_PROJECT_DEPTH = 8;
|
|
1874
1906
|
function conversationId(file, root) {
|
|
@@ -1905,29 +1937,33 @@ async function sumTokens() {
|
|
|
1905
1937
|
return { input, output };
|
|
1906
1938
|
}
|
|
1907
1939
|
async function listJsonlFiles(root) {
|
|
1908
|
-
const
|
|
1940
|
+
const entries = await readEntries(root, true);
|
|
1909
1941
|
const out = [];
|
|
1910
|
-
for (const
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
if (!stat4.isDirectory()) continue;
|
|
1914
|
-
await collectJsonl(projectDir, MAX_PROJECT_DEPTH, out);
|
|
1942
|
+
for (const entry of entries) {
|
|
1943
|
+
if (!await isDirectory(entry, root)) continue;
|
|
1944
|
+
await collectJsonl(path6.join(root, entry.name), MAX_PROJECT_DEPTH, out);
|
|
1915
1945
|
}
|
|
1916
1946
|
return out;
|
|
1917
1947
|
}
|
|
1918
1948
|
async function collectJsonl(dir, depth, out) {
|
|
1919
1949
|
if (depth <= 0) return;
|
|
1920
|
-
const
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
const child = path6.join(dir, entry);
|
|
1926
|
-
const st = await fs4.stat(child);
|
|
1927
|
-
if (st.isDirectory()) await collectJsonl(child, depth - 1, out);
|
|
1950
|
+
for (const entry of await readEntries(dir, false)) {
|
|
1951
|
+
if (entry.name.endsWith(".jsonl")) {
|
|
1952
|
+
out.push(path6.join(dir, entry.name));
|
|
1953
|
+
} else if (depth > 1 && await isDirectory(entry, dir)) {
|
|
1954
|
+
await collectJsonl(path6.join(dir, entry.name), depth - 1, out);
|
|
1928
1955
|
}
|
|
1929
1956
|
}
|
|
1930
1957
|
}
|
|
1958
|
+
async function readEntries(dir, failLoud) {
|
|
1959
|
+
if (failLoud) return readRoot(dir, () => fs4.readdir(dir, { withFileTypes: true }));
|
|
1960
|
+
return fs4.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
1961
|
+
}
|
|
1962
|
+
async function isDirectory(entry, parent) {
|
|
1963
|
+
if (entry.isDirectory()) return true;
|
|
1964
|
+
if (!entry.isSymbolicLink()) return false;
|
|
1965
|
+
return fs4.stat(path6.join(parent, entry.name)).then((st) => st.isDirectory()).catch(() => false);
|
|
1966
|
+
}
|
|
1931
1967
|
function addNum(value) {
|
|
1932
1968
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1933
1969
|
}
|
|
@@ -1960,11 +1996,8 @@ function num(v) {
|
|
|
1960
1996
|
}
|
|
1961
1997
|
async function sumCodexByConversation() {
|
|
1962
1998
|
const root = codexSessionsDir();
|
|
1963
|
-
await fs5.stat(root);
|
|
1964
|
-
const files =
|
|
1965
|
-
...await collectRollouts(path7.join(root, "sessions")),
|
|
1966
|
-
...await collectRollouts(path7.join(root, "archived_sessions"))
|
|
1967
|
-
];
|
|
1999
|
+
await readRoot(root, () => fs5.stat(root));
|
|
2000
|
+
const files = await listCodexRollouts(root);
|
|
1968
2001
|
const cache = await ScanCache.open("codex");
|
|
1969
2002
|
const totals = await mapWithConcurrency(
|
|
1970
2003
|
files,
|
|
@@ -1986,6 +2019,12 @@ async function sumCodexTokens() {
|
|
|
1986
2019
|
}
|
|
1987
2020
|
return { input, output };
|
|
1988
2021
|
}
|
|
2022
|
+
async function listCodexRollouts(root) {
|
|
2023
|
+
return [
|
|
2024
|
+
...await collectRollouts(path7.join(root, "sessions")),
|
|
2025
|
+
...await collectRollouts(path7.join(root, "archived_sessions"))
|
|
2026
|
+
];
|
|
2027
|
+
}
|
|
1989
2028
|
async function collectRollouts(dir) {
|
|
1990
2029
|
let entries;
|
|
1991
2030
|
try {
|
|
@@ -2056,7 +2095,7 @@ async function sumGeminiTokens() {
|
|
|
2056
2095
|
return { input, output };
|
|
2057
2096
|
}
|
|
2058
2097
|
async function listChatFiles(root) {
|
|
2059
|
-
const entries = await fs6.readdir(root);
|
|
2098
|
+
const entries = await readRoot(root, () => fs6.readdir(root));
|
|
2060
2099
|
const out = [];
|
|
2061
2100
|
for (const entry of entries) {
|
|
2062
2101
|
const chatsDir = path8.join(root, entry, "chats");
|
|
@@ -2154,7 +2193,7 @@ async function readAllSources(race, primary, progress) {
|
|
|
2154
2193
|
const primaryByConv = /* @__PURE__ */ new Map();
|
|
2155
2194
|
if (primaryResult.ok) {
|
|
2156
2195
|
for (const [id, totals] of primaryResult.map) primaryByConv.set(id, scoreFor(race, totals));
|
|
2157
|
-
} else if (primaryResult.err
|
|
2196
|
+
} else if (!(primaryResult.err instanceof SourceRootMissing)) {
|
|
2158
2197
|
const err = primaryResult.err;
|
|
2159
2198
|
return { stall: `Can't read ${primary} token usage: ${err?.message ?? String(err)}` };
|
|
2160
2199
|
}
|
|
@@ -2221,6 +2260,7 @@ var RaceScoreTracker = class {
|
|
|
2221
2260
|
seq;
|
|
2222
2261
|
stalls = 0;
|
|
2223
2262
|
lastStall = null;
|
|
2263
|
+
primaryEmptyBeats = 0;
|
|
2224
2264
|
primary;
|
|
2225
2265
|
primaryTop5;
|
|
2226
2266
|
constructor(init, primary, primaryTop5) {
|
|
@@ -2253,6 +2293,7 @@ var RaceScoreTracker = class {
|
|
|
2253
2293
|
const v = reading.secondary[key];
|
|
2254
2294
|
if (v > 0) this.lastGood[key] = v;
|
|
2255
2295
|
}
|
|
2296
|
+
this.primaryEmptyBeats = reading.primaryByConv.size === 0 ? this.primaryEmptyBeats + 1 : 0;
|
|
2256
2297
|
for (const [id, v] of reading.primaryByConv) {
|
|
2257
2298
|
const prev = this.primaryConvLast[id] ?? 0;
|
|
2258
2299
|
if (v > prev) this.primaryConvLast[id] = v;
|
|
@@ -2304,6 +2345,10 @@ var RaceScoreTracker = class {
|
|
|
2304
2345
|
get stalled() {
|
|
2305
2346
|
return this.stalls >= STALL_THRESHOLD;
|
|
2306
2347
|
}
|
|
2348
|
+
/** The primary source has produced no conversations for long enough to be worth saying. */
|
|
2349
|
+
get primarySilent() {
|
|
2350
|
+
return this.primaryEmptyBeats >= PRIMARY_SILENT_THRESHOLD;
|
|
2351
|
+
}
|
|
2307
2352
|
/** Human-readable cause of the most recent stall (null once a good read recovers). */
|
|
2308
2353
|
get stallReason() {
|
|
2309
2354
|
return this.lastStall;
|
|
@@ -2332,6 +2377,75 @@ var RaceScoreTracker = class {
|
|
|
2332
2377
|
}
|
|
2333
2378
|
};
|
|
2334
2379
|
|
|
2380
|
+
// src/tokens/source-probe.ts
|
|
2381
|
+
import * as fs7 from "fs/promises";
|
|
2382
|
+
var ROOTS = {
|
|
2383
|
+
claude: claudeProjectsDir,
|
|
2384
|
+
codex: codexSessionsDir,
|
|
2385
|
+
gemini: geminiTmpDir
|
|
2386
|
+
};
|
|
2387
|
+
var LISTERS = {
|
|
2388
|
+
claude: listJsonlFiles,
|
|
2389
|
+
codex: listCodexRollouts,
|
|
2390
|
+
gemini: listChatFiles
|
|
2391
|
+
};
|
|
2392
|
+
var LABELS2 = { claude: "Claude", codex: "Codex", gemini: "Gemini" };
|
|
2393
|
+
function sourceDir(key) {
|
|
2394
|
+
return ROOTS[key]();
|
|
2395
|
+
}
|
|
2396
|
+
async function probeSource(key) {
|
|
2397
|
+
const dir = ROOTS[key]();
|
|
2398
|
+
const exists = await fs7.stat(dir).then((st) => st.isDirectory()).catch(() => false);
|
|
2399
|
+
if (!exists) return { key, dir, exists: false, projects: 0, transcripts: 0 };
|
|
2400
|
+
const entries = await fs7.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
2401
|
+
const projects = entries.filter((e) => e.isDirectory() || e.isSymbolicLink()).length;
|
|
2402
|
+
const files = await LISTERS[key](dir).catch(() => []);
|
|
2403
|
+
return { key, dir, exists: true, projects, transcripts: files.length };
|
|
2404
|
+
}
|
|
2405
|
+
function overrideVar(key) {
|
|
2406
|
+
return `TOKEN_DERBY_${key.toUpperCase()}_DIR`;
|
|
2407
|
+
}
|
|
2408
|
+
async function confirmEmptySource(opts) {
|
|
2409
|
+
if (opts.probe.transcripts > 0) return true;
|
|
2410
|
+
opts.warn(describeEmptySource(opts.probe));
|
|
2411
|
+
if (!opts.interactive) return true;
|
|
2412
|
+
return opts.ask();
|
|
2413
|
+
}
|
|
2414
|
+
function describeEmptySource(probe) {
|
|
2415
|
+
const label = LABELS2[probe.key];
|
|
2416
|
+
const populated = probe.exists && probe.projects > 0;
|
|
2417
|
+
const reason = !probe.exists ? "does not exist" : populated ? `holds ${probe.projects} project ${probe.projects === 1 ? "directory" : "directories"}, none of which could be read` : "exists, but holds no transcripts";
|
|
2418
|
+
const lines = [
|
|
2419
|
+
`\u26A0 No ${label} transcripts found \u2014 your horse will not move.`,
|
|
2420
|
+
``,
|
|
2421
|
+
` Looked in: ${probe.dir}`,
|
|
2422
|
+
` (${reason})`,
|
|
2423
|
+
``
|
|
2424
|
+
];
|
|
2425
|
+
if (populated) {
|
|
2426
|
+
lines.push(
|
|
2427
|
+
` The directory is there and has history in it, so this is usually a`,
|
|
2428
|
+
` dangling symlink or a permissions problem on one of those projects.`,
|
|
2429
|
+
` To find dangling links:`,
|
|
2430
|
+
` find ${probe.dir} -type l ! -exec test -e {} \\; -print`,
|
|
2431
|
+
``
|
|
2432
|
+
);
|
|
2433
|
+
}
|
|
2434
|
+
lines.push(
|
|
2435
|
+
` Token Derby counts ${label} usage from this machine's own filesystem.`,
|
|
2436
|
+
` If ${label} runs in a container, over SSH, or on another machine, join`,
|
|
2437
|
+
` the race from there instead.`
|
|
2438
|
+
);
|
|
2439
|
+
if (probe.key === "claude") {
|
|
2440
|
+
lines.push(
|
|
2441
|
+
` If CLAUDE_CONFIG_DIR relocated your config, Token Derby follows it \u2014`,
|
|
2442
|
+
` check it points at the config root, not the projects directory.`
|
|
2443
|
+
);
|
|
2444
|
+
}
|
|
2445
|
+
lines.push(``, ` To read them from somewhere else: export ${overrideVar(probe.key)}=<dir>`);
|
|
2446
|
+
return lines.join("\n");
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2335
2449
|
// src/runtime/run-race.tsx
|
|
2336
2450
|
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2337
2451
|
function RunRace({ active, initialState, pendingMode, ownUserName }) {
|
|
@@ -2348,6 +2462,7 @@ function RunRace({ active, initialState, pendingMode, ownUserName }) {
|
|
|
2348
2462
|
const ctrl = useRef(new AbortController());
|
|
2349
2463
|
const [stalled, setStalled] = useState5(false);
|
|
2350
2464
|
const [stallReason, setStallReason] = useState5(null);
|
|
2465
|
+
const [primarySilent, setPrimarySilent] = useState5(false);
|
|
2351
2466
|
useEffect2(() => {
|
|
2352
2467
|
const t = setInterval(() => setTickNow(/* @__PURE__ */ new Date()), 1e3);
|
|
2353
2468
|
return () => clearInterval(t);
|
|
@@ -2379,6 +2494,7 @@ function RunRace({ active, initialState, pendingMode, ownUserName }) {
|
|
|
2379
2494
|
if (pendingRef.current && !isStall(reading)) tracker.reprime();
|
|
2380
2495
|
setStalled(tracker.stalled);
|
|
2381
2496
|
setStallReason(tracker.stalled ? tracker.stallReason : null);
|
|
2497
|
+
setPrimarySilent(tracker.primarySilent);
|
|
2382
2498
|
return tracker.nextBeat();
|
|
2383
2499
|
},
|
|
2384
2500
|
sendBeat: async (snapshot) => {
|
|
@@ -2446,6 +2562,8 @@ function RunRace({ active, initialState, pendingMode, ownUserName }) {
|
|
|
2446
2562
|
lastHeartbeatOk: lastHbOk,
|
|
2447
2563
|
stalled,
|
|
2448
2564
|
stallReason,
|
|
2565
|
+
primarySilent,
|
|
2566
|
+
primarySourceDir: sourceDir(active.primary_model),
|
|
2449
2567
|
primaryModel: active.primary_model
|
|
2450
2568
|
}
|
|
2451
2569
|
),
|
|
@@ -2513,6 +2631,28 @@ async function buildInitialState(args) {
|
|
|
2513
2631
|
};
|
|
2514
2632
|
}
|
|
2515
2633
|
|
|
2634
|
+
// src/ui/prompt.ts
|
|
2635
|
+
async function promptYesNo(question, opts = {}) {
|
|
2636
|
+
const input = opts.input ?? process.stdin;
|
|
2637
|
+
const output = opts.output ?? process.stdout;
|
|
2638
|
+
if (input === process.stdin) resetStdinAfterInk();
|
|
2639
|
+
const readline6 = await import("readline/promises");
|
|
2640
|
+
const rl = readline6.createInterface({ input, output });
|
|
2641
|
+
const a = (await rl.question(question)).trim().toLowerCase();
|
|
2642
|
+
rl.close();
|
|
2643
|
+
if (a === "") return opts.defaultYes !== false;
|
|
2644
|
+
return a === "y" || a === "yes";
|
|
2645
|
+
}
|
|
2646
|
+
function resetStdinAfterInk() {
|
|
2647
|
+
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
2648
|
+
process.stdin.setRawMode(false);
|
|
2649
|
+
}
|
|
2650
|
+
while (process.stdin.read() !== null) {
|
|
2651
|
+
}
|
|
2652
|
+
process.stdin.pause();
|
|
2653
|
+
process.stdin.ref();
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2516
2656
|
// src/commands/join.ts
|
|
2517
2657
|
function parsePrimaryFlag(argv) {
|
|
2518
2658
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -2607,6 +2747,19 @@ async function joinCommand(joinCode, argv = []) {
|
|
|
2607
2747
|
if (primaryFlag) chosenPrimary = primaryFlag;
|
|
2608
2748
|
else if (process.stdout.isTTY) chosenPrimary = await pickPrimary();
|
|
2609
2749
|
}
|
|
2750
|
+
const effectivePrimary = ownHorse?.primary_model ?? chosenPrimary;
|
|
2751
|
+
const proceed = await confirmEmptySource({
|
|
2752
|
+
probe: await probeSource(effectivePrimary),
|
|
2753
|
+
interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
2754
|
+
warn: (text) => console.error(`
|
|
2755
|
+
${text}
|
|
2756
|
+
`),
|
|
2757
|
+
ask: () => promptYesNo("Join anyway? [y/N] ", { defaultYes: false })
|
|
2758
|
+
});
|
|
2759
|
+
if (!proceed) {
|
|
2760
|
+
console.log("Cancelled.");
|
|
2761
|
+
return 1;
|
|
2762
|
+
}
|
|
2610
2763
|
let joinResp;
|
|
2611
2764
|
try {
|
|
2612
2765
|
joinResp = await joinRace(code, { stable_horse_id: chosenStableHorseId, primary_model: chosenPrimary });
|
|
@@ -2907,25 +3060,6 @@ async function webCommand(deps = {}) {
|
|
|
2907
3060
|
return 0;
|
|
2908
3061
|
}
|
|
2909
3062
|
|
|
2910
|
-
// src/ui/prompt.ts
|
|
2911
|
-
async function promptYesNo(question) {
|
|
2912
|
-
resetStdinAfterInk();
|
|
2913
|
-
const readline6 = await import("readline/promises");
|
|
2914
|
-
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
2915
|
-
const a = (await rl.question(question)).trim().toLowerCase();
|
|
2916
|
-
rl.close();
|
|
2917
|
-
return a === "" || a === "y" || a === "yes";
|
|
2918
|
-
}
|
|
2919
|
-
function resetStdinAfterInk() {
|
|
2920
|
-
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
2921
|
-
process.stdin.setRawMode(false);
|
|
2922
|
-
}
|
|
2923
|
-
while (process.stdin.read() !== null) {
|
|
2924
|
-
}
|
|
2925
|
-
process.stdin.pause();
|
|
2926
|
-
process.stdin.ref();
|
|
2927
|
-
}
|
|
2928
|
-
|
|
2929
3063
|
// src/commands/login.ts
|
|
2930
3064
|
function parseDeviceNameFlag(argv) {
|
|
2931
3065
|
for (let i = 0; i < argv.length; i++) {
|