@noir-ai/cli 1.10.1-beta.1 → 1.11.0-beta.1
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/bin.d.ts +1 -1
- package/dist/bin.js +290 -19
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-PED37JWV.js → chunk-Y4M2YC6U.js} +29 -1
- package/dist/{chunk-PED37JWV.js.map → chunk-Y4M2YC6U.js.map} +1 -1
- package/dist/tui/{chunk-RCMEIONF.js → chunk-4OBUMHF6.js} +29 -1
- package/dist/tui/{chunk-RCMEIONF.js.map → chunk-4OBUMHF6.js.map} +1 -1
- package/dist/tui/index.js +670 -468
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/{update-VIQUC3DT.js → update-OM2EP2F5.js} +8 -4
- package/dist/{update-6P7SEZ67.js → update-S3IZXXKO.js} +8 -4
- package/package.json +11 -11
- /package/dist/tui/{update-VIQUC3DT.js.map → update-OM2EP2F5.js.map} +0 -0
- /package/dist/{update-6P7SEZ67.js.map → update-S3IZXXKO.js.map} +0 -0
package/dist/tui/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_UPDATE_CONFIG,
|
|
3
|
+
buildUpdateNotice,
|
|
3
4
|
runAsyncUpdateCheck
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-4OBUMHF6.js";
|
|
5
6
|
import "./chunk-R6246ISQ.js";
|
|
6
7
|
import {
|
|
7
8
|
doctor,
|
|
@@ -38,7 +39,7 @@ import { render } from "ink";
|
|
|
38
39
|
// src/bin.ts
|
|
39
40
|
import { realpathSync } from "fs";
|
|
40
41
|
import { pathToFileURL } from "url";
|
|
41
|
-
import { SUPPORTED_HOSTS } from "@noir-ai/adapters";
|
|
42
|
+
import { SUPPORTED_HOSTS as SUPPORTED_HOSTS2 } from "@noir-ai/adapters";
|
|
42
43
|
import { NOIR_VERSION as NOIR_VERSION4 } from "@noir-ai/core";
|
|
43
44
|
import { Command, Option } from "commander";
|
|
44
45
|
|
|
@@ -1467,9 +1468,12 @@ async function handoff(opts) {
|
|
|
1467
1468
|
// src/commands/home.ts
|
|
1468
1469
|
import { hostLaunchDirective as hostLaunchDirective2 } from "@noir-ai/adapters";
|
|
1469
1470
|
import {
|
|
1471
|
+
detectActiveMethod,
|
|
1472
|
+
latestVersionFromCache,
|
|
1470
1473
|
loadProjectInfo as loadProjectInfo6,
|
|
1471
1474
|
NOIR_VERSION as NOIR_VERSION3,
|
|
1472
|
-
readInstallRecord
|
|
1475
|
+
readInstallRecord,
|
|
1476
|
+
readUpdateCache
|
|
1473
1477
|
} from "@noir-ai/core";
|
|
1474
1478
|
|
|
1475
1479
|
// src/banner.ts
|
|
@@ -1507,7 +1511,6 @@ function shouldShowBanner(opts) {
|
|
|
1507
1511
|
// src/tui/commands/registry.ts
|
|
1508
1512
|
var DESTRUCTIVE_PREFIXES = [
|
|
1509
1513
|
["context", "index"],
|
|
1510
|
-
["context", "forget"],
|
|
1511
1514
|
["memory", "save"],
|
|
1512
1515
|
["memory", "forget"],
|
|
1513
1516
|
["memory", "consolidate"],
|
|
@@ -1521,7 +1524,8 @@ var DESTRUCTIVE_PREFIXES = [
|
|
|
1521
1524
|
["create"],
|
|
1522
1525
|
["sync"],
|
|
1523
1526
|
["install"],
|
|
1524
|
-
["update"]
|
|
1527
|
+
["update"],
|
|
1528
|
+
["run"]
|
|
1525
1529
|
];
|
|
1526
1530
|
function isDestructive(argv) {
|
|
1527
1531
|
return DESTRUCTIVE_PREFIXES.some((prefix) => {
|
|
@@ -1570,7 +1574,6 @@ var HOME_SECTIONS = [
|
|
|
1570
1574
|
id: "status",
|
|
1571
1575
|
label: "Status & context",
|
|
1572
1576
|
hint: "snapshot \xB7 index \xB7 search \xB7 doctor",
|
|
1573
|
-
key: "1",
|
|
1574
1577
|
items: [
|
|
1575
1578
|
{
|
|
1576
1579
|
id: "status",
|
|
@@ -1605,7 +1608,6 @@ var HOME_SECTIONS = [
|
|
|
1605
1608
|
id: "memory",
|
|
1606
1609
|
label: "Memory",
|
|
1607
1610
|
hint: "recall \xB7 save \xB7 sessions \xB7 forget \xB7 consolidate",
|
|
1608
|
-
key: "2",
|
|
1609
1611
|
items: [
|
|
1610
1612
|
{
|
|
1611
1613
|
id: "memory recall",
|
|
@@ -1643,7 +1645,6 @@ var HOME_SECTIONS = [
|
|
|
1643
1645
|
id: "workflow",
|
|
1644
1646
|
label: "Workflow",
|
|
1645
1647
|
hint: "next \xB7 status \xB7 advance \xB7 handoff \xB7 wrap",
|
|
1646
|
-
key: "3",
|
|
1647
1648
|
items: [
|
|
1648
1649
|
{
|
|
1649
1650
|
id: "task next",
|
|
@@ -1678,7 +1679,6 @@ var HOME_SECTIONS = [
|
|
|
1678
1679
|
id: "setup",
|
|
1679
1680
|
label: "Setup & maintenance",
|
|
1680
1681
|
hint: "init \xB7 create \xB7 sync \xB7 skills \xB7 install \xB7 update",
|
|
1681
|
-
key: "4",
|
|
1682
1682
|
items: [
|
|
1683
1683
|
{
|
|
1684
1684
|
id: "init",
|
|
@@ -1727,7 +1727,6 @@ var HOME_SECTIONS = [
|
|
|
1727
1727
|
id: "dashboard",
|
|
1728
1728
|
label: "Dashboard (full-screen)",
|
|
1729
1729
|
hint: "noir tui \xB7 noir palette \xB7 home",
|
|
1730
|
-
key: "5",
|
|
1731
1730
|
items: [
|
|
1732
1731
|
{
|
|
1733
1732
|
id: "tui",
|
|
@@ -1956,6 +1955,19 @@ ${COMMANDS_HINT}
|
|
|
1956
1955
|
`
|
|
1957
1956
|
${c.warn(`noir installed via ${rec.method}`)} \u2014 consider \`noir install\` for the native path (auto-update, no npm prefix/PATH issues). Dismiss with: \`noir install --dismiss\` (persisted per version).
|
|
1958
1957
|
|
|
1958
|
+
`
|
|
1959
|
+
);
|
|
1960
|
+
}
|
|
1961
|
+
const updateNotice = buildUpdateNotice({
|
|
1962
|
+
method: detectActiveMethod(),
|
|
1963
|
+
currentVersion: NOIR_VERSION3,
|
|
1964
|
+
latestKnown: latestVersionFromCache(readUpdateCache(), "latest")
|
|
1965
|
+
});
|
|
1966
|
+
if (updateNotice) {
|
|
1967
|
+
process.stderr.write(
|
|
1968
|
+
`
|
|
1969
|
+
${c.warn(`noir ${updateNotice.latestVersion} is available`)} (you have ${updateNotice.currentVersion}) \u2014 ${updateNotice.advice}.
|
|
1970
|
+
|
|
1959
1971
|
`
|
|
1960
1972
|
);
|
|
1961
1973
|
}
|
|
@@ -2321,6 +2333,255 @@ async function release(opts) {
|
|
|
2321
2333
|
}
|
|
2322
2334
|
}
|
|
2323
2335
|
|
|
2336
|
+
// src/commands/run.ts
|
|
2337
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2338
|
+
import { join as join2 } from "path";
|
|
2339
|
+
import { SUPPORTED_HOSTS } from "@noir-ai/adapters";
|
|
2340
|
+
|
|
2341
|
+
// src/orchestrator.ts
|
|
2342
|
+
import { spawn } from "child_process";
|
|
2343
|
+
import { createInterface } from "readline";
|
|
2344
|
+
var HOST_BINARY = {
|
|
2345
|
+
claude: "claude",
|
|
2346
|
+
gemini: "gemini",
|
|
2347
|
+
opencode: "opencode",
|
|
2348
|
+
cursor: "cursor-agent",
|
|
2349
|
+
"agents-md": null
|
|
2350
|
+
// emits AGENTS.md — a file host, not a spawnable CLI
|
|
2351
|
+
};
|
|
2352
|
+
var HOST_FLAGS = {
|
|
2353
|
+
claude: ["-p", "--output-format", "stream-json", "--verbose"],
|
|
2354
|
+
gemini: ["-p", "--output-format", "stream-json"],
|
|
2355
|
+
opencode: ["run"],
|
|
2356
|
+
cursor: ["-p"],
|
|
2357
|
+
"agents-md": []
|
|
2358
|
+
};
|
|
2359
|
+
function resolveHostRun(host, customBinary) {
|
|
2360
|
+
const binary = customBinary && customBinary.length > 0 ? customBinary : HOST_BINARY[host];
|
|
2361
|
+
if (!binary) return null;
|
|
2362
|
+
return { binary, flags: HOST_FLAGS[host] };
|
|
2363
|
+
}
|
|
2364
|
+
function asObj(v) {
|
|
2365
|
+
return typeof v === "object" && v !== null ? v : null;
|
|
2366
|
+
}
|
|
2367
|
+
function str(v) {
|
|
2368
|
+
return typeof v === "string" ? v : void 0;
|
|
2369
|
+
}
|
|
2370
|
+
function num(v) {
|
|
2371
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
2372
|
+
}
|
|
2373
|
+
function usageFrom(v) {
|
|
2374
|
+
const o = asObj(v);
|
|
2375
|
+
if (!o) return void 0;
|
|
2376
|
+
const input = num(o.input_tokens);
|
|
2377
|
+
const output = num(o.output_tokens);
|
|
2378
|
+
const cache = num(o.cache_read_input_tokens) ?? num(o.cache_creation_input_tokens);
|
|
2379
|
+
if (input === void 0 && output === void 0 && cache === void 0) return void 0;
|
|
2380
|
+
return { inputTokens: input, outputTokens: output, cacheReadTokens: cache };
|
|
2381
|
+
}
|
|
2382
|
+
function messageText(message) {
|
|
2383
|
+
if (!message) return void 0;
|
|
2384
|
+
const content = message.content;
|
|
2385
|
+
if (typeof content === "string") return content;
|
|
2386
|
+
if (Array.isArray(content)) {
|
|
2387
|
+
const parts = content.map((block) => {
|
|
2388
|
+
const b = asObj(block);
|
|
2389
|
+
return b && typeof b.text === "string" ? b.text : null;
|
|
2390
|
+
}).filter((t) => t !== null);
|
|
2391
|
+
if (parts.length > 0) return parts.join("");
|
|
2392
|
+
}
|
|
2393
|
+
return void 0;
|
|
2394
|
+
}
|
|
2395
|
+
function parseStreamLine(line) {
|
|
2396
|
+
const trimmed = line.trim();
|
|
2397
|
+
if (trimmed.length === 0) return null;
|
|
2398
|
+
try {
|
|
2399
|
+
return JSON.parse(trimmed);
|
|
2400
|
+
} catch {
|
|
2401
|
+
return null;
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
function normalizeStreamEvent(raw) {
|
|
2405
|
+
const r = asObj(raw);
|
|
2406
|
+
if (!r) return null;
|
|
2407
|
+
const type = r.type;
|
|
2408
|
+
if (type === "system") {
|
|
2409
|
+
if (r.subtype === "init") {
|
|
2410
|
+
return { kind: "init", sessionId: str(r.session_id), model: str(r.model) };
|
|
2411
|
+
}
|
|
2412
|
+
return { kind: "other" };
|
|
2413
|
+
}
|
|
2414
|
+
if (type === "assistant") {
|
|
2415
|
+
const message = asObj(r.message);
|
|
2416
|
+
return {
|
|
2417
|
+
kind: "assistant",
|
|
2418
|
+
messageId: message ? str(message.id) : void 0,
|
|
2419
|
+
text: messageText(message),
|
|
2420
|
+
usage: message ? usageFrom(message.usage) : void 0
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
if (type === "result") {
|
|
2424
|
+
return {
|
|
2425
|
+
kind: "result",
|
|
2426
|
+
isError: r.is_error === true,
|
|
2427
|
+
durationMs: num(r.duration_ms),
|
|
2428
|
+
numTurns: num(r.num_turns),
|
|
2429
|
+
totalCostUsd: num(r.total_cost_usd),
|
|
2430
|
+
usage: usageFrom(r.usage)
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
return { kind: "other" };
|
|
2434
|
+
}
|
|
2435
|
+
var UsageReducer = class {
|
|
2436
|
+
maxByMessage = /* @__PURE__ */ new Map();
|
|
2437
|
+
input = 0;
|
|
2438
|
+
output = 0;
|
|
2439
|
+
costUsd = 0;
|
|
2440
|
+
turns = 0;
|
|
2441
|
+
/** Feed one normalized event into the reducer. */
|
|
2442
|
+
add(event) {
|
|
2443
|
+
if (event.kind === "assistant" && event.messageId && event.usage) {
|
|
2444
|
+
const key = event.messageId;
|
|
2445
|
+
const input = event.usage.inputTokens ?? 0;
|
|
2446
|
+
const output = event.usage.outputTokens ?? 0;
|
|
2447
|
+
const prev = this.maxByMessage.get(key);
|
|
2448
|
+
if (!prev) {
|
|
2449
|
+
this.maxByMessage.set(key, { input, output });
|
|
2450
|
+
this.input += input;
|
|
2451
|
+
this.output += output;
|
|
2452
|
+
} else {
|
|
2453
|
+
this.input += Math.max(0, input - prev.input);
|
|
2454
|
+
this.output += Math.max(0, output - prev.output);
|
|
2455
|
+
this.maxByMessage.set(key, {
|
|
2456
|
+
input: Math.max(prev.input, input),
|
|
2457
|
+
output: Math.max(prev.output, output)
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
} else if (event.kind === "result") {
|
|
2461
|
+
if (event.totalCostUsd !== void 0) this.costUsd = event.totalCostUsd;
|
|
2462
|
+
if (event.numTurns !== void 0) this.turns = event.numTurns;
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
/** The current accumulator snapshot. */
|
|
2466
|
+
snapshot() {
|
|
2467
|
+
return {
|
|
2468
|
+
inputTokens: this.input,
|
|
2469
|
+
outputTokens: this.output,
|
|
2470
|
+
totalCostUsd: this.costUsd,
|
|
2471
|
+
numTurns: this.turns
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
};
|
|
2475
|
+
function runHost(opts) {
|
|
2476
|
+
const spec = resolveHostRun(opts.host, opts.customBinary);
|
|
2477
|
+
return new Promise((resolve, reject) => {
|
|
2478
|
+
if (!spec) {
|
|
2479
|
+
reject(new Error(`host '${opts.host}' is not a spawnable CLI (no default command)`));
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
const child = spawn(spec.binary, [...spec.flags, opts.prompt], {
|
|
2483
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2484
|
+
});
|
|
2485
|
+
const reducer = new UsageReducer();
|
|
2486
|
+
let eventCount = 0;
|
|
2487
|
+
let stderrBuf = "";
|
|
2488
|
+
child.on("error", (err) => reject(err));
|
|
2489
|
+
const rl = createInterface({ input: child.stdout });
|
|
2490
|
+
rl.on("line", (line) => {
|
|
2491
|
+
opts.onLine?.(line);
|
|
2492
|
+
const raw = parseStreamLine(line);
|
|
2493
|
+
if (raw === null) return;
|
|
2494
|
+
const event = normalizeStreamEvent(raw);
|
|
2495
|
+
if (event === null) return;
|
|
2496
|
+
eventCount += 1;
|
|
2497
|
+
reducer.add(event);
|
|
2498
|
+
opts.onEvent?.(event);
|
|
2499
|
+
});
|
|
2500
|
+
child.stderr.setEncoding("utf8");
|
|
2501
|
+
child.stderr.on("data", (chunk) => {
|
|
2502
|
+
stderrBuf += chunk;
|
|
2503
|
+
});
|
|
2504
|
+
child.on("close", (code) => {
|
|
2505
|
+
resolve({ exitCode: code ?? 0, usage: reducer.snapshot(), eventCount, stderr: stderrBuf });
|
|
2506
|
+
});
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
// src/commands/run.ts
|
|
2511
|
+
function formatUsage(u) {
|
|
2512
|
+
const cost = u.totalCostUsd > 0 ? ` $${u.totalCostUsd.toFixed(2)}` : "";
|
|
2513
|
+
const turns = u.numTurns > 0 ? ` \xB7 ${u.numTurns} turns` : "";
|
|
2514
|
+
return `${u.inputTokens.toLocaleString()} in / ${u.outputTokens.toLocaleString()} out${cost}${turns}`;
|
|
2515
|
+
}
|
|
2516
|
+
function streamEvent(event, opts) {
|
|
2517
|
+
if (opts.json === true) return;
|
|
2518
|
+
if (event.kind === "assistant" && event.text && event.text.length > 0) {
|
|
2519
|
+
process.stdout.write(event.text);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
async function run(prompt, opts) {
|
|
2523
|
+
const host = opts.host ?? "claude";
|
|
2524
|
+
if (!SUPPORTED_HOSTS.includes(host)) {
|
|
2525
|
+
fail(EXIT.USAGE, `unknown host '${host}' (supported: ${SUPPORTED_HOSTS.join(", ")})`, opts);
|
|
2526
|
+
}
|
|
2527
|
+
if (prompt.length === 0) {
|
|
2528
|
+
fail(EXIT.USAGE, "a prompt is required: `noir run <prompt>`", opts);
|
|
2529
|
+
}
|
|
2530
|
+
const transcriptLines = [];
|
|
2531
|
+
try {
|
|
2532
|
+
const result = await runHost({
|
|
2533
|
+
host,
|
|
2534
|
+
prompt,
|
|
2535
|
+
customBinary: opts.command,
|
|
2536
|
+
onLine: (line) => transcriptLines.push(line),
|
|
2537
|
+
onEvent: (event) => streamEvent(event, opts)
|
|
2538
|
+
});
|
|
2539
|
+
if (opts.json !== true) {
|
|
2540
|
+
process.stdout.write("\n");
|
|
2541
|
+
}
|
|
2542
|
+
const transcript = writeTranscript(host, transcriptLines);
|
|
2543
|
+
if (opts.json === true) {
|
|
2544
|
+
json({
|
|
2545
|
+
ok: true,
|
|
2546
|
+
data: {
|
|
2547
|
+
host,
|
|
2548
|
+
prompt,
|
|
2549
|
+
exitCode: result.exitCode,
|
|
2550
|
+
usage: result.usage,
|
|
2551
|
+
numTurns: result.usage.numTurns,
|
|
2552
|
+
events: result.eventCount,
|
|
2553
|
+
transcript
|
|
2554
|
+
}
|
|
2555
|
+
});
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
if (result.exitCode !== 0) {
|
|
2559
|
+
log(`host exited ${result.exitCode}`, opts);
|
|
2560
|
+
if (result.stderr.length > 0) log(result.stderr.trim(), opts);
|
|
2561
|
+
}
|
|
2562
|
+
success(`usage: ${formatUsage(result.usage)} (API-equivalent estimate, not billed)`, opts);
|
|
2563
|
+
log(`transcript: ${transcript}`, opts);
|
|
2564
|
+
} catch (err) {
|
|
2565
|
+
fail(
|
|
2566
|
+
EXIT.ERROR,
|
|
2567
|
+
`failed to run host '${host}': ${err instanceof Error ? err.message : String(err)}`,
|
|
2568
|
+
opts
|
|
2569
|
+
);
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
function writeTranscript(host, lines) {
|
|
2573
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2574
|
+
const dir = join2(process.cwd(), ".noir", "transcripts");
|
|
2575
|
+
const file = join2(dir, `${host}-${ts}.jsonl`);
|
|
2576
|
+
try {
|
|
2577
|
+
mkdirSync2(dir, { recursive: true });
|
|
2578
|
+
writeFileSync2(file, `${lines.join("\n")}${lines.length > 0 ? "\n" : ""}`);
|
|
2579
|
+
} catch {
|
|
2580
|
+
return "(not persisted)";
|
|
2581
|
+
}
|
|
2582
|
+
return file;
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2324
2585
|
// src/commands/skills.ts
|
|
2325
2586
|
import { claudeAdapter } from "@noir-ai/adapters";
|
|
2326
2587
|
import { loadProjectInfo as loadProjectInfo7 } from "@noir-ai/core";
|
|
@@ -2640,8 +2901,8 @@ function createProgram() {
|
|
|
2640
2901
|
// them), errors → stderr. --json data is emitted by command actions via
|
|
2641
2902
|
// process.stdout directly, NOT by commander's writeOut, so routing help/
|
|
2642
2903
|
// version to stdout does not conflict with --json payloads.
|
|
2643
|
-
writeOut: (
|
|
2644
|
-
writeErr: (
|
|
2904
|
+
writeOut: (str2) => process.stdout.write(str2),
|
|
2905
|
+
writeErr: (str2) => process.stderr.write(str2)
|
|
2645
2906
|
});
|
|
2646
2907
|
program2.hook("preAction", (_thisCmd, actionCmd) => {
|
|
2647
2908
|
const opts = actionCmd.optsWithGlobals();
|
|
@@ -2672,7 +2933,7 @@ function createProgram() {
|
|
|
2672
2933
|
new Option(
|
|
2673
2934
|
"--host <id>",
|
|
2674
2935
|
"target agentic CLI (default: claude) \u2014 drives host-side emission"
|
|
2675
|
-
).choices(
|
|
2936
|
+
).choices(SUPPORTED_HOSTS2)
|
|
2676
2937
|
).action(
|
|
2677
2938
|
async (opts, cmd) => {
|
|
2678
2939
|
const transport = opts.transport === "streamable-http" ? "streamable-http" : "stdio";
|
|
@@ -2697,7 +2958,7 @@ function createProgram() {
|
|
|
2697
2958
|
new Option(
|
|
2698
2959
|
"--host <id>",
|
|
2699
2960
|
"target agentic CLI (default: claude) \u2014 drives host-side emission"
|
|
2700
|
-
).choices(
|
|
2961
|
+
).choices(SUPPORTED_HOSTS2)
|
|
2701
2962
|
).action(
|
|
2702
2963
|
async (dir, opts, cmd) => {
|
|
2703
2964
|
const transport = opts.transport === "streamable-http" ? "streamable-http" : "stdio";
|
|
@@ -2740,7 +3001,7 @@ function createProgram() {
|
|
|
2740
3001
|
new Option(
|
|
2741
3002
|
"--host <id>",
|
|
2742
3003
|
"override the configured host (advanced; default reads .noir/config.yml)"
|
|
2743
|
-
).choices(
|
|
3004
|
+
).choices(SUPPORTED_HOSTS2)
|
|
2744
3005
|
).action(
|
|
2745
3006
|
async (opts, cmd) => {
|
|
2746
3007
|
const { sync } = await import("./sync-Y7MTQ4JC.js");
|
|
@@ -3059,7 +3320,7 @@ function createProgram() {
|
|
|
3059
3320
|
);
|
|
3060
3321
|
installCmd.alias("migrate");
|
|
3061
3322
|
program2.command("update").description("update Noir to the latest version via the active install method").option("--check", "check for a new version without changing anything").argument("[spec]", "channel ('latest'|'beta') or exact version (default: latest)").action(async (spec, opts, cmd) => {
|
|
3062
|
-
const { update } = await import("./update-
|
|
3323
|
+
const { update } = await import("./update-OM2EP2F5.js");
|
|
3063
3324
|
const cli = cmd.optsWithGlobals();
|
|
3064
3325
|
await update({ ...cli, spec, check: opts.check === true });
|
|
3065
3326
|
});
|
|
@@ -3120,6 +3381,16 @@ function createProgram() {
|
|
|
3120
3381
|
};
|
|
3121
3382
|
await runPalette2(opts, dispatch);
|
|
3122
3383
|
});
|
|
3384
|
+
program2.command("run").description("drive the host CLI headless and render its stream-json (v2)").argument("[prompt...]", "prompt to send to the host").addOption(new Option("--host <id>", "host to drive (default claude)").choices(SUPPORTED_HOSTS2)).option("--command <binary>", "custom host binary (e.g. claude-work)").action(async (...args) => {
|
|
3385
|
+
const g = trailingCmd(args).optsWithGlobals();
|
|
3386
|
+
const prompt = args.filter((a) => typeof a === "string").join(" ").trim();
|
|
3387
|
+
const opts = {
|
|
3388
|
+
...toCliOptions(g),
|
|
3389
|
+
...typeof g.host === "string" ? { host: g.host } : {},
|
|
3390
|
+
...typeof g.command === "string" ? { command: g.command } : {}
|
|
3391
|
+
};
|
|
3392
|
+
await run(prompt, opts);
|
|
3393
|
+
});
|
|
3123
3394
|
program2.action(async (...args) => {
|
|
3124
3395
|
const cmd = trailingCmd(args);
|
|
3125
3396
|
const leftovers = cmd.args;
|
|
@@ -3149,7 +3420,7 @@ var homeDeps = {
|
|
|
3149
3420
|
// uses — so the menu cannot drift from the commander tree.
|
|
3150
3421
|
commands: buildPaletteCommands(createProgram())
|
|
3151
3422
|
};
|
|
3152
|
-
async function
|
|
3423
|
+
async function run2(argv = []) {
|
|
3153
3424
|
try {
|
|
3154
3425
|
await program.parseAsync([...argv], { from: "user" });
|
|
3155
3426
|
} catch (err) {
|
|
@@ -3167,12 +3438,12 @@ var isMainModule = (() => {
|
|
|
3167
3438
|
}
|
|
3168
3439
|
})();
|
|
3169
3440
|
if (isMainModule) {
|
|
3170
|
-
void
|
|
3441
|
+
void run2(process.argv.slice(2));
|
|
3171
3442
|
}
|
|
3172
3443
|
|
|
3173
3444
|
// src/tui/App.tsx
|
|
3174
|
-
import { Box as
|
|
3175
|
-
import { useEffect, useState as
|
|
3445
|
+
import { Box as Box5, Text as Text8, useApp, useInput } from "ink";
|
|
3446
|
+
import { useEffect, useState as useState2 } from "react";
|
|
3176
3447
|
|
|
3177
3448
|
// src/tui/CommandInput.tsx
|
|
3178
3449
|
import { Box, Text } from "ink";
|
|
@@ -3228,14 +3499,30 @@ async function captureProcessOutput(fn) {
|
|
|
3228
3499
|
|
|
3229
3500
|
// src/tui/Footer.tsx
|
|
3230
3501
|
import { Text as Text2 } from "ink";
|
|
3502
|
+
|
|
3503
|
+
// src/tui/hints.ts
|
|
3504
|
+
var FOOTER_HINT = "?/h/Ctrl+K palette \xB7 Ctrl+F find \xB7 \u2191/\u2193 scroll \xB7 Enter run \xB7 q/Esc quit \xB7 Ctrl+C exit";
|
|
3505
|
+
var RUNNING_HINT = "running\u2026 (Ctrl+C to force exit)";
|
|
3506
|
+
var LIST_NAV_HINT = "\u2191/\u2193 navigate \xB7 Enter run \xB7 Esc close \xB7 Tab corpus";
|
|
3507
|
+
var HELP_ENTRIES = [
|
|
3508
|
+
{ keys: "/command", desc: "run a Noir sub-command (e.g. /status, /sync, /task next)" },
|
|
3509
|
+
{ keys: "Enter", desc: "run the typed /command" },
|
|
3510
|
+
{ keys: "Esc", desc: "back: clear input \u2192 dismiss output \u2192 quit" },
|
|
3511
|
+
{ keys: "q", desc: "quit (when the input is empty)" },
|
|
3512
|
+
{ keys: "\u2191 / \u2193", desc: "scroll the output pane (dashboard) \xB7 navigate the list (palette)" },
|
|
3513
|
+
{ keys: "?", desc: "palette \u2014 help & keybindings (this list)" },
|
|
3514
|
+
{ keys: "h", desc: "palette \u2014 commands (quick actions + all commands)" },
|
|
3515
|
+
{ keys: "Ctrl+K", desc: "palette \u2014 all commands (fuzzy search)" },
|
|
3516
|
+
{ keys: "Ctrl+F", desc: "palette \u2014 find in the dispatched output" },
|
|
3517
|
+
{ keys: "Tab", desc: "switch the palette corpus" },
|
|
3518
|
+
{ keys: "y / n", desc: "approve / decline a destructive command prompt" },
|
|
3519
|
+
{ keys: "Ctrl+C", desc: "force exit" }
|
|
3520
|
+
];
|
|
3521
|
+
|
|
3522
|
+
// src/tui/Footer.tsx
|
|
3231
3523
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
3232
3524
|
function Footer({ running }) {
|
|
3233
|
-
|
|
3234
|
-
return /* @__PURE__ */ jsx2(Text2, { children: c.dim("running\u2026 (Ctrl+C to force exit)") });
|
|
3235
|
-
}
|
|
3236
|
-
return /* @__PURE__ */ jsx2(Text2, { children: c.dim(
|
|
3237
|
-
"? help \xB7 q/Esc quit \xB7 \u2191/\u2193 scroll \xB7 Enter run \xB7 Ctrl+K palette \xB7 Ctrl+F find \xB7 Ctrl+C exit"
|
|
3238
|
-
) });
|
|
3525
|
+
return /* @__PURE__ */ jsx2(Text2, { children: c.dim(running ? RUNNING_HINT : FOOTER_HINT) });
|
|
3239
3526
|
}
|
|
3240
3527
|
|
|
3241
3528
|
// src/tui/format.ts
|
|
@@ -3266,105 +3553,15 @@ function Header({ tagline }) {
|
|
|
3266
3553
|
] });
|
|
3267
3554
|
}
|
|
3268
3555
|
|
|
3269
|
-
// src/tui/HomeMenu.tsx
|
|
3270
|
-
import { Box as Box3, Text as Text4, useInput } from "ink";
|
|
3271
|
-
import { useState } from "react";
|
|
3272
|
-
|
|
3273
|
-
// src/tui/Panel.tsx
|
|
3274
|
-
import { Box as Box2 } from "ink";
|
|
3275
|
-
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
3276
|
-
function Panel({ children, maxWidth }) {
|
|
3277
|
-
const full = contentWidth() + 4;
|
|
3278
|
-
const width = maxWidth != null ? Math.min(full, maxWidth) : full;
|
|
3279
|
-
return /* @__PURE__ */ jsx4(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, width, children });
|
|
3280
|
-
}
|
|
3281
|
-
|
|
3282
|
-
// src/tui/HomeMenu.tsx
|
|
3283
|
-
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
3284
|
-
function flatten(sections) {
|
|
3285
|
-
const rows = [];
|
|
3286
|
-
for (const section of sections) {
|
|
3287
|
-
for (const action of section.items) {
|
|
3288
|
-
rows.push({ section, action });
|
|
3289
|
-
}
|
|
3290
|
-
}
|
|
3291
|
-
return rows;
|
|
3292
|
-
}
|
|
3293
|
-
function HomeMenu({ sections, onSelect, onClose }) {
|
|
3294
|
-
const [active, setActive] = useState(0);
|
|
3295
|
-
const rows = flatten(sections);
|
|
3296
|
-
const activeRow = rows[Math.min(Math.max(active, 0), Math.max(0, rows.length - 1))];
|
|
3297
|
-
useInput((input, key) => {
|
|
3298
|
-
if (key.escape || key.tab) {
|
|
3299
|
-
onClose();
|
|
3300
|
-
return;
|
|
3301
|
-
}
|
|
3302
|
-
if (key.return) {
|
|
3303
|
-
if (activeRow) onSelect(activeRow.action);
|
|
3304
|
-
return;
|
|
3305
|
-
}
|
|
3306
|
-
if (key.upArrow) {
|
|
3307
|
-
setActive((i) => Math.max(0, i - 1));
|
|
3308
|
-
return;
|
|
3309
|
-
}
|
|
3310
|
-
if (key.downArrow) {
|
|
3311
|
-
setActive((i) => Math.min(rows.length - 1, i + 1));
|
|
3312
|
-
return;
|
|
3313
|
-
}
|
|
3314
|
-
if (input === "k") {
|
|
3315
|
-
setActive((i) => Math.max(0, i - 1));
|
|
3316
|
-
return;
|
|
3317
|
-
}
|
|
3318
|
-
if (input === "j") {
|
|
3319
|
-
setActive((i) => Math.min(rows.length - 1, i + 1));
|
|
3320
|
-
return;
|
|
3321
|
-
}
|
|
3322
|
-
});
|
|
3323
|
-
const rowsEl = [];
|
|
3324
|
-
let lastSectionId = null;
|
|
3325
|
-
for (let i = 0; i < rows.length; i++) {
|
|
3326
|
-
const row = rows[i];
|
|
3327
|
-
if (!row) continue;
|
|
3328
|
-
const { section, action } = row;
|
|
3329
|
-
if (section.id !== lastSectionId) {
|
|
3330
|
-
rowsEl.push(/* @__PURE__ */ jsx5(Text4, { children: c.dim(`\u2500\u2500 ${section.label} \u2500\u2500`) }, `sec:${section.id}`));
|
|
3331
|
-
lastSectionId = section.id;
|
|
3332
|
-
}
|
|
3333
|
-
const isActive = i === activeRowIndex(active, rows);
|
|
3334
|
-
const prefix = isActive ? "\u25B8 " : " ";
|
|
3335
|
-
rowsEl.push(
|
|
3336
|
-
/* @__PURE__ */ jsxs3(Text4, { children: [
|
|
3337
|
-
prefix,
|
|
3338
|
-
isActive ? c.accent(action.label) : action.label,
|
|
3339
|
-
/* @__PURE__ */ jsx5(Text4, { children: c.dim(` ${action.hint}`) })
|
|
3340
|
-
] }, action.id)
|
|
3341
|
-
);
|
|
3342
|
-
}
|
|
3343
|
-
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
3344
|
-
/* @__PURE__ */ jsxs3(Panel, { children: [
|
|
3345
|
-
/* @__PURE__ */ jsx5(Box3, { paddingX: 1, children: /* @__PURE__ */ jsxs3(Text4, { children: [
|
|
3346
|
-
c.bold("\u25B8 home "),
|
|
3347
|
-
/* @__PURE__ */ jsx5(Text4, { children: c.dim(`quick actions (${rows.length})`) })
|
|
3348
|
-
] }) }),
|
|
3349
|
-
rowsEl.map((row) => /* @__PURE__ */ jsx5(Box3, { paddingX: 1, children: row }, row.key ?? "row"))
|
|
3350
|
-
] }),
|
|
3351
|
-
/* @__PURE__ */ jsx5(Text4, { children: c.dim("\u2191/\u2193 navigate \xB7 Enter run \xB7 Esc close") })
|
|
3352
|
-
] });
|
|
3353
|
-
}
|
|
3354
|
-
function activeRowIndex(active, rows) {
|
|
3355
|
-
return Math.min(Math.max(active, 0), Math.max(0, rows.length - 1));
|
|
3356
|
-
}
|
|
3357
|
-
|
|
3358
3556
|
// src/tui/hooks/useInputBuffer.ts
|
|
3359
|
-
import { useRef, useState
|
|
3557
|
+
import { useRef, useState } from "react";
|
|
3360
3558
|
function useInputBuffer() {
|
|
3361
|
-
const [buffer, setBuffer] =
|
|
3559
|
+
const [buffer, setBuffer] = useState("");
|
|
3362
3560
|
const historyRef = useRef([]);
|
|
3363
3561
|
const cursorRef = useRef(-1);
|
|
3364
3562
|
function pushHistory(text) {
|
|
3365
3563
|
if (text.length === 0) return;
|
|
3366
|
-
const history = historyRef.current;
|
|
3367
|
-
if (history.length > 0 && history[0] === text) return;
|
|
3564
|
+
const history = historyRef.current.filter((e) => e !== text);
|
|
3368
3565
|
historyRef.current = [text, ...history];
|
|
3369
3566
|
}
|
|
3370
3567
|
function recall(dir) {
|
|
@@ -3383,12 +3580,16 @@ function useInputBuffer() {
|
|
|
3383
3580
|
setBuffer(() => "");
|
|
3384
3581
|
cursorRef.current = -1;
|
|
3385
3582
|
}
|
|
3386
|
-
|
|
3583
|
+
function seed(entries) {
|
|
3584
|
+
historyRef.current = [...entries];
|
|
3585
|
+
cursorRef.current = -1;
|
|
3586
|
+
}
|
|
3587
|
+
return { buffer, setBuffer, pushHistory, recall, clear, seed };
|
|
3387
3588
|
}
|
|
3388
3589
|
|
|
3389
3590
|
// src/tui/OutputPane.tsx
|
|
3390
|
-
import { Text as
|
|
3391
|
-
import { Fragment, jsx as
|
|
3591
|
+
import { Text as Text4 } from "ink";
|
|
3592
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
3392
3593
|
function toRows(visible, baseIndex) {
|
|
3393
3594
|
const rows = [];
|
|
3394
3595
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -3422,13 +3623,13 @@ function OutputPane({
|
|
|
3422
3623
|
}) {
|
|
3423
3624
|
const width = contentWidth();
|
|
3424
3625
|
if (lines.length === 0) {
|
|
3425
|
-
return /* @__PURE__ */
|
|
3626
|
+
return /* @__PURE__ */ jsx4(Text4, { children: c.dim("(no output \u2014 type a /command, or ? for help)") });
|
|
3426
3627
|
}
|
|
3427
3628
|
const maxOffset = Math.max(0, lines.length - height);
|
|
3428
3629
|
const offset = Math.min(Math.max(0, scrollOffset), maxOffset);
|
|
3429
3630
|
const rows = toRows(lines.slice(offset, offset + height), offset);
|
|
3430
|
-
return /* @__PURE__ */
|
|
3431
|
-
title !== void 0 ? /* @__PURE__ */
|
|
3631
|
+
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
3632
|
+
title !== void 0 ? /* @__PURE__ */ jsx4(Text4, { wrap: "truncate-end", children: c.dim(`\u2500\u2500 ${title} \u2500\u2500`) }) : null,
|
|
3432
3633
|
rows.map((row) => {
|
|
3433
3634
|
const truncated = row.text.length > width ? `${row.text.slice(0, Math.max(1, width - 1))}\u2026` : row.text;
|
|
3434
3635
|
let body = truncated;
|
|
@@ -3437,32 +3638,32 @@ function OutputPane({
|
|
|
3437
3638
|
} else if (highlightQuery !== void 0 && highlightQuery.length > 0) {
|
|
3438
3639
|
body = highlightFirst(truncated, highlightQuery);
|
|
3439
3640
|
}
|
|
3440
|
-
return /* @__PURE__ */
|
|
3641
|
+
return /* @__PURE__ */ jsx4(Text4, { wrap: "truncate-end", children: body }, row.key);
|
|
3441
3642
|
})
|
|
3442
3643
|
] });
|
|
3443
3644
|
}
|
|
3444
3645
|
|
|
3445
3646
|
// src/tui/overlays/ConfirmOverlay.tsx
|
|
3446
|
-
import { Box as
|
|
3447
|
-
|
|
3647
|
+
import { Box as Box3, Text as Text5 } from "ink";
|
|
3648
|
+
|
|
3649
|
+
// src/tui/Panel.tsx
|
|
3650
|
+
import { Box as Box2 } from "ink";
|
|
3651
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
3652
|
+
function Panel({ children, maxWidth }) {
|
|
3653
|
+
const full = contentWidth() + 4;
|
|
3654
|
+
const width = maxWidth != null ? Math.min(full, maxWidth) : full;
|
|
3655
|
+
return /* @__PURE__ */ jsx5(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, width, children });
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
// src/tui/overlays/ConfirmOverlay.tsx
|
|
3659
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
3448
3660
|
function ConfirmOverlay({ argv }) {
|
|
3449
|
-
return /* @__PURE__ */
|
|
3661
|
+
return /* @__PURE__ */ jsx6(Panel, { children: /* @__PURE__ */ jsx6(Box3, { paddingX: 1, children: /* @__PURE__ */ jsxs4(Text5, { children: [
|
|
3450
3662
|
c.warn(`! run /${argv.join(" ")}? (y/N)`),
|
|
3451
|
-
/* @__PURE__ */
|
|
3663
|
+
/* @__PURE__ */ jsx6(Text5, { children: c.dim(" [y approve \xB7 n/Esc back to palette]") })
|
|
3452
3664
|
] }) }) });
|
|
3453
3665
|
}
|
|
3454
3666
|
|
|
3455
|
-
// src/tui/overlays/SearchMode.ts
|
|
3456
|
-
function computeMatches(lines, query) {
|
|
3457
|
-
if (query.length === 0) return [];
|
|
3458
|
-
const needle = query.toLowerCase();
|
|
3459
|
-
const out = [];
|
|
3460
|
-
for (let i = 0; i < lines.length; i++) {
|
|
3461
|
-
if ((lines[i] ?? "").toLowerCase().includes(needle)) out.push(i);
|
|
3462
|
-
}
|
|
3463
|
-
return out;
|
|
3464
|
-
}
|
|
3465
|
-
|
|
3466
3667
|
// src/tui/palette/fuzzyMatch.ts
|
|
3467
3668
|
var WEIGHTS = {
|
|
3468
3669
|
label: 1,
|
|
@@ -3556,141 +3757,208 @@ var handRolledMatcher = {
|
|
|
3556
3757
|
};
|
|
3557
3758
|
|
|
3558
3759
|
// src/tui/palette/Palette.tsx
|
|
3559
|
-
import { Box as
|
|
3560
|
-
import {
|
|
3561
|
-
import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
3760
|
+
import { Box as Box4, Text as Text6 } from "ink";
|
|
3761
|
+
import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
3562
3762
|
var PALETTE_WIDTH = 64;
|
|
3563
3763
|
var VISIBLE_ROWS = 10;
|
|
3564
|
-
var MATCH_LIMIT = 20;
|
|
3565
|
-
function rankPalette(query, matcher, commands, recent) {
|
|
3566
|
-
if (query.length === 0) {
|
|
3567
|
-
const recents = recentDeduped(recent, commands);
|
|
3568
|
-
const recentIds = new Set(recents.map((r) => r.id));
|
|
3569
|
-
const rest = commands.filter((cmd) => !recentIds.has(cmd.id));
|
|
3570
|
-
return [
|
|
3571
|
-
...recents.map((cmd) => ({ cmd, indices: [] })),
|
|
3572
|
-
...rest.map((cmd) => ({ cmd, indices: [] }))
|
|
3573
|
-
];
|
|
3574
|
-
}
|
|
3575
|
-
return matcher.search(query, commands, MATCH_LIMIT).map((m) => ({
|
|
3576
|
-
cmd: m.item,
|
|
3577
|
-
indices: m.matchedIndices
|
|
3578
|
-
}));
|
|
3579
|
-
}
|
|
3580
|
-
function truncateLabel(label) {
|
|
3581
|
-
const max = Math.max(20, PALETTE_WIDTH - 8);
|
|
3582
|
-
return label.length > max ? `${label.slice(0, max - 1)}\u2026` : label;
|
|
3583
|
-
}
|
|
3584
3764
|
function highlight(label, matchedIndices) {
|
|
3765
|
+
if (matchedIndices.length === 0) return /* @__PURE__ */ jsx7(Fragment2, { children: label });
|
|
3585
3766
|
const set = new Set(matchedIndices);
|
|
3586
3767
|
const chars = [];
|
|
3587
3768
|
for (let i = 0; i < label.length; i++) {
|
|
3588
3769
|
const ch = label[i] ?? "";
|
|
3589
|
-
chars.push(set.has(i) ? /* @__PURE__ */
|
|
3770
|
+
chars.push(set.has(i) ? /* @__PURE__ */ jsx7(Text6, { children: c.accent(ch) }, i) : /* @__PURE__ */ jsx7(Text6, { children: ch }, i));
|
|
3590
3771
|
}
|
|
3591
|
-
return /* @__PURE__ */
|
|
3772
|
+
return /* @__PURE__ */ jsx7(Fragment2, { children: chars });
|
|
3592
3773
|
}
|
|
3593
|
-
function
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
const visible =
|
|
3604
|
-
const
|
|
3605
|
-
const
|
|
3606
|
-
|
|
3607
|
-
if (key.escape) {
|
|
3608
|
-
onClose();
|
|
3609
|
-
return;
|
|
3610
|
-
}
|
|
3611
|
-
if (key.return) {
|
|
3612
|
-
const hit = visible[active];
|
|
3613
|
-
if (hit) onSelect(hit.cmd);
|
|
3614
|
-
return;
|
|
3615
|
-
}
|
|
3616
|
-
if (key.upArrow) {
|
|
3617
|
-
setActiveIndex((i) => Math.max(0, i - 1));
|
|
3618
|
-
return;
|
|
3619
|
-
}
|
|
3620
|
-
if (key.downArrow) {
|
|
3621
|
-
setActiveIndex((i) => i + 1);
|
|
3622
|
-
return;
|
|
3623
|
-
}
|
|
3624
|
-
if (key.backspace || key.delete) {
|
|
3625
|
-
setQuery((q) => q.slice(0, -1));
|
|
3626
|
-
setActiveIndex(0);
|
|
3627
|
-
return;
|
|
3628
|
-
}
|
|
3629
|
-
if (key.ctrl) return;
|
|
3630
|
-
if (input.length > 0) {
|
|
3631
|
-
setQuery((q) => q + input);
|
|
3632
|
-
setActiveIndex(0);
|
|
3633
|
-
}
|
|
3634
|
-
});
|
|
3635
|
-
const rows = [
|
|
3636
|
-
/* @__PURE__ */ jsx8(Box5, { paddingX: 1, children: /* @__PURE__ */ jsxs6(Text7, { children: [
|
|
3774
|
+
function truncate2(label) {
|
|
3775
|
+
const max = Math.max(20, PALETTE_WIDTH - 8);
|
|
3776
|
+
return label.length > max ? `${label.slice(0, max - 1)}\u2026` : label;
|
|
3777
|
+
}
|
|
3778
|
+
function placeholder(corpus) {
|
|
3779
|
+
if (corpus === "commands") return "type to search commands";
|
|
3780
|
+
if (corpus === "output") return "type to filter output";
|
|
3781
|
+
return "keybindings";
|
|
3782
|
+
}
|
|
3783
|
+
function Palette({ corpus, query, active, rows }) {
|
|
3784
|
+
const visible = rows.slice(0, VISIBLE_ROWS);
|
|
3785
|
+
const activeIndex = Math.min(Math.max(active, 0), Math.max(0, visible.length - 1));
|
|
3786
|
+
const elements = [
|
|
3787
|
+
/* @__PURE__ */ jsx7(Box4, { paddingX: 1, children: /* @__PURE__ */ jsxs5(Text6, { children: [
|
|
3637
3788
|
c.bold("\u25B8 palette "),
|
|
3638
|
-
/* @__PURE__ */
|
|
3789
|
+
/* @__PURE__ */ jsx7(Text6, { children: c.dim(`(${corpus} \xB7 ${visible.length} shown)`) })
|
|
3639
3790
|
] }) }, "header"),
|
|
3640
|
-
/* @__PURE__ */
|
|
3791
|
+
/* @__PURE__ */ jsx7(Box4, { paddingX: 1, children: /* @__PURE__ */ jsxs5(Text6, { children: [
|
|
3641
3792
|
c.dim("> "),
|
|
3642
|
-
query,
|
|
3643
|
-
/* @__PURE__ */
|
|
3793
|
+
query.length > 0 ? query : c.dim(placeholder(corpus)),
|
|
3794
|
+
/* @__PURE__ */ jsx7(Text6, { children: c.dim("\u258C") })
|
|
3644
3795
|
] }) }, "query")
|
|
3645
3796
|
];
|
|
3646
|
-
let
|
|
3647
|
-
for (
|
|
3648
|
-
const
|
|
3649
|
-
if (
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
} else if (!isRecent && cmd.category !== lastCategory) {
|
|
3656
|
-
rows.push(
|
|
3657
|
-
/* @__PURE__ */ jsx8(Box5, { paddingX: 1, children: /* @__PURE__ */ jsx8(Text7, { children: c.dim(`\u2500\u2500 ${cmd.category} \u2500\u2500`) }) }, `cat:${cmd.category}`)
|
|
3658
|
-
);
|
|
3659
|
-
lastCategory = cmd.category;
|
|
3660
|
-
}
|
|
3797
|
+
let lastGroup = null;
|
|
3798
|
+
for (let i = 0; i < visible.length; i++) {
|
|
3799
|
+
const row = visible[i];
|
|
3800
|
+
if (!row) continue;
|
|
3801
|
+
if (row.group !== null && row.group !== lastGroup) {
|
|
3802
|
+
elements.push(
|
|
3803
|
+
/* @__PURE__ */ jsx7(Box4, { paddingX: 1, children: /* @__PURE__ */ jsx7(Text6, { children: c.dim(`\u2500\u2500 ${row.group} \u2500\u2500`) }) }, `cat:${row.group}`)
|
|
3804
|
+
);
|
|
3805
|
+
lastGroup = row.group;
|
|
3661
3806
|
}
|
|
3662
|
-
const
|
|
3663
|
-
|
|
3664
|
-
/* @__PURE__ */
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
] }) }, cmd.id)
|
|
3807
|
+
const isActive = i === activeIndex;
|
|
3808
|
+
elements.push(
|
|
3809
|
+
/* @__PURE__ */ jsx7(Box4, { paddingX: 1, children: /* @__PURE__ */ jsxs5(Text6, { children: [
|
|
3810
|
+
isActive ? "\u25B8 " : " ",
|
|
3811
|
+
isActive ? c.accent(truncate2(row.primary)) : highlight(truncate2(row.primary), row.indices),
|
|
3812
|
+
/* @__PURE__ */ jsx7(Text6, { children: c.dim(` ${row.secondary}`) })
|
|
3813
|
+
] }) }, row.key)
|
|
3670
3814
|
);
|
|
3671
3815
|
}
|
|
3672
|
-
return /* @__PURE__ */
|
|
3673
|
-
/* @__PURE__ */
|
|
3674
|
-
/* @__PURE__ */
|
|
3816
|
+
return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", children: [
|
|
3817
|
+
/* @__PURE__ */ jsx7(Panel, { maxWidth: PALETTE_WIDTH, children: elements }),
|
|
3818
|
+
/* @__PURE__ */ jsx7(Text6, { children: c.dim(LIST_NAV_HINT) })
|
|
3675
3819
|
] });
|
|
3676
3820
|
}
|
|
3821
|
+
|
|
3822
|
+
// src/tui/palette/output.ts
|
|
3823
|
+
function computeMatches(lines, query) {
|
|
3824
|
+
if (query.length === 0) return [];
|
|
3825
|
+
const needle = query.toLowerCase();
|
|
3826
|
+
const out = [];
|
|
3827
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3828
|
+
if ((lines[i] ?? "").toLowerCase().includes(needle)) out.push(i);
|
|
3829
|
+
}
|
|
3830
|
+
return out;
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
// src/tui/palette/rows.ts
|
|
3834
|
+
var CORPORA = ["commands", "output", "help"];
|
|
3835
|
+
var MATCH_LIMIT = 20;
|
|
3836
|
+
function flattenHome(sections) {
|
|
3837
|
+
const out = [];
|
|
3838
|
+
for (const section of sections) {
|
|
3839
|
+
for (const action of section.items) out.push({ group: section.label, action });
|
|
3840
|
+
}
|
|
3841
|
+
return out;
|
|
3842
|
+
}
|
|
3677
3843
|
function recentDeduped(recent, commands) {
|
|
3678
3844
|
const known = new Set(commands.map((cmd) => cmd.id));
|
|
3679
3845
|
return recent.filter((cmd) => known.has(cmd.id));
|
|
3680
3846
|
}
|
|
3847
|
+
function buildPaletteRows(input) {
|
|
3848
|
+
const { corpus, query, commands, matcher, recent, homeSections, outputLines } = input;
|
|
3849
|
+
if (corpus === "help") {
|
|
3850
|
+
return HELP_ENTRIES.map((e) => ({
|
|
3851
|
+
key: `help:${e.keys}`,
|
|
3852
|
+
primary: e.keys,
|
|
3853
|
+
secondary: e.desc,
|
|
3854
|
+
argv: null,
|
|
3855
|
+
destructive: false,
|
|
3856
|
+
indices: [],
|
|
3857
|
+
group: "keybindings"
|
|
3858
|
+
}));
|
|
3859
|
+
}
|
|
3860
|
+
if (corpus === "output") {
|
|
3861
|
+
const matches = computeMatches(outputLines, query);
|
|
3862
|
+
if (matches.length === 0) {
|
|
3863
|
+
if (outputLines.length === 0) {
|
|
3864
|
+
return [
|
|
3865
|
+
{
|
|
3866
|
+
key: "empty",
|
|
3867
|
+
primary: "(no output)",
|
|
3868
|
+
secondary: "run a /command first",
|
|
3869
|
+
argv: null,
|
|
3870
|
+
destructive: false,
|
|
3871
|
+
indices: [],
|
|
3872
|
+
group: null
|
|
3873
|
+
}
|
|
3874
|
+
];
|
|
3875
|
+
}
|
|
3876
|
+
return outputLines.map((line, i) => ({
|
|
3877
|
+
key: `out:${i}`,
|
|
3878
|
+
primary: line,
|
|
3879
|
+
secondary: String(i + 1),
|
|
3880
|
+
argv: null,
|
|
3881
|
+
destructive: false,
|
|
3882
|
+
indices: [],
|
|
3883
|
+
group: query.length === 0 ? null : "matches"
|
|
3884
|
+
}));
|
|
3885
|
+
}
|
|
3886
|
+
return matches.map((i) => ({
|
|
3887
|
+
key: `out:${i}`,
|
|
3888
|
+
primary: outputLines[i] ?? "",
|
|
3889
|
+
secondary: String(i + 1),
|
|
3890
|
+
argv: null,
|
|
3891
|
+
destructive: false,
|
|
3892
|
+
indices: [],
|
|
3893
|
+
group: "matches"
|
|
3894
|
+
}));
|
|
3895
|
+
}
|
|
3896
|
+
if (query.length === 0) {
|
|
3897
|
+
const rows = [];
|
|
3898
|
+
const recents = recentDeduped(recent, commands);
|
|
3899
|
+
const recentIds = new Set(recents.map((r) => r.id));
|
|
3900
|
+
for (const cmd of recents) {
|
|
3901
|
+
rows.push({
|
|
3902
|
+
key: `recent:${cmd.id}`,
|
|
3903
|
+
primary: cmd.label,
|
|
3904
|
+
secondary: cmd.description,
|
|
3905
|
+
argv: cmd.argv,
|
|
3906
|
+
destructive: cmd.destructive,
|
|
3907
|
+
indices: [],
|
|
3908
|
+
group: "recent"
|
|
3909
|
+
});
|
|
3910
|
+
}
|
|
3911
|
+
const homeIds = /* @__PURE__ */ new Set();
|
|
3912
|
+
for (const { group, action } of flattenHome(homeSections)) {
|
|
3913
|
+
const argv = action.dispatch ?? [action.id];
|
|
3914
|
+
homeIds.add(action.id);
|
|
3915
|
+
rows.push({
|
|
3916
|
+
key: `home:${action.id}`,
|
|
3917
|
+
primary: action.label,
|
|
3918
|
+
secondary: action.hint,
|
|
3919
|
+
argv,
|
|
3920
|
+
destructive: action.destructive ?? false,
|
|
3921
|
+
indices: [],
|
|
3922
|
+
group
|
|
3923
|
+
});
|
|
3924
|
+
}
|
|
3925
|
+
for (const cmd of commands) {
|
|
3926
|
+
if (recentIds.has(cmd.id) || homeIds.has(cmd.id)) continue;
|
|
3927
|
+
rows.push({
|
|
3928
|
+
key: `cmd:${cmd.id}`,
|
|
3929
|
+
primary: cmd.label,
|
|
3930
|
+
secondary: cmd.description,
|
|
3931
|
+
argv: cmd.argv,
|
|
3932
|
+
destructive: cmd.destructive,
|
|
3933
|
+
indices: [],
|
|
3934
|
+
group: cmd.category
|
|
3935
|
+
});
|
|
3936
|
+
}
|
|
3937
|
+
return rows;
|
|
3938
|
+
}
|
|
3939
|
+
return matcher.search(query, commands, MATCH_LIMIT).map((m) => ({
|
|
3940
|
+
key: `cmd:${m.item.id}`,
|
|
3941
|
+
primary: m.item.label,
|
|
3942
|
+
secondary: m.item.description,
|
|
3943
|
+
argv: m.item.argv,
|
|
3944
|
+
destructive: m.item.destructive,
|
|
3945
|
+
indices: m.matchedIndices,
|
|
3946
|
+
group: null
|
|
3947
|
+
}));
|
|
3948
|
+
}
|
|
3681
3949
|
|
|
3682
3950
|
// src/tui/StatusBar.tsx
|
|
3683
|
-
import { Text as
|
|
3684
|
-
import { jsx as
|
|
3951
|
+
import { Text as Text7 } from "ink";
|
|
3952
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
3685
3953
|
function Cell({
|
|
3686
3954
|
label,
|
|
3687
3955
|
value,
|
|
3688
3956
|
accent
|
|
3689
3957
|
}) {
|
|
3690
|
-
return /* @__PURE__ */
|
|
3691
|
-
/* @__PURE__ */
|
|
3958
|
+
return /* @__PURE__ */ jsxs6(Text7, { children: [
|
|
3959
|
+
/* @__PURE__ */ jsx8(Text7, { children: c.dim(`${label}:`) }),
|
|
3692
3960
|
" ",
|
|
3693
|
-
/* @__PURE__ */
|
|
3961
|
+
/* @__PURE__ */ jsx8(Text7, { children: accent ? c.accent(value) : value })
|
|
3694
3962
|
] });
|
|
3695
3963
|
}
|
|
3696
3964
|
function StatusBar({ payload, loading }) {
|
|
@@ -3699,14 +3967,14 @@ function StatusBar({ payload, loading }) {
|
|
|
3699
3967
|
const phase = payload?.workflow?.phase ?? (loading ? "\u2026" : "idle");
|
|
3700
3968
|
const daemonRunning = payload?.daemon.running === true;
|
|
3701
3969
|
const daemon = payload === null && loading ? "\u2026" : daemonRunning ? "up" : "down";
|
|
3702
|
-
return /* @__PURE__ */
|
|
3703
|
-
/* @__PURE__ */
|
|
3704
|
-
/* @__PURE__ */
|
|
3705
|
-
/* @__PURE__ */
|
|
3706
|
-
/* @__PURE__ */
|
|
3707
|
-
/* @__PURE__ */
|
|
3708
|
-
/* @__PURE__ */
|
|
3709
|
-
/* @__PURE__ */
|
|
3970
|
+
return /* @__PURE__ */ jsxs6(Text7, { children: [
|
|
3971
|
+
/* @__PURE__ */ jsx8(Cell, { label: "host", value: host, accent: true }),
|
|
3972
|
+
/* @__PURE__ */ jsx8(Text7, { children: c.dim(" \xB7 ") }),
|
|
3973
|
+
/* @__PURE__ */ jsx8(Cell, { label: "mode", value: mode }),
|
|
3974
|
+
/* @__PURE__ */ jsx8(Text7, { children: c.dim(" \xB7 ") }),
|
|
3975
|
+
/* @__PURE__ */ jsx8(Cell, { label: "phase", value: phase }),
|
|
3976
|
+
/* @__PURE__ */ jsx8(Text7, { children: c.dim(" \xB7 ") }),
|
|
3977
|
+
/* @__PURE__ */ jsxs6(Text7, { children: [
|
|
3710
3978
|
c.dim("daemon:"),
|
|
3711
3979
|
" ",
|
|
3712
3980
|
daemonRunning ? c.ok(daemon) : c.warn(daemon)
|
|
@@ -3715,7 +3983,7 @@ function StatusBar({ payload, loading }) {
|
|
|
3715
3983
|
}
|
|
3716
3984
|
|
|
3717
3985
|
// src/tui/App.tsx
|
|
3718
|
-
import { jsx as
|
|
3986
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
3719
3987
|
function App({
|
|
3720
3988
|
deps,
|
|
3721
3989
|
initialPayload = null,
|
|
@@ -3723,31 +3991,35 @@ function App({
|
|
|
3723
3991
|
initialMode = { kind: "dashboard" }
|
|
3724
3992
|
}) {
|
|
3725
3993
|
const { exit } = useApp();
|
|
3726
|
-
const { buffer, setBuffer, pushHistory, recall, clear } = useInputBuffer();
|
|
3727
|
-
const [mode, setMode] =
|
|
3728
|
-
const [payload, setPayload] =
|
|
3729
|
-
const [loading, setLoading] =
|
|
3730
|
-
const [output, setOutput] =
|
|
3731
|
-
const [searchLines, setSearchLines] =
|
|
3732
|
-
const [scrollOffset, setScrollOffset] =
|
|
3733
|
-
const [
|
|
3734
|
-
const [
|
|
3735
|
-
const [
|
|
3736
|
-
const [
|
|
3737
|
-
const [
|
|
3738
|
-
const [
|
|
3994
|
+
const { buffer, setBuffer, pushHistory, recall, clear, seed } = useInputBuffer();
|
|
3995
|
+
const [mode, setMode] = useState2(initialMode);
|
|
3996
|
+
const [payload, setPayload] = useState2(initialPayload);
|
|
3997
|
+
const [loading, setLoading] = useState2(initialPayload === null);
|
|
3998
|
+
const [output, setOutput] = useState2(null);
|
|
3999
|
+
const [searchLines, setSearchLines] = useState2([]);
|
|
4000
|
+
const [scrollOffset, setScrollOffset] = useState2(0);
|
|
4001
|
+
const [running, setRunning] = useState2(false);
|
|
4002
|
+
const [notice, setNotice] = useState2(null);
|
|
4003
|
+
const [pending, setPending] = useState2(null);
|
|
4004
|
+
const [recent, setRecent] = useState2([]);
|
|
4005
|
+
const [homeSections, setHomeSections] = useState2([]);
|
|
4006
|
+
const [paletteQuery, setPaletteQuery] = useState2("");
|
|
4007
|
+
const [paletteActive, setPaletteActive] = useState2(0);
|
|
3739
4008
|
useEffect(() => {
|
|
3740
4009
|
if (!deps.loadRecent) return;
|
|
3741
4010
|
const loader = deps.loadRecent;
|
|
3742
4011
|
let cancelled = false;
|
|
3743
4012
|
void loader().then((entries) => {
|
|
3744
|
-
if (!cancelled)
|
|
4013
|
+
if (!cancelled) {
|
|
4014
|
+
setRecent(entries);
|
|
4015
|
+
seed(entries.map((c2) => `/${c2.argv.join(" ")}`));
|
|
4016
|
+
}
|
|
3745
4017
|
}).catch(() => {
|
|
3746
4018
|
});
|
|
3747
4019
|
return () => {
|
|
3748
4020
|
cancelled = true;
|
|
3749
4021
|
};
|
|
3750
|
-
}, [deps.loadRecent]);
|
|
4022
|
+
}, [deps.loadRecent, seed]);
|
|
3751
4023
|
useEffect(() => {
|
|
3752
4024
|
let cancelled = false;
|
|
3753
4025
|
void resolveSections(deps.commands ?? []).then((sections) => {
|
|
@@ -3787,7 +4059,7 @@ function App({
|
|
|
3787
4059
|
if (pending === null) return;
|
|
3788
4060
|
const argv = pending;
|
|
3789
4061
|
let active = true;
|
|
3790
|
-
const
|
|
4062
|
+
const run3 = async () => {
|
|
3791
4063
|
const captured = await captureProcessOutput(() => deps.dispatch(argv));
|
|
3792
4064
|
if (!active) return;
|
|
3793
4065
|
const combined = `${captured.stdout}${captured.stderr}`;
|
|
@@ -3800,7 +4072,7 @@ function App({
|
|
|
3800
4072
|
setPending(null);
|
|
3801
4073
|
setScrollOffset(0);
|
|
3802
4074
|
};
|
|
3803
|
-
void
|
|
4075
|
+
void run3();
|
|
3804
4076
|
return () => {
|
|
3805
4077
|
active = false;
|
|
3806
4078
|
};
|
|
@@ -3810,13 +4082,43 @@ function App({
|
|
|
3810
4082
|
setRunning(true);
|
|
3811
4083
|
setPending([...argv]);
|
|
3812
4084
|
}
|
|
4085
|
+
function handleRun(argv, destructive) {
|
|
4086
|
+
if (destructive) {
|
|
4087
|
+
setMode({ kind: "confirm", argv: [...argv] });
|
|
4088
|
+
return;
|
|
4089
|
+
}
|
|
4090
|
+
dispatchCmd([...argv]);
|
|
4091
|
+
void recordRuns([...argv]);
|
|
4092
|
+
pushHistory(`/${argv.join(" ")}`);
|
|
4093
|
+
setMode({ kind: "dashboard" });
|
|
4094
|
+
}
|
|
4095
|
+
function recordRuns(argv) {
|
|
4096
|
+
const recorder = deps.record;
|
|
4097
|
+
if (recorder) void recorder(argv);
|
|
4098
|
+
}
|
|
4099
|
+
function openPalette(corpus) {
|
|
4100
|
+
setPaletteQuery("");
|
|
4101
|
+
setPaletteActive(0);
|
|
4102
|
+
setMode({ kind: "palette", corpus });
|
|
4103
|
+
setNotice(null);
|
|
4104
|
+
}
|
|
4105
|
+
function openOutputSearch() {
|
|
4106
|
+
if (output === null) return;
|
|
4107
|
+
setSearchLines(output.lines);
|
|
4108
|
+
openPalette("output");
|
|
4109
|
+
}
|
|
4110
|
+
function nextCorpus(current) {
|
|
4111
|
+
const idx = CORPORA.indexOf(current);
|
|
4112
|
+
const candidates = [...CORPORA.slice(idx + 1), ...CORPORA.slice(0, idx + 1)];
|
|
4113
|
+
for (const corpus of candidates) {
|
|
4114
|
+
if (corpus === "output" && output === null) continue;
|
|
4115
|
+
return corpus;
|
|
4116
|
+
}
|
|
4117
|
+
return "commands";
|
|
4118
|
+
}
|
|
3813
4119
|
function handleDashboardInput(input, key) {
|
|
3814
4120
|
if (running) return;
|
|
3815
4121
|
setNotice(null);
|
|
3816
|
-
if (help) {
|
|
3817
|
-
if (input === "?" || key.escape || input === "q") setHelp(false);
|
|
3818
|
-
return;
|
|
3819
|
-
}
|
|
3820
4122
|
if (key.return) {
|
|
3821
4123
|
submit();
|
|
3822
4124
|
return;
|
|
@@ -3855,11 +4157,11 @@ function App({
|
|
|
3855
4157
|
}
|
|
3856
4158
|
if (key.ctrl) {
|
|
3857
4159
|
if (input === "k") {
|
|
3858
|
-
openPalette();
|
|
4160
|
+
openPalette("commands");
|
|
3859
4161
|
return;
|
|
3860
4162
|
}
|
|
3861
4163
|
if (input === "f" && output !== null) {
|
|
3862
|
-
|
|
4164
|
+
openOutputSearch();
|
|
3863
4165
|
return;
|
|
3864
4166
|
}
|
|
3865
4167
|
return;
|
|
@@ -3871,11 +4173,11 @@ function App({
|
|
|
3871
4173
|
return;
|
|
3872
4174
|
}
|
|
3873
4175
|
if (input === "?") {
|
|
3874
|
-
|
|
4176
|
+
openPalette("help");
|
|
3875
4177
|
return;
|
|
3876
4178
|
}
|
|
3877
4179
|
if (input === "h") {
|
|
3878
|
-
|
|
4180
|
+
openPalette("commands");
|
|
3879
4181
|
return;
|
|
3880
4182
|
}
|
|
3881
4183
|
}
|
|
@@ -3886,114 +4188,77 @@ function App({
|
|
|
3886
4188
|
setBuffer((b) => b + input);
|
|
3887
4189
|
}
|
|
3888
4190
|
}
|
|
3889
|
-
function
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
function openSearch() {
|
|
3894
|
-
if (output === null) return;
|
|
3895
|
-
setSearchLines(output.lines);
|
|
3896
|
-
setMode({ kind: "search", query: "", matches: [], active: -1 });
|
|
3897
|
-
setNotice(null);
|
|
3898
|
-
}
|
|
3899
|
-
function handlePaletteSelect(cmd) {
|
|
3900
|
-
if (cmd.destructive) {
|
|
3901
|
-
setMode({ kind: "confirm", argv: [...cmd.argv] });
|
|
4191
|
+
function handlePaletteInput(input, key) {
|
|
4192
|
+
if (mode.kind !== "palette") return;
|
|
4193
|
+
if (key.escape) {
|
|
4194
|
+
setMode({ kind: "dashboard" });
|
|
3902
4195
|
return;
|
|
3903
4196
|
}
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
function handleHomeSelect(action) {
|
|
3909
|
-
const argv = action.dispatch ?? [action.id];
|
|
3910
|
-
if (action.destructive) {
|
|
3911
|
-
setMode({ kind: "confirm", argv: [...argv] });
|
|
4197
|
+
if (key.tab) {
|
|
4198
|
+
setMode({ kind: "palette", corpus: nextCorpus(mode.corpus) });
|
|
4199
|
+
setPaletteQuery("");
|
|
4200
|
+
setPaletteActive(0);
|
|
3912
4201
|
return;
|
|
3913
4202
|
}
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
}
|
|
3918
|
-
function handleConfirmApprove() {
|
|
3919
|
-
if (mode.kind !== "confirm") return;
|
|
3920
|
-
dispatchCmd([...mode.argv]);
|
|
3921
|
-
void recordRuns([...mode.argv]);
|
|
3922
|
-
setMode({ kind: "dashboard" });
|
|
3923
|
-
}
|
|
3924
|
-
function handleConfirmDecline() {
|
|
3925
|
-
setMode({ kind: "palette" });
|
|
3926
|
-
}
|
|
3927
|
-
function recordRuns(argv) {
|
|
3928
|
-
const recorder = deps.record;
|
|
3929
|
-
if (recorder) void recorder(argv);
|
|
3930
|
-
}
|
|
3931
|
-
function handleConfirmInput(input, key) {
|
|
3932
|
-
if (key.escape || input === "n" || input === "N") {
|
|
3933
|
-
handleConfirmDecline();
|
|
4203
|
+
if (mode.corpus === "help") {
|
|
4204
|
+
if (key.upArrow) setPaletteActive((a) => Math.max(0, a - 1));
|
|
4205
|
+
else if (key.downArrow) setPaletteActive((a) => a + 1);
|
|
3934
4206
|
return;
|
|
3935
4207
|
}
|
|
3936
|
-
if (
|
|
3937
|
-
|
|
4208
|
+
if (key.return) {
|
|
4209
|
+
if (mode.corpus === "commands") {
|
|
4210
|
+
const row = paletteRows[paletteActive];
|
|
4211
|
+
if (row?.argv) handleRun(row.argv, row.destructive);
|
|
4212
|
+
} else {
|
|
4213
|
+
setPaletteActive((a) => a + 1);
|
|
4214
|
+
}
|
|
3938
4215
|
return;
|
|
3939
4216
|
}
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
if (mode.kind !== "search") return;
|
|
3943
|
-
if (key.escape) {
|
|
3944
|
-
setMode({ kind: "dashboard" });
|
|
4217
|
+
if (key.upArrow) {
|
|
4218
|
+
setPaletteActive((a) => Math.max(0, a - 1));
|
|
3945
4219
|
return;
|
|
3946
4220
|
}
|
|
3947
|
-
if (key.
|
|
3948
|
-
|
|
3949
|
-
setMode(searchFor(nextQuery));
|
|
4221
|
+
if (key.downArrow) {
|
|
4222
|
+
setPaletteActive((a) => a + 1);
|
|
3950
4223
|
return;
|
|
3951
4224
|
}
|
|
3952
|
-
if (key.
|
|
3953
|
-
|
|
3954
|
-
|
|
4225
|
+
if (key.backspace || key.delete) {
|
|
4226
|
+
setPaletteQuery((q) => q.slice(0, -1));
|
|
4227
|
+
setPaletteActive(0);
|
|
3955
4228
|
return;
|
|
3956
4229
|
}
|
|
3957
|
-
if (
|
|
3958
|
-
|
|
3959
|
-
|
|
4230
|
+
if (key.ctrl) return;
|
|
4231
|
+
if (input.length > 0) {
|
|
4232
|
+
setPaletteQuery((q) => q + input);
|
|
4233
|
+
setPaletteActive(0);
|
|
3960
4234
|
}
|
|
3961
|
-
|
|
3962
|
-
|
|
4235
|
+
}
|
|
4236
|
+
function handleConfirmInput(input, key) {
|
|
4237
|
+
if (key.escape || input === "n" || input === "N") {
|
|
4238
|
+
setPaletteQuery("");
|
|
4239
|
+
setPaletteActive(0);
|
|
4240
|
+
setMode({ kind: "palette", corpus: "commands" });
|
|
3963
4241
|
return;
|
|
3964
4242
|
}
|
|
3965
|
-
if (input
|
|
3966
|
-
|
|
4243
|
+
if (input === "y" || input === "Y") {
|
|
4244
|
+
if (mode.kind !== "confirm") return;
|
|
4245
|
+
dispatchCmd([...mode.argv]);
|
|
4246
|
+
void recordRuns([...mode.argv]);
|
|
4247
|
+
pushHistory(`/${mode.argv.join(" ")}`);
|
|
4248
|
+
setMode({ kind: "dashboard" });
|
|
3967
4249
|
}
|
|
3968
4250
|
}
|
|
3969
|
-
|
|
3970
|
-
const matches = computeMatches(searchLines, query);
|
|
3971
|
-
return { kind: "search", query, matches, active: matches.length > 0 ? 0 : -1 };
|
|
3972
|
-
}
|
|
3973
|
-
function advanceActive(delta) {
|
|
3974
|
-
if (mode.kind !== "search") return;
|
|
3975
|
-
const n = mode.matches.length;
|
|
3976
|
-
if (n === 0) return;
|
|
3977
|
-
const next = (mode.active + delta + n) % n;
|
|
3978
|
-
setMode({ ...mode, active: next });
|
|
3979
|
-
}
|
|
3980
|
-
useInput3((input, key) => {
|
|
4251
|
+
useInput((input, key) => {
|
|
3981
4252
|
switch (mode.kind) {
|
|
3982
4253
|
case "dashboard":
|
|
3983
4254
|
handleDashboardInput(input, key);
|
|
3984
4255
|
return;
|
|
3985
4256
|
case "palette":
|
|
4257
|
+
handlePaletteInput(input, key);
|
|
3986
4258
|
return;
|
|
3987
|
-
// Palette renders its own useInput; no App-level keys here
|
|
3988
|
-
case "home":
|
|
3989
|
-
return;
|
|
3990
|
-
// HomeMenu renders its own useInput (S4)
|
|
3991
4259
|
case "confirm":
|
|
3992
4260
|
handleConfirmInput(input, key);
|
|
3993
4261
|
return;
|
|
3994
|
-
case "search":
|
|
3995
|
-
handleSearchInput(input, key);
|
|
3996
|
-
return;
|
|
3997
4262
|
}
|
|
3998
4263
|
});
|
|
3999
4264
|
function submit() {
|
|
@@ -4009,140 +4274,75 @@ function App({
|
|
|
4009
4274
|
setNotice("empty /command");
|
|
4010
4275
|
return;
|
|
4011
4276
|
}
|
|
4277
|
+
if (isDestructive(argv)) {
|
|
4278
|
+
setMode({ kind: "confirm", argv });
|
|
4279
|
+
return;
|
|
4280
|
+
}
|
|
4012
4281
|
pushHistory(text);
|
|
4013
4282
|
dispatchCmd(argv);
|
|
4014
4283
|
void recordRuns(argv);
|
|
4015
4284
|
}
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4285
|
+
const paletteCorpus = mode.kind === "palette" ? mode.corpus : "commands";
|
|
4286
|
+
const paletteRows = buildPaletteRows({
|
|
4287
|
+
corpus: paletteCorpus,
|
|
4288
|
+
query: paletteQuery,
|
|
4289
|
+
commands: deps.commands ?? [],
|
|
4290
|
+
matcher: deps.matcher ?? handRolledMatcher,
|
|
4291
|
+
recent,
|
|
4292
|
+
homeSections,
|
|
4293
|
+
outputLines: searchLines
|
|
4294
|
+
});
|
|
4019
4295
|
if (mode.kind === "palette") {
|
|
4020
|
-
return /* @__PURE__ */
|
|
4021
|
-
/* @__PURE__ */
|
|
4022
|
-
/* @__PURE__ */
|
|
4296
|
+
return /* @__PURE__ */ jsxs7(Box5, { flexDirection: "column", children: [
|
|
4297
|
+
/* @__PURE__ */ jsx9(Header, { tagline: `palette \xB7 ${mode.corpus}` }),
|
|
4298
|
+
/* @__PURE__ */ jsx9(
|
|
4023
4299
|
Palette,
|
|
4024
4300
|
{
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
onClose: () => setMode({ kind: "dashboard" })
|
|
4030
|
-
}
|
|
4031
|
-
),
|
|
4032
|
-
/* @__PURE__ */ jsx10(Footer, { running: false })
|
|
4033
|
-
] });
|
|
4034
|
-
}
|
|
4035
|
-
if (mode.kind === "home") {
|
|
4036
|
-
return /* @__PURE__ */ jsxs8(Box6, { flexDirection: "column", children: [
|
|
4037
|
-
/* @__PURE__ */ jsx10(Header, { tagline: "home" }),
|
|
4038
|
-
/* @__PURE__ */ jsx10(
|
|
4039
|
-
HomeMenu,
|
|
4040
|
-
{
|
|
4041
|
-
sections: homeSections,
|
|
4042
|
-
onSelect: handleHomeSelect,
|
|
4043
|
-
onClose: () => setMode({ kind: "dashboard" })
|
|
4301
|
+
corpus: mode.corpus,
|
|
4302
|
+
query: paletteQuery,
|
|
4303
|
+
active: paletteActive,
|
|
4304
|
+
rows: paletteRows
|
|
4044
4305
|
}
|
|
4045
4306
|
),
|
|
4046
|
-
/* @__PURE__ */
|
|
4307
|
+
/* @__PURE__ */ jsx9(Footer, { running: false })
|
|
4047
4308
|
] });
|
|
4048
4309
|
}
|
|
4049
4310
|
if (mode.kind === "confirm") {
|
|
4050
|
-
return /* @__PURE__ */
|
|
4051
|
-
/* @__PURE__ */
|
|
4052
|
-
/* @__PURE__ */
|
|
4053
|
-
/* @__PURE__ */
|
|
4054
|
-
] });
|
|
4055
|
-
}
|
|
4056
|
-
if (mode.kind === "search") {
|
|
4057
|
-
const activeLine = mode.active >= 0 ? mode.matches[mode.active] : void 0;
|
|
4058
|
-
const matchCount = mode.matches.length;
|
|
4059
|
-
return /* @__PURE__ */ jsxs8(Box6, { flexDirection: "column", children: [
|
|
4060
|
-
/* @__PURE__ */ jsx10(Header, { tagline: "search" }),
|
|
4061
|
-
/* @__PURE__ */ jsx10(Panel, { children: /* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(
|
|
4062
|
-
OutputPane,
|
|
4063
|
-
{
|
|
4064
|
-
lines: searchLines,
|
|
4065
|
-
scrollOffset,
|
|
4066
|
-
title: output?.title,
|
|
4067
|
-
highlightQuery: mode.query,
|
|
4068
|
-
activeLine
|
|
4069
|
-
}
|
|
4070
|
-
) }) }),
|
|
4071
|
-
/* @__PURE__ */ jsx10(Box6, { borderStyle: "round", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
4072
|
-
c.dim("search: "),
|
|
4073
|
-
mode.query.length > 0 ? mode.query : c.dim("(type to filter output)"),
|
|
4074
|
-
/* @__PURE__ */ jsx10(Text9, { children: c.dim(" \u258C") })
|
|
4075
|
-
] }) }),
|
|
4076
|
-
/* @__PURE__ */ jsx10(Text9, { children: c.dim(
|
|
4077
|
-
matchCount === 0 ? "no matches \xB7 Esc exit" : `${mode.active + 1}/${matchCount} matches \xB7 n/Enter next \xB7 N prev \xB7 Esc exit`
|
|
4078
|
-
) }),
|
|
4079
|
-
/* @__PURE__ */ jsx10(Footer, { running: false })
|
|
4311
|
+
return /* @__PURE__ */ jsxs7(Box5, { flexDirection: "column", children: [
|
|
4312
|
+
/* @__PURE__ */ jsx9(Header, { tagline: "confirm" }),
|
|
4313
|
+
/* @__PURE__ */ jsx9(ConfirmOverlay, { argv: mode.argv }),
|
|
4314
|
+
/* @__PURE__ */ jsx9(Footer, { running: false })
|
|
4080
4315
|
] });
|
|
4081
4316
|
}
|
|
4082
4317
|
const snapshotLines = formatStatusPayload(payload);
|
|
4083
4318
|
const paneLines = output?.lines ?? snapshotLines ?? [];
|
|
4084
4319
|
const paneTitle = output?.title ?? "snapshot";
|
|
4085
|
-
return /* @__PURE__ */
|
|
4086
|
-
/* @__PURE__ */
|
|
4087
|
-
/* @__PURE__ */
|
|
4088
|
-
/* @__PURE__ */
|
|
4089
|
-
/* @__PURE__ */
|
|
4090
|
-
/* @__PURE__ */
|
|
4320
|
+
return /* @__PURE__ */ jsxs7(Box5, { flexDirection: "column", children: [
|
|
4321
|
+
/* @__PURE__ */ jsx9(Header, { tagline: "dashboard" }),
|
|
4322
|
+
/* @__PURE__ */ jsxs7(Panel, { children: [
|
|
4323
|
+
/* @__PURE__ */ jsx9(Box5, { paddingX: 1, children: /* @__PURE__ */ jsx9(StatusBar, { payload, loading }) }),
|
|
4324
|
+
/* @__PURE__ */ jsx9(Box5, { paddingX: 1, children: /* @__PURE__ */ jsx9(Text8, { children: divider() }) }),
|
|
4325
|
+
/* @__PURE__ */ jsx9(Box5, { flexDirection: "column", paddingX: 1, children: /* @__PURE__ */ jsx9(OutputPane, { lines: paneLines, scrollOffset, title: paneTitle }) })
|
|
4091
4326
|
] }),
|
|
4092
|
-
/* @__PURE__ */
|
|
4093
|
-
notice !== null ? /* @__PURE__ */
|
|
4094
|
-
/* @__PURE__ */
|
|
4095
|
-
] });
|
|
4096
|
-
}
|
|
4097
|
-
function Help() {
|
|
4098
|
-
return /* @__PURE__ */ jsxs8(Box6, { flexDirection: "column", children: [
|
|
4099
|
-
/* @__PURE__ */ jsx10(Header, { tagline: "help" }),
|
|
4100
|
-
/* @__PURE__ */ jsxs8(Panel, { children: [
|
|
4101
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.bold("Keybindings") }) }),
|
|
4102
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.dim(" /<command> run a Noir sub-command (e.g. /status, /sync, /task next)") }) }),
|
|
4103
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.dim(" Enter run the typed /command") }) }),
|
|
4104
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.dim(" Esc back: clear input \u2192 dismiss output \u2192 quit") }) }),
|
|
4105
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.dim(" q quit (when the input is empty)") }) }),
|
|
4106
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.dim(" h open the curated home quick-actions (home menu)") }) }),
|
|
4107
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.dim(" \u2191 / \u2193 scroll the output pane") }) }),
|
|
4108
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.dim(" ? toggle this help") }) }),
|
|
4109
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.dim(" Ctrl+K open the command palette") }) }),
|
|
4110
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.dim(" Ctrl+F find in the dispatched output pane") }) }),
|
|
4111
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.dim(" n / N next / previous match in search (Enter = next)") }) }),
|
|
4112
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.dim(" y / n approve / decline a destructive command prompt") }) }),
|
|
4113
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.dim(" Ctrl+C force exit") }) }),
|
|
4114
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: divider() }) }),
|
|
4115
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.bold("Home menu (h)") }) }),
|
|
4116
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.dim(" The curated quick actions mirror the bare-`noir` home menu:") }) }),
|
|
4117
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.dim(
|
|
4118
|
-
" Status & context \xB7 Memory \xB7 Workflow \xB7 Setup & maintenance \xB7 Dashboard"
|
|
4119
|
-
) }) }),
|
|
4120
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: divider() }) }),
|
|
4121
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.bold("Commands") }) }),
|
|
4122
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.dim(" Dispatched through the same routing as `noir` at the prompt.") }) }),
|
|
4123
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.dim(
|
|
4124
|
-
" /status /sync /doctor /context search <q> /task next /memory recall <q>"
|
|
4125
|
-
) }) }),
|
|
4126
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.dim(" Commands that need their own interactive prompts (e.g. a /sync with a") }) }),
|
|
4127
|
-
/* @__PURE__ */ jsx10(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text9, { children: c.dim(" conflict) are best run directly \u2014 exit first with q.") }) })
|
|
4128
|
-
] }),
|
|
4129
|
-
/* @__PURE__ */ jsx10(Text9, { children: c.dim("press ? / Esc / q to close this help") })
|
|
4327
|
+
/* @__PURE__ */ jsx9(CommandInput, { buffer, running }),
|
|
4328
|
+
notice !== null ? /* @__PURE__ */ jsx9(Text8, { children: c.dim(notice) }) : null,
|
|
4329
|
+
/* @__PURE__ */ jsx9(Footer, { running })
|
|
4130
4330
|
] });
|
|
4131
4331
|
}
|
|
4132
4332
|
|
|
4133
4333
|
// src/tui/palette/history.ts
|
|
4134
|
-
import { existsSync, mkdirSync as
|
|
4334
|
+
import { existsSync, mkdirSync as mkdirSync3, readFileSync } from "fs";
|
|
4135
4335
|
import { homedir } from "os";
|
|
4136
|
-
import { join as
|
|
4336
|
+
import { join as join3 } from "path";
|
|
4137
4337
|
import { atomicWriteFile } from "@noir-ai/core";
|
|
4138
4338
|
var MAX_ENTRIES = 50;
|
|
4139
4339
|
var TUI_HISTORY_DISABLE_ENV = "NOIR_DISABLE_TUI_HISTORY";
|
|
4140
4340
|
var testHome = null;
|
|
4141
4341
|
function homeDir() {
|
|
4142
|
-
return testHome ??
|
|
4342
|
+
return testHome ?? join3(homedir(), ".noir");
|
|
4143
4343
|
}
|
|
4144
4344
|
function historyFile(projectId) {
|
|
4145
|
-
return
|
|
4345
|
+
return join3(homeDir(), projectId, "tui-history.json");
|
|
4146
4346
|
}
|
|
4147
4347
|
function isHistoryEnabled() {
|
|
4148
4348
|
const v = process.env[TUI_HISTORY_DISABLE_ENV];
|
|
@@ -4177,8 +4377,8 @@ function recordRecent(projectId, argv) {
|
|
|
4177
4377
|
const current = loadRecent(projectId).filter((e) => e.id !== entry.id);
|
|
4178
4378
|
const next = [entry, ...current].slice(0, MAX_ENTRIES);
|
|
4179
4379
|
try {
|
|
4180
|
-
const dir =
|
|
4181
|
-
|
|
4380
|
+
const dir = join3(homeDir(), projectId);
|
|
4381
|
+
mkdirSync3(dir, { recursive: true });
|
|
4182
4382
|
atomicWriteFile(historyFile(projectId), `${JSON.stringify(next, null, 2)}
|
|
4183
4383
|
`);
|
|
4184
4384
|
} catch {
|
|
@@ -4186,7 +4386,7 @@ function recordRecent(projectId, argv) {
|
|
|
4186
4386
|
}
|
|
4187
4387
|
|
|
4188
4388
|
// src/tui/index.tsx
|
|
4189
|
-
import { jsx as
|
|
4389
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
4190
4390
|
function defaultFetchStatus(opts) {
|
|
4191
4391
|
return async () => {
|
|
4192
4392
|
try {
|
|
@@ -4199,13 +4399,15 @@ function defaultFetchStatus(opts) {
|
|
|
4199
4399
|
async function runTui(opts, dispatch) {
|
|
4200
4400
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
4201
4401
|
const deps = await buildTuiDeps(opts, dispatch);
|
|
4202
|
-
const instance = render(/* @__PURE__ */
|
|
4402
|
+
const instance = render(/* @__PURE__ */ jsx10(App, { deps }));
|
|
4203
4403
|
await instance.waitUntilExit();
|
|
4204
4404
|
}
|
|
4205
4405
|
async function runPalette(opts, dispatch) {
|
|
4206
4406
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
4207
4407
|
const deps = await buildTuiDeps(opts, dispatch);
|
|
4208
|
-
const instance = render(
|
|
4408
|
+
const instance = render(
|
|
4409
|
+
/* @__PURE__ */ jsx10(App, { deps, initialMode: { kind: "palette", corpus: "commands" } })
|
|
4410
|
+
);
|
|
4209
4411
|
await instance.waitUntilExit();
|
|
4210
4412
|
}
|
|
4211
4413
|
async function buildTuiDeps(opts, dispatch) {
|