salidium 0.2.3 → 0.2.4
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 +4 -3
- package/bundle/salidium.mjs +572 -64
- package/bundle/ui/assets/index-C21z644P.js +86 -0
- package/bundle/ui/assets/index-J4zWsYLU.css +1 -0
- package/bundle/ui/index.html +2 -2
- package/package.json +1 -1
- package/bundle/ui/assets/index-CZSEaq9g.css +0 -1
- package/bundle/ui/assets/index-CsY_ZjeU.js +0 -86
package/bundle/salidium.mjs
CHANGED
|
@@ -21651,7 +21651,7 @@ function explainerCwd() {
|
|
|
21651
21651
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
21652
21652
|
return dir;
|
|
21653
21653
|
}
|
|
21654
|
-
function runProcess(invocation, timeoutMs) {
|
|
21654
|
+
function runProcess(invocation, timeoutMs, signal) {
|
|
21655
21655
|
return new Promise((resolve2, reject) => {
|
|
21656
21656
|
const path = trustedPathEntries().join(delimiter2);
|
|
21657
21657
|
const child = spawn(invocation.command, invocation.args, {
|
|
@@ -21666,14 +21666,29 @@ function runProcess(invocation, timeoutMs) {
|
|
|
21666
21666
|
let err = "";
|
|
21667
21667
|
let outBytes = 0;
|
|
21668
21668
|
let settled = false;
|
|
21669
|
+
let timer;
|
|
21670
|
+
const cleanup = () => {
|
|
21671
|
+
if (timer)
|
|
21672
|
+
clearTimeout(timer);
|
|
21673
|
+
signal?.removeEventListener("abort", abort);
|
|
21674
|
+
};
|
|
21669
21675
|
const fail = (error51) => {
|
|
21670
21676
|
if (settled)
|
|
21671
21677
|
return;
|
|
21672
21678
|
settled = true;
|
|
21673
|
-
|
|
21679
|
+
cleanup();
|
|
21674
21680
|
reject(error51);
|
|
21675
21681
|
};
|
|
21676
|
-
const
|
|
21682
|
+
const abort = () => {
|
|
21683
|
+
child.kill("SIGKILL");
|
|
21684
|
+
fail(new Error("explainer canceled"));
|
|
21685
|
+
};
|
|
21686
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
21687
|
+
if (signal?.aborted) {
|
|
21688
|
+
abort();
|
|
21689
|
+
return;
|
|
21690
|
+
}
|
|
21691
|
+
timer = setTimeout(() => {
|
|
21677
21692
|
child.kill("SIGKILL");
|
|
21678
21693
|
fail(new Error(`explainer timed out after ${timeoutMs}ms`));
|
|
21679
21694
|
}, timeoutMs);
|
|
@@ -21697,7 +21712,7 @@ function runProcess(invocation, timeoutMs) {
|
|
|
21697
21712
|
if (settled)
|
|
21698
21713
|
return;
|
|
21699
21714
|
settled = true;
|
|
21700
|
-
|
|
21715
|
+
cleanup();
|
|
21701
21716
|
if (code === 0)
|
|
21702
21717
|
resolve2(out);
|
|
21703
21718
|
else
|
|
@@ -21793,7 +21808,7 @@ function createClaudeExplainerBackend(resolvedCommand) {
|
|
|
21793
21808
|
throw new Error("trusted claude command is unavailable");
|
|
21794
21809
|
const invocation = buildClaudeInvocation(request, command);
|
|
21795
21810
|
return {
|
|
21796
|
-
output: await runProcess(invocation, request.timeoutMs),
|
|
21811
|
+
output: await runProcess(invocation, request.timeoutMs, request.signal),
|
|
21797
21812
|
model: invocation.model
|
|
21798
21813
|
};
|
|
21799
21814
|
}
|
|
@@ -21811,7 +21826,7 @@ function createCodexExplainerBackend(resolvedCommand) {
|
|
|
21811
21826
|
writeFileSync(schemaPath, JSON.stringify(request.schema), { mode: 384 });
|
|
21812
21827
|
const invocation = buildCodexInvocation(request, schemaPath, command);
|
|
21813
21828
|
return {
|
|
21814
|
-
output: await runProcess(invocation, request.timeoutMs),
|
|
21829
|
+
output: await runProcess(invocation, request.timeoutMs, request.signal),
|
|
21815
21830
|
model: invocation.model
|
|
21816
21831
|
};
|
|
21817
21832
|
}
|
|
@@ -22087,7 +22102,8 @@ async function explainWithStatus(state, opts = {}) {
|
|
|
22087
22102
|
evidence,
|
|
22088
22103
|
schema: SCHEMA,
|
|
22089
22104
|
model,
|
|
22090
|
-
timeoutMs
|
|
22105
|
+
timeoutMs,
|
|
22106
|
+
signal: opts.signal
|
|
22091
22107
|
});
|
|
22092
22108
|
raw = result.output;
|
|
22093
22109
|
generatedBy = result.model;
|
|
@@ -23076,6 +23092,8 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
23076
23092
|
closed = false;
|
|
23077
23093
|
flushFailures = 0;
|
|
23078
23094
|
explanationStatus;
|
|
23095
|
+
/** Owns the one provider call this coordinator may have in flight. */
|
|
23096
|
+
explanationAbort;
|
|
23079
23097
|
/** The stop in force for this session; the registry pushes a change to every live coordinator. */
|
|
23080
23098
|
cadence;
|
|
23081
23099
|
idleEndTimer;
|
|
@@ -23107,11 +23125,11 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
23107
23125
|
flushThreshold: 250,
|
|
23108
23126
|
checkpointEvery: 500,
|
|
23109
23127
|
explain: envAllows,
|
|
23110
|
-
//
|
|
23111
|
-
//
|
|
23112
|
-
cadence:
|
|
23128
|
+
// Fail closed when a caller does not pass a stored preference. Model work must always be an
|
|
23129
|
+
// explicit opt-in, including in future coordinator call sites that bypass the registry.
|
|
23130
|
+
cadence: "off",
|
|
23113
23131
|
idleEndMs: IDLE_END_MS,
|
|
23114
|
-
explainSession: explainWithStatus,
|
|
23132
|
+
explainSession: (state2, signal) => explainWithStatus(state2, { signal }),
|
|
23115
23133
|
now: Date.now,
|
|
23116
23134
|
...args.options
|
|
23117
23135
|
};
|
|
@@ -23282,6 +23300,7 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
23282
23300
|
this.cadence = cadence;
|
|
23283
23301
|
if (cadence === "off") {
|
|
23284
23302
|
this.clearIdleEnd();
|
|
23303
|
+
this.explanationAbort?.abort();
|
|
23285
23304
|
this.explanationStatus = "disabled";
|
|
23286
23305
|
} else if (wasOff) {
|
|
23287
23306
|
this.explanationStatus = void 0;
|
|
@@ -23341,7 +23360,7 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
23341
23360
|
}
|
|
23342
23361
|
if (this.state.internal)
|
|
23343
23362
|
return;
|
|
23344
|
-
if (this.
|
|
23363
|
+
if (this.explanationAbort)
|
|
23345
23364
|
return;
|
|
23346
23365
|
if (this.explainedSeq === this.state.latestSeq)
|
|
23347
23366
|
return;
|
|
@@ -23352,14 +23371,22 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
23352
23371
|
this.explanationStatus = "generating";
|
|
23353
23372
|
this.scheduleSummary();
|
|
23354
23373
|
const seq = this.state.latestSeq;
|
|
23355
|
-
|
|
23374
|
+
const controller = new AbortController();
|
|
23375
|
+
this.explanationAbort = controller;
|
|
23376
|
+
void this.opts.explainSession(this.state, controller.signal).then((result) => {
|
|
23377
|
+
if (controller.signal.aborted)
|
|
23378
|
+
return;
|
|
23356
23379
|
if (result.status === "generated")
|
|
23357
23380
|
this.ingest([result.event]);
|
|
23358
23381
|
this.explanationStatus = result.status;
|
|
23359
23382
|
this.explainedSeq = result.status === "generated" ? this.state.latestSeq : seq;
|
|
23360
23383
|
}).catch(() => {
|
|
23384
|
+
if (controller.signal.aborted)
|
|
23385
|
+
return;
|
|
23361
23386
|
this.explanationStatus = "failed";
|
|
23362
23387
|
}).finally(() => {
|
|
23388
|
+
if (this.explanationAbort === controller)
|
|
23389
|
+
this.explanationAbort = void 0;
|
|
23363
23390
|
this.scheduleSummary();
|
|
23364
23391
|
});
|
|
23365
23392
|
}
|
|
@@ -23434,6 +23461,7 @@ var SessionCoordinator = class _SessionCoordinator {
|
|
|
23434
23461
|
close() {
|
|
23435
23462
|
this.closed = true;
|
|
23436
23463
|
this.clearIdleEnd();
|
|
23464
|
+
this.explanationAbort?.abort();
|
|
23437
23465
|
if (this.summaryTimer)
|
|
23438
23466
|
clearTimeout(this.summaryTimer);
|
|
23439
23467
|
this.checkpoint();
|
|
@@ -23465,7 +23493,7 @@ var SessionRegistry = class {
|
|
|
23465
23493
|
* The stop every coordinator is loaded with. Held here rather than read from the store on each
|
|
23466
23494
|
* load: it is one value for the whole daemon, and a coordinator is created on the ingest path.
|
|
23467
23495
|
*/
|
|
23468
|
-
explainerCadence = "
|
|
23496
|
+
explainerCadence = "off";
|
|
23469
23497
|
/** Handed to every coordinator this registry loads; see `CoordinatorOptions.now`. */
|
|
23470
23498
|
now;
|
|
23471
23499
|
/** Reads the daemon's current helper routing at call time, so settings apply without a restart. */
|
|
@@ -25450,7 +25478,7 @@ var VERSION = (() => {
|
|
|
25450
25478
|
}
|
|
25451
25479
|
})();
|
|
25452
25480
|
var DEFAULT_SETTINGS = {
|
|
25453
|
-
explainerCadence: "
|
|
25481
|
+
explainerCadence: "off",
|
|
25454
25482
|
explainerBackend: "auto",
|
|
25455
25483
|
explainerModel: null
|
|
25456
25484
|
};
|
|
@@ -25548,9 +25576,9 @@ async function startDaemon(overrides = {}) {
|
|
|
25548
25576
|
const activeExplainer = () => explainedConfiguration(stored.explainerBackend, stored.explainerModel, process.env);
|
|
25549
25577
|
const registry2 = new SessionRegistry(store, {
|
|
25550
25578
|
explainerCadence: effectiveCadence(stored.explainerCadence),
|
|
25551
|
-
explainSession: (state) => {
|
|
25579
|
+
explainSession: (state, signal) => {
|
|
25552
25580
|
const active = activeExplainer();
|
|
25553
|
-
return explainWithStatus(state, { mode: active.mode, model: active.model });
|
|
25581
|
+
return explainWithStatus(state, { mode: active.mode, model: active.model, signal });
|
|
25554
25582
|
},
|
|
25555
25583
|
...overrides.now ? { now: overrides.now } : {}
|
|
25556
25584
|
});
|
|
@@ -25915,6 +25943,36 @@ function renderAudit(r, opts) {
|
|
|
25915
25943
|
`;
|
|
25916
25944
|
}
|
|
25917
25945
|
|
|
25946
|
+
// src/explanationMode.ts
|
|
25947
|
+
var LOCAL_ONLY = {
|
|
25948
|
+
value: "off",
|
|
25949
|
+
label: "Local only",
|
|
25950
|
+
detail: "No model calls"
|
|
25951
|
+
};
|
|
25952
|
+
var WHEN_DONE = {
|
|
25953
|
+
value: "session",
|
|
25954
|
+
label: "When done",
|
|
25955
|
+
detail: "One model call after a session ends"
|
|
25956
|
+
};
|
|
25957
|
+
var EACH_REPLY = {
|
|
25958
|
+
value: "turn",
|
|
25959
|
+
label: "Each reply",
|
|
25960
|
+
detail: "One model call after each agent reply"
|
|
25961
|
+
};
|
|
25962
|
+
var EXPLANATION_MODES = [LOCAL_ONLY, WHEN_DONE, EACH_REPLY];
|
|
25963
|
+
function explanationMode(cadence) {
|
|
25964
|
+
if (cadence === "session") return WHEN_DONE;
|
|
25965
|
+
if (cadence === "turn") return EACH_REPLY;
|
|
25966
|
+
return LOCAL_ONLY;
|
|
25967
|
+
}
|
|
25968
|
+
function parseExplanationMode(value) {
|
|
25969
|
+
const normalized2 = value?.trim().toLowerCase();
|
|
25970
|
+
if (normalized2 === "off" || normalized2 === "local" || normalized2 === "local-only") return "off";
|
|
25971
|
+
if (normalized2 === "session" || normalized2 === "when-done") return "session";
|
|
25972
|
+
if (normalized2 === "turn" || normalized2 === "each-reply") return "turn";
|
|
25973
|
+
return void 0;
|
|
25974
|
+
}
|
|
25975
|
+
|
|
25918
25976
|
// src/integrations.ts
|
|
25919
25977
|
import { existsSync as existsSync8 } from "node:fs";
|
|
25920
25978
|
import { join as join11 } from "node:path";
|
|
@@ -26265,11 +26323,164 @@ function integrationById(id) {
|
|
|
26265
26323
|
return providerIntegrationRegistry.get(id);
|
|
26266
26324
|
}
|
|
26267
26325
|
|
|
26326
|
+
// src/terminalUi.ts
|
|
26327
|
+
var RESET = "\x1B[0m";
|
|
26328
|
+
var ANSI2 = {
|
|
26329
|
+
bold: "\x1B[1m",
|
|
26330
|
+
accent: "\x1B[38;2;143;166;255m",
|
|
26331
|
+
accentBackground: "\x1B[48;2;59;91;219m\x1B[38;2;255;255;255m",
|
|
26332
|
+
textMuted: "\x1B[38;2;142;142;139m",
|
|
26333
|
+
rail: "\x1B[38;2;91;91;88m",
|
|
26334
|
+
ok: "\x1B[38;2;95;207;136m",
|
|
26335
|
+
warn: "\x1B[38;2;224;168;60m",
|
|
26336
|
+
danger: "\x1B[38;2;240;115;106m",
|
|
26337
|
+
claude: "\x1B[38;2;231;155;125m",
|
|
26338
|
+
codex: "\x1B[38;2;79;196;163m"
|
|
26339
|
+
};
|
|
26340
|
+
function paint(enabled, code, value) {
|
|
26341
|
+
return enabled ? `${code}${value}${RESET}` : value;
|
|
26342
|
+
}
|
|
26343
|
+
var TerminalUi = class {
|
|
26344
|
+
color;
|
|
26345
|
+
constructor(color = false) {
|
|
26346
|
+
this.color = color;
|
|
26347
|
+
}
|
|
26348
|
+
tone(value, tone) {
|
|
26349
|
+
const code = tone === "muted" ? ANSI2.textMuted : tone === "claude" ? ANSI2.claude : tone === "codex" ? ANSI2.codex : ANSI2[tone];
|
|
26350
|
+
return paint(this.color, code, value);
|
|
26351
|
+
}
|
|
26352
|
+
bold(value) {
|
|
26353
|
+
return paint(this.color, ANSI2.bold, value);
|
|
26354
|
+
}
|
|
26355
|
+
rail(glyph) {
|
|
26356
|
+
return paint(this.color, ANSI2.rail, glyph);
|
|
26357
|
+
}
|
|
26358
|
+
header(firstRun) {
|
|
26359
|
+
const brand = paint(this.color, ANSI2.accentBackground, " SALIDIUM ");
|
|
26360
|
+
const mode = this.tone(firstRun ? "FIRST-RUN SETUP" : "AGENT SETUP", "muted");
|
|
26361
|
+
return `
|
|
26362
|
+
${brand} ${mode}
|
|
26363
|
+
${this.tone("Connect your coding agents", "muted")}
|
|
26364
|
+
`;
|
|
26365
|
+
}
|
|
26366
|
+
section(label) {
|
|
26367
|
+
return `
|
|
26368
|
+
${this.tone("\u25C6", "accent")} ${this.bold(label.toUpperCase())}
|
|
26369
|
+
`;
|
|
26370
|
+
}
|
|
26371
|
+
copy(text) {
|
|
26372
|
+
return ` ${this.rail("\u2502")} ${text}
|
|
26373
|
+
`;
|
|
26374
|
+
}
|
|
26375
|
+
spacer() {
|
|
26376
|
+
return ` ${this.rail("\u2502")}
|
|
26377
|
+
`;
|
|
26378
|
+
}
|
|
26379
|
+
status(mark2, label, detail, tone) {
|
|
26380
|
+
return ` ${this.rail("\u2502")} ${this.tone(mark2, tone)} ${this.bold(label.padEnd(14))} ${this.tone(detail, "muted")}
|
|
26381
|
+
`;
|
|
26382
|
+
}
|
|
26383
|
+
path(provider, providerId, value) {
|
|
26384
|
+
const tone = providerId === "claude-code" ? "claude" : "codex";
|
|
26385
|
+
return [
|
|
26386
|
+
` ${this.rail("\u2502")} ${this.tone(provider, tone)}
|
|
26387
|
+
`,
|
|
26388
|
+
` ${this.rail("\u2502")} ${this.rail("\u2514\u2500")} ${this.tone(value, "muted")}
|
|
26389
|
+
`
|
|
26390
|
+
].join("");
|
|
26391
|
+
}
|
|
26392
|
+
attention(text) {
|
|
26393
|
+
return ` ${this.rail("\u2502")} ${this.tone("!", "warn")} ${text}
|
|
26394
|
+
`;
|
|
26395
|
+
}
|
|
26396
|
+
item(mark2, text, tone) {
|
|
26397
|
+
return ` ${this.rail("\u2502")} ${this.tone(mark2, tone)} ${text}
|
|
26398
|
+
`;
|
|
26399
|
+
}
|
|
26400
|
+
failure(text) {
|
|
26401
|
+
return ` ${this.rail("\u2502")} ${this.tone("\xD7", "danger")} ${text}
|
|
26402
|
+
`;
|
|
26403
|
+
}
|
|
26404
|
+
choices(label, options, selectedIndex) {
|
|
26405
|
+
const selected = (value) => this.color ? paint(true, ANSI2.accentBackground, ` ${value} `) : `[ ${value} ]`;
|
|
26406
|
+
const idle = (value) => this.tone(` ${value} `, "muted");
|
|
26407
|
+
const rendered = options.map((option, index) => index === selectedIndex ? selected(option) : idle(option)).join(" ");
|
|
26408
|
+
return ` ${this.rail("\u251C\u2500")} ${this.bold(label)} ${rendered}`;
|
|
26409
|
+
}
|
|
26410
|
+
choice(label, yes) {
|
|
26411
|
+
return this.choices(label, ["No", "Yes"], yes ? 1 : 0);
|
|
26412
|
+
}
|
|
26413
|
+
close(mark2, text, tone = "muted") {
|
|
26414
|
+
return ` ${this.rail("\u2514\u2500")} ${this.tone(mark2, tone)} ${text}
|
|
26415
|
+
`;
|
|
26416
|
+
}
|
|
26417
|
+
open(url2, opened, firstRun = false) {
|
|
26418
|
+
const label = opened ? "OPENED" : "LOCAL URL";
|
|
26419
|
+
const mark2 = opened ? "\u2197" : "\u2192";
|
|
26420
|
+
const urlRail = firstRun ? "\u251C\u2500" : "\u2514\u2500";
|
|
26421
|
+
const next = firstRun ? ` ${this.rail("\u2514\u2500")} ${this.tone("NEXT", "muted")} ${this.bold("npx salidium")}
|
|
26422
|
+
` : "";
|
|
26423
|
+
return `
|
|
26424
|
+
${this.tone("\u25C6", "accent")} ${this.bold(label)}
|
|
26425
|
+
${this.rail(urlRail)} ${this.tone(mark2, "accent")} ${this.tone(url2, "accent")}
|
|
26426
|
+
${next}
|
|
26427
|
+
`;
|
|
26428
|
+
}
|
|
26429
|
+
running(explanations, detail) {
|
|
26430
|
+
return [
|
|
26431
|
+
this.section("Running"),
|
|
26432
|
+
this.status("\u2713", "Salidium", "Background service is active", "ok"),
|
|
26433
|
+
this.status(
|
|
26434
|
+
"\u25CF",
|
|
26435
|
+
"Explanations",
|
|
26436
|
+
`${explanations} \xB7 ${detail}`,
|
|
26437
|
+
explanations === "Local only" ? "ok" : "accent"
|
|
26438
|
+
),
|
|
26439
|
+
this.item("\u2192", "Stop service: salidium stop", "muted"),
|
|
26440
|
+
this.close("\u2192", "Stop model calls: salidium explanations off")
|
|
26441
|
+
].join("");
|
|
26442
|
+
}
|
|
26443
|
+
};
|
|
26444
|
+
function supportsTerminalColor(isTty, environment2 = process.env) {
|
|
26445
|
+
return isTty && !("NO_COLOR" in environment2) && environment2.TERM !== "dumb";
|
|
26446
|
+
}
|
|
26447
|
+
function homeRelative(path, userHome2) {
|
|
26448
|
+
if (path === userHome2) return "~";
|
|
26449
|
+
return path.startsWith(`${userHome2}/`) ? `~${path.slice(userHome2.length)}` : path;
|
|
26450
|
+
}
|
|
26451
|
+
function selectionKeyResult(key, selectedIndex, optionCount) {
|
|
26452
|
+
const last = Math.max(0, optionCount - 1);
|
|
26453
|
+
const selected = Math.max(0, Math.min(last, selectedIndex));
|
|
26454
|
+
if (key === "") return { selectedIndex: selected, aborted: true };
|
|
26455
|
+
if (key === "\r" || key === "\n") return { selectedIndex: selected, decision: selected };
|
|
26456
|
+
if (key === "\x1B") return { selectedIndex: 0, decision: 0 };
|
|
26457
|
+
if (key === "\x1B[D") return { selectedIndex: Math.max(0, selected - 1) };
|
|
26458
|
+
if (key === "\x1B[C" || key === " " || key === " ")
|
|
26459
|
+
return { selectedIndex: Math.min(last, selected + 1) };
|
|
26460
|
+
if (/^[1-9]$/.test(key)) {
|
|
26461
|
+
const direct = Number(key) - 1;
|
|
26462
|
+
if (direct <= last) return { selectedIndex: direct, decision: direct };
|
|
26463
|
+
}
|
|
26464
|
+
return { selectedIndex: selected };
|
|
26465
|
+
}
|
|
26466
|
+
function consentKeyResult(key, selected) {
|
|
26467
|
+
if (key === "y" || key === "Y") return { selected: true, decision: true };
|
|
26468
|
+
if (key === "n" || key === "N") return { selected: false, decision: false };
|
|
26469
|
+
if (key === " " || key === " ") return { selected: !selected };
|
|
26470
|
+
const result = selectionKeyResult(key, selected ? 1 : 0, 2);
|
|
26471
|
+
return {
|
|
26472
|
+
selected: result.selectedIndex === 1,
|
|
26473
|
+
...result.decision === void 0 ? {} : { decision: result.decision === 1 },
|
|
26474
|
+
...result.aborted ? { aborted: true } : {}
|
|
26475
|
+
};
|
|
26476
|
+
}
|
|
26477
|
+
|
|
26268
26478
|
// src/onboarding.ts
|
|
26269
26479
|
function names(providers) {
|
|
26270
26480
|
return providers.map((provider) => provider.name).join(", ");
|
|
26271
26481
|
}
|
|
26272
26482
|
async function runFirstRunOnboarding(context, io, options = {}) {
|
|
26483
|
+
const ui = new TerminalUi(io.color);
|
|
26273
26484
|
const integrations = options.integrations ?? providerIntegrations;
|
|
26274
26485
|
const detected = integrations.filter((provider) => provider.detect(context).detected);
|
|
26275
26486
|
const hookCapable = detected.filter((provider) => provider.liveHooksSupported(context));
|
|
@@ -26284,45 +26495,66 @@ async function runFirstRunOnboarding(context, io, options = {}) {
|
|
|
26284
26495
|
const status = inspections.get(provider.id)?.status;
|
|
26285
26496
|
return status === "not-configured" || status === "partial";
|
|
26286
26497
|
});
|
|
26287
|
-
const configured = hookCapable.filter(
|
|
26288
|
-
(provider) => inspections.get(provider.id)?.status === "configured"
|
|
26289
|
-
);
|
|
26290
26498
|
const shouldDescribe = Boolean(options.firstRun || pending.length || invalid.length);
|
|
26291
26499
|
if (shouldDescribe) {
|
|
26292
|
-
io.write(
|
|
26293
|
-
|
|
26294
|
-
|
|
26295
|
-
|
|
26500
|
+
io.write(ui.header(Boolean(options.firstRun)));
|
|
26501
|
+
io.write(ui.section("Agents"));
|
|
26502
|
+
if (detected.length === 0) {
|
|
26503
|
+
io.write(ui.item("\u25CB", "No supported coding agents detected", "muted"));
|
|
26504
|
+
io.write(ui.close("\u2192", "Start Claude Code or Codex, then run Salidium again", "accent"));
|
|
26505
|
+
} else {
|
|
26506
|
+
for (const provider of detected) {
|
|
26507
|
+
const inspection = inspections.get(provider.id);
|
|
26508
|
+
const detail = inspection?.status === "configured" ? "Connected" : provider.liveHooksSupported(context) ? "Detected" : "History only";
|
|
26509
|
+
io.write(
|
|
26510
|
+
ui.status(
|
|
26511
|
+
inspection?.status === "configured" ? "\u2713" : "\u25CF",
|
|
26512
|
+
provider.name,
|
|
26513
|
+
detail,
|
|
26514
|
+
provider.id === "claude-code" ? "claude" : "codex"
|
|
26515
|
+
)
|
|
26516
|
+
);
|
|
26517
|
+
}
|
|
26518
|
+
}
|
|
26296
26519
|
if (historyOnly.length > 0) {
|
|
26520
|
+
io.write(ui.spacer());
|
|
26297
26521
|
io.write(
|
|
26298
|
-
|
|
26299
|
-
`
|
|
26522
|
+
ui.attention(
|
|
26523
|
+
`Native Windows imports ${names(historyOnly)} history; live POSIX hooks are unavailable`
|
|
26524
|
+
)
|
|
26300
26525
|
);
|
|
26301
26526
|
}
|
|
26302
|
-
if (configured.length > 0) io.write(`Already connected: ${names(configured)}.
|
|
26303
|
-
`);
|
|
26304
26527
|
for (const provider of invalid) {
|
|
26305
26528
|
const inspection = inspections.get(provider.id);
|
|
26529
|
+
io.write(ui.spacer());
|
|
26306
26530
|
io.write(
|
|
26307
|
-
|
|
26308
|
-
`
|
|
26531
|
+
ui.attention(
|
|
26532
|
+
`${provider.name} was not changed: ${inspection?.issue ?? "its settings could not be read safely"}`
|
|
26533
|
+
)
|
|
26309
26534
|
);
|
|
26310
26535
|
}
|
|
26311
26536
|
}
|
|
26312
26537
|
let consent = "not-needed";
|
|
26538
|
+
let explainerCadence;
|
|
26313
26539
|
const changed = [];
|
|
26314
26540
|
const guidance = [];
|
|
26315
26541
|
if (pending.length > 0) {
|
|
26316
|
-
io.write("Permission
|
|
26317
|
-
|
|
26318
|
-
|
|
26319
|
-
|
|
26320
|
-
|
|
26542
|
+
io.write(ui.section("Permission"));
|
|
26543
|
+
io.write(ui.copy("Salidium will add its hooks. Existing settings stay intact."));
|
|
26544
|
+
io.write(ui.spacer());
|
|
26545
|
+
for (const [index, provider] of pending.entries()) {
|
|
26546
|
+
const settingsPath3 = inspections.get(provider.id)?.settingsPath;
|
|
26547
|
+
if (settingsPath3)
|
|
26548
|
+
io.write(ui.path(provider.name, provider.id, homeRelative(settingsPath3, context.userHome)));
|
|
26549
|
+
if (index < pending.length - 1) io.write(ui.spacer());
|
|
26550
|
+
}
|
|
26551
|
+
io.write(ui.spacer());
|
|
26321
26552
|
let approved = Boolean(options.assumeYes);
|
|
26322
26553
|
if (options.assumeYes) {
|
|
26323
26554
|
consent = "approved";
|
|
26324
26555
|
} else if (io.interactive) {
|
|
26325
|
-
|
|
26556
|
+
const question = pending.length === 1 ? `Connect ${pending[0]?.name}?` : "Connect both agents?";
|
|
26557
|
+
approved = await io.confirm(question);
|
|
26326
26558
|
consent = approved ? "approved" : "declined";
|
|
26327
26559
|
} else {
|
|
26328
26560
|
consent = "non-interactive";
|
|
@@ -26333,36 +26565,80 @@ async function runFirstRunOnboarding(context, io, options = {}) {
|
|
|
26333
26565
|
const result = provider.install(context);
|
|
26334
26566
|
changed.push(result);
|
|
26335
26567
|
io.write(
|
|
26336
|
-
result.changed ?
|
|
26337
|
-
` : `Connected: ${provider.name} (no changes needed).
|
|
26338
|
-
`
|
|
26568
|
+
ui.status("\u2713", provider.name, result.changed ? "Connected" : "Already connected", "ok")
|
|
26339
26569
|
);
|
|
26340
26570
|
guidance.push(...provider.guidance(result));
|
|
26341
26571
|
} catch (error51) {
|
|
26342
26572
|
io.write(
|
|
26343
|
-
|
|
26344
|
-
`
|
|
26573
|
+
ui.failure(
|
|
26574
|
+
`${provider.name} could not be connected: ${error51 instanceof Error ? error51.message : String(error51)}`
|
|
26575
|
+
)
|
|
26345
26576
|
);
|
|
26346
26577
|
}
|
|
26347
26578
|
}
|
|
26348
26579
|
} else if (consent === "non-interactive") {
|
|
26349
26580
|
io.write(
|
|
26350
|
-
|
|
26581
|
+
ui.close(
|
|
26582
|
+
"\u25CB",
|
|
26583
|
+
"No changes made. Re-run with --yes, or use salidium install-hooks later.",
|
|
26584
|
+
"muted"
|
|
26585
|
+
)
|
|
26351
26586
|
);
|
|
26352
26587
|
} else {
|
|
26353
|
-
io.write("No
|
|
26588
|
+
io.write(ui.close("\u25CB", "No changes made. Use salidium install-hooks when ready.", "muted"));
|
|
26589
|
+
}
|
|
26590
|
+
}
|
|
26591
|
+
if (options.firstRun) {
|
|
26592
|
+
io.write(ui.section("Explanations"));
|
|
26593
|
+
io.write(ui.copy("Reports, evidence, and quantities stay local."));
|
|
26594
|
+
io.write(ui.copy("Only the written Why and How can call a model."));
|
|
26595
|
+
io.write(ui.spacer());
|
|
26596
|
+
let selectedIndex = 0;
|
|
26597
|
+
if (io.interactive && !options.assumeYes) {
|
|
26598
|
+
selectedIndex = await io.select(
|
|
26599
|
+
"Written Why + How",
|
|
26600
|
+
EXPLANATION_MODES.map((mode2) => mode2.label),
|
|
26601
|
+
0
|
|
26602
|
+
);
|
|
26354
26603
|
}
|
|
26604
|
+
explainerCadence = EXPLANATION_MODES[selectedIndex]?.value ?? "off";
|
|
26605
|
+
const mode = explanationMode(explainerCadence);
|
|
26606
|
+
io.write(
|
|
26607
|
+
ui.close(
|
|
26608
|
+
explainerCadence === "off" ? "\u2713" : "\u25CF",
|
|
26609
|
+
`${mode.label} \xB7 ${mode.detail}`,
|
|
26610
|
+
explainerCadence === "off" ? "ok" : "accent"
|
|
26611
|
+
)
|
|
26612
|
+
);
|
|
26355
26613
|
}
|
|
26356
26614
|
const validations = detected.flatMap((provider) => provider.validate(context));
|
|
26357
26615
|
const attention = validations.filter((validation) => validation.level === "attention");
|
|
26358
26616
|
if (shouldDescribe || changed.length > 0) {
|
|
26359
|
-
|
|
26360
|
-
|
|
26361
|
-
|
|
26617
|
+
const ready = [];
|
|
26618
|
+
if (detected.length === 0) {
|
|
26619
|
+
ready.push({ mark: "\u25CB", text: "Waiting for Claude Code or Codex", tone: "muted" });
|
|
26620
|
+
} else if (attention.length === 0) {
|
|
26621
|
+
ready.push({ mark: "\u2713", text: "Setup checks passed", tone: "ok" });
|
|
26622
|
+
} else {
|
|
26623
|
+
for (const validation of attention)
|
|
26624
|
+
ready.push({ mark: "!", text: validation.message, tone: "warn" });
|
|
26625
|
+
}
|
|
26626
|
+
for (const instruction of guidance)
|
|
26627
|
+
ready.push({ mark: "!", text: `Codex: ${instruction}`, tone: "warn" });
|
|
26628
|
+
io.write(ui.section("Ready"));
|
|
26629
|
+
for (const row of ready.slice(0, -1)) io.write(ui.item(row.mark, row.text, row.tone));
|
|
26630
|
+
const last = ready.at(-1);
|
|
26631
|
+
if (last) io.write(ui.close(last.mark, last.text, last.tone));
|
|
26362
26632
|
}
|
|
26363
|
-
|
|
26364
|
-
|
|
26365
|
-
|
|
26633
|
+
return {
|
|
26634
|
+
detected,
|
|
26635
|
+
changed,
|
|
26636
|
+
guidance,
|
|
26637
|
+
validations,
|
|
26638
|
+
consent,
|
|
26639
|
+
...explainerCadence ? { explainerCadence } : {},
|
|
26640
|
+
presented: shouldDescribe || changed.length > 0
|
|
26641
|
+
};
|
|
26366
26642
|
}
|
|
26367
26643
|
|
|
26368
26644
|
// src/render.ts
|
|
@@ -26555,6 +26831,9 @@ Usage:
|
|
|
26555
26831
|
salidium stop Stop the background daemon
|
|
26556
26832
|
salidium restart Stop it, start it again, and open the UI (--no-open to skip)
|
|
26557
26833
|
salidium status Show daemon status
|
|
26834
|
+
salidium explanations Show whether written explanations can call a model
|
|
26835
|
+
salidium explanations off|when-done|each-reply
|
|
26836
|
+
Change model-call frequency without stopping local reports
|
|
26558
26837
|
salidium open Open the UI in your browser
|
|
26559
26838
|
salidium show [session] Print the report for a session as text (default: most recent)
|
|
26560
26839
|
--detail=summary|detail|source, --width=N
|
|
@@ -26631,35 +26910,70 @@ async function main(argv) {
|
|
|
26631
26910
|
}
|
|
26632
26911
|
case "start": {
|
|
26633
26912
|
const running = await ensureDaemon();
|
|
26913
|
+
const explanations = await currentExplanationState(running, "reachable");
|
|
26634
26914
|
process.stdout.write(
|
|
26635
26915
|
`daemon running on http://127.0.0.1:${running.port} (pid ${running.pid})
|
|
26916
|
+
Explanations: ${explanationStateLabel(explanations)}
|
|
26636
26917
|
`
|
|
26637
26918
|
);
|
|
26638
26919
|
return 0;
|
|
26639
26920
|
}
|
|
26640
26921
|
case "up": {
|
|
26641
26922
|
const context = { userHome, salidiumHome };
|
|
26642
|
-
|
|
26923
|
+
const color = supportsTerminalColor(Boolean(process.stdout.isTTY));
|
|
26924
|
+
const firstRun = !existsSync9(daemonPaths(salidiumHome).db);
|
|
26925
|
+
const onboarding = await runFirstRunOnboarding(
|
|
26643
26926
|
context,
|
|
26644
26927
|
{
|
|
26645
26928
|
interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
26646
|
-
|
|
26929
|
+
color,
|
|
26930
|
+
confirm: (question) => confirmSetup(question, color),
|
|
26931
|
+
select: (question, options, selectedIndex) => selectTerminalOption(question, options, selectedIndex, color),
|
|
26647
26932
|
write: (text) => process.stdout.write(text)
|
|
26648
26933
|
},
|
|
26649
26934
|
{
|
|
26650
26935
|
assumeYes,
|
|
26651
|
-
firstRun
|
|
26936
|
+
firstRun
|
|
26652
26937
|
}
|
|
26653
26938
|
);
|
|
26654
|
-
|
|
26655
|
-
|
|
26939
|
+
if (onboarding.explainerCadence) {
|
|
26940
|
+
writeSettings(salidiumHome, {
|
|
26941
|
+
...readSettings(salidiumHome),
|
|
26942
|
+
explainerCadence: onboarding.explainerCadence
|
|
26943
|
+
});
|
|
26944
|
+
}
|
|
26945
|
+
const systemAttention = essentialValidations().filter(
|
|
26946
|
+
(validation) => validation.level === "attention"
|
|
26947
|
+
);
|
|
26948
|
+
if (systemAttention.length > 0 && onboarding.presented) {
|
|
26949
|
+
const ui2 = new TerminalUi(color);
|
|
26950
|
+
process.stdout.write(ui2.section("System"));
|
|
26951
|
+
for (const validation of systemAttention.slice(0, -1))
|
|
26952
|
+
process.stdout.write(ui2.item("!", validation.message, "warn"));
|
|
26953
|
+
const last = systemAttention.at(-1);
|
|
26954
|
+
if (last) process.stdout.write(ui2.close("!", last.message, "warn"));
|
|
26955
|
+
} else {
|
|
26956
|
+
for (const validation of systemAttention)
|
|
26656
26957
|
process.stdout.write(`Needs attention: ${validation.message}.
|
|
26657
26958
|
`);
|
|
26658
26959
|
}
|
|
26659
26960
|
const running = await ensureDaemon();
|
|
26660
26961
|
if (!noOpen && process.stdout.isTTY) openBrowser(uiUrl(running));
|
|
26661
|
-
|
|
26962
|
+
const url2 = uiUrl(running);
|
|
26963
|
+
const ui = new TerminalUi(color);
|
|
26964
|
+
const state = await currentExplanationState(running, "reachable");
|
|
26965
|
+
if (onboarding.presented) {
|
|
26966
|
+
process.stdout.write(
|
|
26967
|
+
ui.open(url2, !noOpen && Boolean(process.stdout.isTTY), Boolean(firstRun))
|
|
26968
|
+
);
|
|
26969
|
+
} else if (process.stdout.isTTY) {
|
|
26970
|
+
const mode = explanationMode(state.effective);
|
|
26971
|
+
process.stdout.write(ui.running(mode.label, mode.detail));
|
|
26972
|
+
process.stdout.write(ui.open(url2, !noOpen));
|
|
26973
|
+
} else {
|
|
26974
|
+
process.stdout.write(`${url2}
|
|
26662
26975
|
`);
|
|
26976
|
+
}
|
|
26663
26977
|
return 0;
|
|
26664
26978
|
}
|
|
26665
26979
|
case "open": {
|
|
@@ -26729,6 +27043,13 @@ async function main(argv) {
|
|
|
26729
27043
|
` : `daemon (pid ${stopped.pid}) was asked to stop and is still running
|
|
26730
27044
|
`
|
|
26731
27045
|
);
|
|
27046
|
+
const storedMode = readSettings(salidiumHome).explainerCadence;
|
|
27047
|
+
if (storedMode !== "off") {
|
|
27048
|
+
process.stdout.write(
|
|
27049
|
+
`Explanations remain set to ${explanationMode(storedMode).label} for the next start. Disable them with: salidium explanations off
|
|
27050
|
+
`
|
|
27051
|
+
);
|
|
27052
|
+
}
|
|
26732
27053
|
return stopped === void 0 || stopped.signaled && stopped.exited ? 0 : 1;
|
|
26733
27054
|
}
|
|
26734
27055
|
/*
|
|
@@ -26761,9 +27082,13 @@ async function main(argv) {
|
|
|
26761
27082
|
`);
|
|
26762
27083
|
const running = await ensureDaemon();
|
|
26763
27084
|
if (!noOpen) openBrowser(uiUrl(running));
|
|
26764
|
-
|
|
27085
|
+
const explanations = await currentExplanationState(running, "reachable");
|
|
27086
|
+
process.stdout.write(
|
|
27087
|
+
`daemon running (pid ${running.pid})
|
|
27088
|
+
Explanations: ${explanationStateLabel(explanations)}
|
|
26765
27089
|
${uiUrl(running)}
|
|
26766
|
-
`
|
|
27090
|
+
`
|
|
27091
|
+
);
|
|
26767
27092
|
return 0;
|
|
26768
27093
|
}
|
|
26769
27094
|
case "status": {
|
|
@@ -26773,6 +27098,9 @@ ${uiUrl(running)}
|
|
|
26773
27098
|
d && presence !== "absent" ? `running: pid ${d.pid}, port ${d.port}, since ${d.startedAt}${presence === "unresponsive" ? "; not answering" : ""}
|
|
26774
27099
|
` : "not running\n"
|
|
26775
27100
|
);
|
|
27101
|
+
const explanations = await currentExplanationState(d, presence);
|
|
27102
|
+
process.stdout.write(`Explanations: ${explanationStateLabel(explanations)}
|
|
27103
|
+
`);
|
|
26776
27104
|
const context = { userHome, salidiumHome };
|
|
26777
27105
|
for (const provider of providerIntegrations) {
|
|
26778
27106
|
const detection = provider.detect(context);
|
|
@@ -26782,6 +27110,75 @@ ${uiUrl(running)}
|
|
|
26782
27110
|
}
|
|
26783
27111
|
return presence === "reachable" ? 0 : 1;
|
|
26784
27112
|
}
|
|
27113
|
+
case "explanations": {
|
|
27114
|
+
const d = readDaemonJson(salidiumHome);
|
|
27115
|
+
const presence = await presenceOf(d);
|
|
27116
|
+
if (!arg) {
|
|
27117
|
+
const state2 = await currentExplanationState(d, presence);
|
|
27118
|
+
const mode = explanationMode(state2.effective);
|
|
27119
|
+
process.stdout.write(
|
|
27120
|
+
`Explanations: ${explanationStateLabel(state2)}
|
|
27121
|
+
${mode.detail}. Reports, evidence, and quantities stay local.
|
|
27122
|
+
Change with: salidium explanations off|when-done|each-reply
|
|
27123
|
+
`
|
|
27124
|
+
);
|
|
27125
|
+
return 0;
|
|
27126
|
+
}
|
|
27127
|
+
const cadence = parseExplanationMode(arg);
|
|
27128
|
+
if (!cadence) {
|
|
27129
|
+
process.stderr.write(
|
|
27130
|
+
"Choose off, when-done, or each-reply. Example: salidium explanations off\n"
|
|
27131
|
+
);
|
|
27132
|
+
return 2;
|
|
27133
|
+
}
|
|
27134
|
+
if (presence === "unresponsive") {
|
|
27135
|
+
process.stderr.write(
|
|
27136
|
+
`daemon pid ${d?.pid ?? "unknown"} is running but did not answer; the setting was not changed
|
|
27137
|
+
`
|
|
27138
|
+
);
|
|
27139
|
+
return 1;
|
|
27140
|
+
}
|
|
27141
|
+
let state;
|
|
27142
|
+
if (d && presence === "reachable") {
|
|
27143
|
+
try {
|
|
27144
|
+
const response = await fetch(`http://127.0.0.1:${d.port}/api/settings/explainer`, {
|
|
27145
|
+
method: "PUT",
|
|
27146
|
+
headers: {
|
|
27147
|
+
Authorization: `Bearer ${d.token}`,
|
|
27148
|
+
"Content-Type": "application/json"
|
|
27149
|
+
},
|
|
27150
|
+
body: JSON.stringify({ cadence }),
|
|
27151
|
+
signal: AbortSignal.timeout(2e3)
|
|
27152
|
+
});
|
|
27153
|
+
if (!response.ok) {
|
|
27154
|
+
process.stderr.write(`daemon refused the explanation setting (${response.status})
|
|
27155
|
+
`);
|
|
27156
|
+
return 1;
|
|
27157
|
+
}
|
|
27158
|
+
const settings = ExplainerSettingsSchema.parse(await response.json());
|
|
27159
|
+
state = explanationStateFromApi(settings);
|
|
27160
|
+
} catch {
|
|
27161
|
+
process.stderr.write(
|
|
27162
|
+
"daemon stopped answering; the explanation setting was not changed\n"
|
|
27163
|
+
);
|
|
27164
|
+
return 1;
|
|
27165
|
+
}
|
|
27166
|
+
} else {
|
|
27167
|
+
writeSettings(salidiumHome, {
|
|
27168
|
+
...readSettings(salidiumHome),
|
|
27169
|
+
explainerCadence: cadence
|
|
27170
|
+
});
|
|
27171
|
+
state = localExplanationState();
|
|
27172
|
+
}
|
|
27173
|
+
const chosen = explanationMode(cadence);
|
|
27174
|
+
process.stdout.write(`Saved: ${chosen.label} \xB7 ${chosen.detail}
|
|
27175
|
+
`);
|
|
27176
|
+
if (state.effective !== cadence)
|
|
27177
|
+
process.stdout.write(
|
|
27178
|
+
"Local only is active because the daemon environment prevents model calls.\n"
|
|
27179
|
+
);
|
|
27180
|
+
return 0;
|
|
27181
|
+
}
|
|
26785
27182
|
case "install-hooks":
|
|
26786
27183
|
case "uninstall-hooks": {
|
|
26787
27184
|
const remove = cmd === "uninstall-hooks";
|
|
@@ -27096,6 +27493,36 @@ function formatBytes(bytes) {
|
|
|
27096
27493
|
function uiUrl(d) {
|
|
27097
27494
|
return `http://127.0.0.1:${d.port}/#token=${d.token}`;
|
|
27098
27495
|
}
|
|
27496
|
+
function explanationStateFromApi(settings) {
|
|
27497
|
+
return {
|
|
27498
|
+
stored: settings.cadence,
|
|
27499
|
+
effective: settings.envOff ? "off" : settings.cadence,
|
|
27500
|
+
envOff: settings.envOff
|
|
27501
|
+
};
|
|
27502
|
+
}
|
|
27503
|
+
function localExplanationState() {
|
|
27504
|
+
const stored = readSettings(salidiumHome).explainerCadence;
|
|
27505
|
+
const effective = effectiveCadence(stored);
|
|
27506
|
+
return { stored, effective, envOff: effective !== stored };
|
|
27507
|
+
}
|
|
27508
|
+
async function currentExplanationState(daemon, presence) {
|
|
27509
|
+
if (daemon && presence === "reachable") {
|
|
27510
|
+
try {
|
|
27511
|
+
const response = await fetch(`http://127.0.0.1:${daemon.port}/api/settings/explainer`, {
|
|
27512
|
+
headers: { Authorization: `Bearer ${daemon.token}` },
|
|
27513
|
+
signal: AbortSignal.timeout(2e3)
|
|
27514
|
+
});
|
|
27515
|
+
if (response.ok)
|
|
27516
|
+
return explanationStateFromApi(ExplainerSettingsSchema.parse(await response.json()));
|
|
27517
|
+
} catch {
|
|
27518
|
+
}
|
|
27519
|
+
}
|
|
27520
|
+
return localExplanationState();
|
|
27521
|
+
}
|
|
27522
|
+
function explanationStateLabel(state) {
|
|
27523
|
+
const mode = explanationMode(state.effective);
|
|
27524
|
+
return `${mode.label}${state.envOff && state.stored !== "off" ? " (forced by environment)" : ""} \xB7 ${mode.detail}`;
|
|
27525
|
+
}
|
|
27099
27526
|
async function alive(d) {
|
|
27100
27527
|
try {
|
|
27101
27528
|
const res = await fetch(`http://127.0.0.1:${d.port}/api/info`, {
|
|
@@ -27263,14 +27690,94 @@ function openBrowser(url2) {
|
|
|
27263
27690
|
} catch {
|
|
27264
27691
|
}
|
|
27265
27692
|
}
|
|
27266
|
-
async function confirmSetup(question) {
|
|
27267
|
-
|
|
27268
|
-
|
|
27269
|
-
|
|
27270
|
-
|
|
27271
|
-
|
|
27272
|
-
|
|
27693
|
+
async function confirmSetup(question, color) {
|
|
27694
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
27695
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
27696
|
+
try {
|
|
27697
|
+
const answer = await prompt.question(`${question} [y/N] `);
|
|
27698
|
+
return /^(y|yes)$/i.test(answer.trim());
|
|
27699
|
+
} finally {
|
|
27700
|
+
prompt.close();
|
|
27701
|
+
}
|
|
27273
27702
|
}
|
|
27703
|
+
const ui = new TerminalUi(color);
|
|
27704
|
+
const input = process.stdin;
|
|
27705
|
+
const output = process.stdout;
|
|
27706
|
+
const wasRaw = Boolean(input.isRaw);
|
|
27707
|
+
let selected = false;
|
|
27708
|
+
const render = () => output.write(`\x1B[?25l\r\x1B[2K${ui.choice(question, selected)}`);
|
|
27709
|
+
return new Promise((resolve2, reject) => {
|
|
27710
|
+
const finish = (decision) => {
|
|
27711
|
+
input.off("data", onData);
|
|
27712
|
+
input.setRawMode(wasRaw);
|
|
27713
|
+
if (!wasRaw) input.pause();
|
|
27714
|
+
render();
|
|
27715
|
+
output.write("\x1B[?25h\n");
|
|
27716
|
+
resolve2(decision);
|
|
27717
|
+
};
|
|
27718
|
+
const onData = (data) => {
|
|
27719
|
+
const result = consentKeyResult(String(data), selected);
|
|
27720
|
+
selected = result.selected;
|
|
27721
|
+
if (result.aborted) {
|
|
27722
|
+
input.off("data", onData);
|
|
27723
|
+
input.setRawMode(wasRaw);
|
|
27724
|
+
if (!wasRaw) input.pause();
|
|
27725
|
+
output.write("\x1B[?25h\n");
|
|
27726
|
+
reject(new Error("Aborted with Ctrl+C"));
|
|
27727
|
+
return;
|
|
27728
|
+
}
|
|
27729
|
+
if (result.decision !== void 0) {
|
|
27730
|
+
finish(result.decision);
|
|
27731
|
+
return;
|
|
27732
|
+
}
|
|
27733
|
+
render();
|
|
27734
|
+
};
|
|
27735
|
+
input.setRawMode(true);
|
|
27736
|
+
input.resume();
|
|
27737
|
+
input.on("data", onData);
|
|
27738
|
+
render();
|
|
27739
|
+
});
|
|
27740
|
+
}
|
|
27741
|
+
async function selectTerminalOption(question, options, initialIndex, color) {
|
|
27742
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return initialIndex;
|
|
27743
|
+
const ui = new TerminalUi(color);
|
|
27744
|
+
const input = process.stdin;
|
|
27745
|
+
const output = process.stdout;
|
|
27746
|
+
const wasRaw = Boolean(input.isRaw);
|
|
27747
|
+
let selectedIndex = initialIndex;
|
|
27748
|
+
const render = () => output.write(`\x1B[?25l\r\x1B[2K${ui.choices(question, options, selectedIndex)}`);
|
|
27749
|
+
return new Promise((resolve2, reject) => {
|
|
27750
|
+
const finish = (decision) => {
|
|
27751
|
+
input.off("data", onData);
|
|
27752
|
+
input.setRawMode(wasRaw);
|
|
27753
|
+
if (!wasRaw) input.pause();
|
|
27754
|
+
selectedIndex = decision;
|
|
27755
|
+
render();
|
|
27756
|
+
output.write("\x1B[?25h\n");
|
|
27757
|
+
resolve2(decision);
|
|
27758
|
+
};
|
|
27759
|
+
const onData = (data) => {
|
|
27760
|
+
const result = selectionKeyResult(String(data), selectedIndex, options.length);
|
|
27761
|
+
selectedIndex = result.selectedIndex;
|
|
27762
|
+
if (result.aborted) {
|
|
27763
|
+
input.off("data", onData);
|
|
27764
|
+
input.setRawMode(wasRaw);
|
|
27765
|
+
if (!wasRaw) input.pause();
|
|
27766
|
+
output.write("\x1B[?25h\n");
|
|
27767
|
+
reject(new Error("Aborted with Ctrl+C"));
|
|
27768
|
+
return;
|
|
27769
|
+
}
|
|
27770
|
+
if (result.decision !== void 0) {
|
|
27771
|
+
finish(result.decision);
|
|
27772
|
+
return;
|
|
27773
|
+
}
|
|
27774
|
+
render();
|
|
27775
|
+
};
|
|
27776
|
+
input.setRawMode(true);
|
|
27777
|
+
input.resume();
|
|
27778
|
+
input.on("data", onData);
|
|
27779
|
+
render();
|
|
27780
|
+
});
|
|
27274
27781
|
}
|
|
27275
27782
|
function essentialValidations() {
|
|
27276
27783
|
const validations = [];
|
|
@@ -27333,6 +27840,7 @@ async function doctor() {
|
|
|
27333
27840
|
lines.push("settings file is invalid; optional explanations are safely off until it is fixed");
|
|
27334
27841
|
problems++;
|
|
27335
27842
|
}
|
|
27843
|
+
lines.push(`explanations ${explanationStateLabel(localExplanationState())}`);
|
|
27336
27844
|
const context = { userHome, salidiumHome };
|
|
27337
27845
|
for (const provider of providerIntegrations) {
|
|
27338
27846
|
const detection = provider.detect(context);
|