steward-cli 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +534 -15
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -41285,7 +41285,7 @@ var require_dist = __commonJS(function(exports) {
|
|
|
41285
41285
|
});
|
|
41286
41286
|
|
|
41287
41287
|
// src/app/app.ts
|
|
41288
|
-
import { existsSync as
|
|
41288
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
41289
41289
|
|
|
41290
41290
|
// src/packages/tui/src/engine/HistoryStore.ts
|
|
41291
41291
|
class HistoryStore {
|
|
@@ -56001,7 +56001,7 @@ import { isIP } from "node:net";
|
|
|
56001
56001
|
// package.json
|
|
56002
56002
|
var package_default = {
|
|
56003
56003
|
name: "steward-cli",
|
|
56004
|
-
version: "0.
|
|
56004
|
+
version: "0.27.0",
|
|
56005
56005
|
description: "Interactive AI engineering assistant for the terminal.",
|
|
56006
56006
|
author: "sapirrior",
|
|
56007
56007
|
license: "MIT",
|
|
@@ -59485,6 +59485,520 @@ var clearCommand = {
|
|
|
59485
59485
|
}
|
|
59486
59486
|
};
|
|
59487
59487
|
|
|
59488
|
+
// src/app/commands/copy/index.ts
|
|
59489
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
59490
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
59491
|
+
function extractAssistantText(msg) {
|
|
59492
|
+
if (msg.role !== "assistant")
|
|
59493
|
+
return "";
|
|
59494
|
+
if (typeof msg.content === "string")
|
|
59495
|
+
return msg.content.trim();
|
|
59496
|
+
if (!Array.isArray(msg.content))
|
|
59497
|
+
return "";
|
|
59498
|
+
return msg.content.filter((b) => b?.type === "text" && typeof b.text === "string").map((b) => b.text.trim()).filter(Boolean).join(`
|
|
59499
|
+
|
|
59500
|
+
`);
|
|
59501
|
+
}
|
|
59502
|
+
async function copyToClipboard(text) {
|
|
59503
|
+
const isTermux = Boolean(process.env.TERMUX_VERSION || existsSync17("/data/data/com.termux/files/usr/bin/termux-clipboard-set"));
|
|
59504
|
+
const [cmd, ...args] = isTermux ? ["termux-clipboard-set"] : process.platform === "darwin" ? ["pbcopy"] : process.platform === "win32" ? ["clip.exe"] : process.env.WAYLAND_DISPLAY ? ["wl-copy"] : ["xclip", "-selection", "clipboard"];
|
|
59505
|
+
const sendOSC52 = () => {
|
|
59506
|
+
try {
|
|
59507
|
+
process.stdout.write(`\x1B]52;c;${Buffer.from(text, "utf8").toString("base64")}\x07`);
|
|
59508
|
+
return true;
|
|
59509
|
+
} catch {
|
|
59510
|
+
return false;
|
|
59511
|
+
}
|
|
59512
|
+
};
|
|
59513
|
+
return new Promise((resolve) => {
|
|
59514
|
+
let resolved = false;
|
|
59515
|
+
const finish = (ok) => {
|
|
59516
|
+
if (resolved)
|
|
59517
|
+
return;
|
|
59518
|
+
resolved = true;
|
|
59519
|
+
clearTimeout(timer);
|
|
59520
|
+
resolve(ok || sendOSC52());
|
|
59521
|
+
};
|
|
59522
|
+
const timer = setTimeout(() => finish(false), 1200);
|
|
59523
|
+
try {
|
|
59524
|
+
const proc = spawn3(cmd, args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
59525
|
+
proc.on("error", () => finish(false));
|
|
59526
|
+
proc.on("close", (code) => finish(code === 0));
|
|
59527
|
+
proc.stdin?.on("error", () => finish(false));
|
|
59528
|
+
proc.stdin?.end(text);
|
|
59529
|
+
} catch {
|
|
59530
|
+
finish(false);
|
|
59531
|
+
}
|
|
59532
|
+
});
|
|
59533
|
+
}
|
|
59534
|
+
var copyCommand = {
|
|
59535
|
+
name: "copy",
|
|
59536
|
+
description: "Copies the last AI response to the clipboard",
|
|
59537
|
+
usage: "/copy",
|
|
59538
|
+
async execute(_args, context) {
|
|
59539
|
+
if (context.session.isBusy) {
|
|
59540
|
+
return { handled: true, message: "Cannot copy while the agent is generating a response." };
|
|
59541
|
+
}
|
|
59542
|
+
const history = context.session.getHistory();
|
|
59543
|
+
let targetText = "";
|
|
59544
|
+
for (let i = history.length - 1;i >= 0; i--) {
|
|
59545
|
+
const text = extractAssistantText(history[i]);
|
|
59546
|
+
if (text) {
|
|
59547
|
+
targetText = text;
|
|
59548
|
+
break;
|
|
59549
|
+
}
|
|
59550
|
+
}
|
|
59551
|
+
if (!targetText) {
|
|
59552
|
+
return { handled: true, message: "No previous AI response found to copy." };
|
|
59553
|
+
}
|
|
59554
|
+
await copyToClipboard(targetText);
|
|
59555
|
+
return { handled: true, message: "Copied last AI response to clipboard." };
|
|
59556
|
+
}
|
|
59557
|
+
};
|
|
59558
|
+
|
|
59559
|
+
// src/app/commands/export/index.ts
|
|
59560
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
59561
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync10, writeFileSync as writeFileSync4 } from "node:fs";
|
|
59562
|
+
import { dirname as dirname7, isAbsolute as isAbsolute8, resolve as resolve11 } from "node:path";
|
|
59563
|
+
function stripAnsi2(str) {
|
|
59564
|
+
return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
|
|
59565
|
+
}
|
|
59566
|
+
function stripLogoFromLine(str) {
|
|
59567
|
+
return str.replace(/^[ ▄▀▙█▟▝▘]{6,8}\s*/, "");
|
|
59568
|
+
}
|
|
59569
|
+
async function copyToClipboard2(text) {
|
|
59570
|
+
const isTermux = Boolean(process.env.TERMUX_VERSION || existsSync18("/data/data/com.termux/files/usr/bin/termux-clipboard-set"));
|
|
59571
|
+
const [cmd, ...args] = isTermux ? ["termux-clipboard-set"] : process.platform === "darwin" ? ["pbcopy"] : process.platform === "win32" ? ["clip.exe"] : process.env.WAYLAND_DISPLAY ? ["wl-copy"] : ["xclip", "-selection", "clipboard"];
|
|
59572
|
+
const sendOSC52 = () => {
|
|
59573
|
+
try {
|
|
59574
|
+
process.stdout.write(`\x1B]52;c;${Buffer.from(text, "utf8").toString("base64")}\x07`);
|
|
59575
|
+
return true;
|
|
59576
|
+
} catch {
|
|
59577
|
+
return false;
|
|
59578
|
+
}
|
|
59579
|
+
};
|
|
59580
|
+
return new Promise((resolvePromise) => {
|
|
59581
|
+
let resolved = false;
|
|
59582
|
+
const finish = (ok) => {
|
|
59583
|
+
if (resolved)
|
|
59584
|
+
return;
|
|
59585
|
+
resolved = true;
|
|
59586
|
+
clearTimeout(timer);
|
|
59587
|
+
resolvePromise(ok || sendOSC52());
|
|
59588
|
+
};
|
|
59589
|
+
const timer = setTimeout(() => finish(false), 1200);
|
|
59590
|
+
try {
|
|
59591
|
+
const proc = spawn4(cmd, args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
59592
|
+
proc.on("error", () => finish(false));
|
|
59593
|
+
proc.on("close", (code) => finish(code === 0));
|
|
59594
|
+
proc.stdin?.on("error", () => finish(false));
|
|
59595
|
+
proc.stdin?.end(text);
|
|
59596
|
+
} catch {
|
|
59597
|
+
finish(false);
|
|
59598
|
+
}
|
|
59599
|
+
});
|
|
59600
|
+
}
|
|
59601
|
+
function formatToolArgs(args) {
|
|
59602
|
+
if (!args || typeof args !== "object")
|
|
59603
|
+
return "";
|
|
59604
|
+
if (args.command)
|
|
59605
|
+
return String(args.command);
|
|
59606
|
+
if (args.path || args.filePath || args.targetFile) {
|
|
59607
|
+
return String(args.path || args.filePath || args.targetFile);
|
|
59608
|
+
}
|
|
59609
|
+
if (args.pattern)
|
|
59610
|
+
return String(args.pattern);
|
|
59611
|
+
if (args.url)
|
|
59612
|
+
return String(args.url);
|
|
59613
|
+
if (args.query)
|
|
59614
|
+
return String(args.query);
|
|
59615
|
+
const firstVal = Object.values(args)[0];
|
|
59616
|
+
if (typeof firstVal === "string")
|
|
59617
|
+
return firstVal;
|
|
59618
|
+
return "";
|
|
59619
|
+
}
|
|
59620
|
+
function formatToolOutput(output) {
|
|
59621
|
+
if (output === undefined || output === null)
|
|
59622
|
+
return [];
|
|
59623
|
+
let text = "";
|
|
59624
|
+
if (typeof output === "string") {
|
|
59625
|
+
text = output;
|
|
59626
|
+
} else if (typeof output === "object") {
|
|
59627
|
+
text = output.message || output.output || JSON.stringify(output);
|
|
59628
|
+
} else {
|
|
59629
|
+
text = String(output);
|
|
59630
|
+
}
|
|
59631
|
+
const lines = text.split(/\r?\n/).filter((l) => l.trim().length > 0);
|
|
59632
|
+
if (lines.length === 0)
|
|
59633
|
+
return [];
|
|
59634
|
+
const preview = lines.slice(0, 5);
|
|
59635
|
+
return preview.map((l, idx) => idx === 0 ? ` └ ${l}` : ` ${l}`);
|
|
59636
|
+
}
|
|
59637
|
+
function generateTranscriptFromSession(sessionData, cwd = process.cwd()) {
|
|
59638
|
+
const model = sessionData.model;
|
|
59639
|
+
const modelTag = model ? `${model.provider}/${model.modelId}` : "ai/model";
|
|
59640
|
+
const lines = [`Steward`, `${modelTag} · API Usage Billing`, `${cwd}`, ``];
|
|
59641
|
+
const turns = sessionData.turns ?? [];
|
|
59642
|
+
for (let turnIdx = 0;turnIdx < turns.length; turnIdx++) {
|
|
59643
|
+
const turn = turns[turnIdx];
|
|
59644
|
+
if (turnIdx > 0) {
|
|
59645
|
+
lines.push(``);
|
|
59646
|
+
}
|
|
59647
|
+
const messages = turn.messages ?? [];
|
|
59648
|
+
for (const msg of messages) {
|
|
59649
|
+
if (msg.role === "user") {
|
|
59650
|
+
const text = typeof msg.content === "string" ? msg.content : Array.isArray(msg.content) ? msg.content.map((c) => c.text || "").join(`
|
|
59651
|
+
`) : "";
|
|
59652
|
+
lines.push(`❯ ${text}`);
|
|
59653
|
+
lines.push(``);
|
|
59654
|
+
} else if (msg.role === "assistant") {
|
|
59655
|
+
if (Array.isArray(msg.content)) {
|
|
59656
|
+
for (const block of msg.content) {
|
|
59657
|
+
if (block.type === "thinking" && block.thinking) {
|
|
59658
|
+
lines.push(` Thought for ${Math.max(1, Math.round(block.thinking.length / 500))}s`);
|
|
59659
|
+
lines.push(``);
|
|
59660
|
+
} else if (block.type === "tool-call") {
|
|
59661
|
+
const argPreview = formatToolArgs(block.arguments);
|
|
59662
|
+
lines.push(`● ${block.name}${argPreview ? `(${argPreview})` : "()"}`);
|
|
59663
|
+
} else if (block.type === "text" && block.text) {
|
|
59664
|
+
const textLines = block.text.trim().split(/\r?\n/);
|
|
59665
|
+
for (let i = 0;i < textLines.length; i++) {
|
|
59666
|
+
lines.push(i === 0 ? `● ${textLines[i]}` : ` ${textLines[i]}`);
|
|
59667
|
+
}
|
|
59668
|
+
lines.push(``);
|
|
59669
|
+
}
|
|
59670
|
+
}
|
|
59671
|
+
} else if (typeof msg.content === "string" && msg.content.trim()) {
|
|
59672
|
+
const textLines = msg.content.trim().split(/\r?\n/);
|
|
59673
|
+
for (let i = 0;i < textLines.length; i++) {
|
|
59674
|
+
lines.push(i === 0 ? `● ${textLines[i]}` : ` ${textLines[i]}`);
|
|
59675
|
+
}
|
|
59676
|
+
lines.push(``);
|
|
59677
|
+
}
|
|
59678
|
+
} else if (msg.role === "tool") {
|
|
59679
|
+
if (Array.isArray(msg.content)) {
|
|
59680
|
+
for (const toolRes of msg.content) {
|
|
59681
|
+
const formatted = formatToolOutput(toolRes.output);
|
|
59682
|
+
for (const l of formatted) {
|
|
59683
|
+
lines.push(l);
|
|
59684
|
+
}
|
|
59685
|
+
}
|
|
59686
|
+
lines.push(``);
|
|
59687
|
+
}
|
|
59688
|
+
}
|
|
59689
|
+
}
|
|
59690
|
+
if (turn.timestamp) {
|
|
59691
|
+
const timeStr = new Date(turn.timestamp).toLocaleTimeString([], {
|
|
59692
|
+
hour: "2-digit",
|
|
59693
|
+
minute: "2-digit"
|
|
59694
|
+
});
|
|
59695
|
+
lines.push(`✻ Done · ${timeStr}`);
|
|
59696
|
+
}
|
|
59697
|
+
}
|
|
59698
|
+
return lines.join(`
|
|
59699
|
+
`).trim() + `
|
|
59700
|
+
`;
|
|
59701
|
+
}
|
|
59702
|
+
var exportCommand = {
|
|
59703
|
+
name: "export",
|
|
59704
|
+
description: "Exports the exact 1:1 UI conversation text to the clipboard or a file",
|
|
59705
|
+
usage: "/export [filename]",
|
|
59706
|
+
async execute(args, context) {
|
|
59707
|
+
if (context.session.isBusy) {
|
|
59708
|
+
return { handled: true, message: "Cannot export while the agent is generating a response." };
|
|
59709
|
+
}
|
|
59710
|
+
let transcript = "";
|
|
59711
|
+
if (typeof context.getScreenLines === "function") {
|
|
59712
|
+
const screenLines = context.getScreenLines();
|
|
59713
|
+
if (screenLines && screenLines.length > 0) {
|
|
59714
|
+
transcript = screenLines.map((l) => stripLogoFromLine(stripAnsi2(l))).join(`
|
|
59715
|
+
`).trim() + `
|
|
59716
|
+
`;
|
|
59717
|
+
}
|
|
59718
|
+
}
|
|
59719
|
+
if (!transcript.trim()) {
|
|
59720
|
+
const sessionData = context.session.session;
|
|
59721
|
+
transcript = generateTranscriptFromSession(sessionData, context.cwd || process.cwd());
|
|
59722
|
+
}
|
|
59723
|
+
if (!transcript.trim()) {
|
|
59724
|
+
return { handled: true, message: "No conversation messages found to export." };
|
|
59725
|
+
}
|
|
59726
|
+
const targetArg = args.join(" ").trim();
|
|
59727
|
+
if (!targetArg) {
|
|
59728
|
+
await copyToClipboard2(transcript);
|
|
59729
|
+
return {
|
|
59730
|
+
handled: true,
|
|
59731
|
+
message: "Copied conversation transcript to clipboard."
|
|
59732
|
+
};
|
|
59733
|
+
}
|
|
59734
|
+
try {
|
|
59735
|
+
const cwd = context.cwd || process.cwd();
|
|
59736
|
+
const filePath = isAbsolute8(targetArg) ? targetArg : resolve11(cwd, targetArg);
|
|
59737
|
+
const targetPath = filePath.endsWith(".txt") || filePath.endsWith(".md") ? filePath : `${filePath}.txt`;
|
|
59738
|
+
mkdirSync10(dirname7(targetPath), { recursive: true });
|
|
59739
|
+
writeFileSync4(targetPath, transcript, "utf-8");
|
|
59740
|
+
return {
|
|
59741
|
+
handled: true,
|
|
59742
|
+
message: `Conversation transcript exported to ${targetPath}`
|
|
59743
|
+
};
|
|
59744
|
+
} catch (err) {
|
|
59745
|
+
return {
|
|
59746
|
+
handled: true,
|
|
59747
|
+
message: `Failed to export transcript: ${err instanceof Error ? err.message : String(err)}`
|
|
59748
|
+
};
|
|
59749
|
+
}
|
|
59750
|
+
}
|
|
59751
|
+
};
|
|
59752
|
+
|
|
59753
|
+
// src/app/commands/init/index.ts
|
|
59754
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync5 } from "node:fs";
|
|
59755
|
+
import { basename, join as join17 } from "node:path";
|
|
59756
|
+
function generateAgentsMd(cwd) {
|
|
59757
|
+
const projectName = basename(cwd);
|
|
59758
|
+
let stack = "General";
|
|
59759
|
+
let commandsSection = "- `make test`: Run test suite";
|
|
59760
|
+
if (existsSync19(join17(cwd, "package.json"))) {
|
|
59761
|
+
try {
|
|
59762
|
+
const pkg = JSON.parse(readFileSync14(join17(cwd, "package.json"), "utf-8"));
|
|
59763
|
+
const scripts = pkg.scripts ? Object.keys(pkg.scripts) : [];
|
|
59764
|
+
stack = "Node.js / TypeScript";
|
|
59765
|
+
if (scripts.length > 0) {
|
|
59766
|
+
commandsSection = scripts.map((s) => `- \`npm run ${s}\` (or \`bun run ${s}\`)`).join(`
|
|
59767
|
+
`);
|
|
59768
|
+
}
|
|
59769
|
+
} catch {
|
|
59770
|
+
stack = "Node.js";
|
|
59771
|
+
}
|
|
59772
|
+
} else if (existsSync19(join17(cwd, "Cargo.toml"))) {
|
|
59773
|
+
stack = "Rust";
|
|
59774
|
+
commandsSection = "- `cargo build`: Build project\n- `cargo test`: Run tests";
|
|
59775
|
+
} else if (existsSync19(join17(cwd, "pyproject.toml")) || existsSync19(join17(cwd, "requirements.txt"))) {
|
|
59776
|
+
stack = "Python";
|
|
59777
|
+
commandsSection = "- `pytest`: Run tests\n- `python main.py`: Run application";
|
|
59778
|
+
} else if (existsSync19(join17(cwd, "go.mod"))) {
|
|
59779
|
+
stack = "Go";
|
|
59780
|
+
commandsSection = "- `go build ./...`: Build packages\n- `go test ./...`: Run test suite";
|
|
59781
|
+
}
|
|
59782
|
+
return `# AGENTS.md
|
|
59783
|
+
|
|
59784
|
+
Operational guidelines and architectural context for AI engineering assistants working in \`${projectName}\`.
|
|
59785
|
+
|
|
59786
|
+
## 1. Project Overview & Tech Stack
|
|
59787
|
+
|
|
59788
|
+
- **Project:** \`${projectName}\`
|
|
59789
|
+
- **Primary Stack:** ${stack}
|
|
59790
|
+
|
|
59791
|
+
## 2. Common Commands
|
|
59792
|
+
|
|
59793
|
+
${commandsSection}
|
|
59794
|
+
|
|
59795
|
+
## 3. Architecture & Guidelines
|
|
59796
|
+
|
|
59797
|
+
- **Code Style:** Follow established project formatting and idioms.
|
|
59798
|
+
- **Verification:** Run relevant tests before finalizing changes.
|
|
59799
|
+
- **Safety:** Verify file paths and permissions before applying destructive edits.
|
|
59800
|
+
`;
|
|
59801
|
+
}
|
|
59802
|
+
var DUMMY_HOOKS_JSON = JSON.stringify({
|
|
59803
|
+
version: 1,
|
|
59804
|
+
hooks: {
|
|
59805
|
+
SessionStart: [
|
|
59806
|
+
{
|
|
59807
|
+
name: "sample-session-start",
|
|
59808
|
+
command: 'echo "Steward session started"',
|
|
59809
|
+
enabled: false,
|
|
59810
|
+
description: "Runs when a session starts or resumes"
|
|
59811
|
+
}
|
|
59812
|
+
],
|
|
59813
|
+
BeforeToolUse: [
|
|
59814
|
+
{
|
|
59815
|
+
name: "sample-tool-guard",
|
|
59816
|
+
command: 'echo "Checking tool call"',
|
|
59817
|
+
matcher: "bash",
|
|
59818
|
+
enabled: false,
|
|
59819
|
+
description: "Runs before specific tool executions"
|
|
59820
|
+
}
|
|
59821
|
+
]
|
|
59822
|
+
}
|
|
59823
|
+
}, null, 2);
|
|
59824
|
+
var initCommand = {
|
|
59825
|
+
name: "init",
|
|
59826
|
+
description: "Initializes project with AGENTS.md, .steward/hooks.json, and .agents/ directory",
|
|
59827
|
+
usage: "/init",
|
|
59828
|
+
async execute(_args, context) {
|
|
59829
|
+
const cwd = context.cwd || process.cwd();
|
|
59830
|
+
const created = [];
|
|
59831
|
+
const skipped = [];
|
|
59832
|
+
try {
|
|
59833
|
+
const agentsMdPath = join17(cwd, "AGENTS.md");
|
|
59834
|
+
if (!existsSync19(agentsMdPath)) {
|
|
59835
|
+
writeFileSync5(agentsMdPath, generateAgentsMd(cwd), "utf-8");
|
|
59836
|
+
created.push("AGENTS.md");
|
|
59837
|
+
} else {
|
|
59838
|
+
skipped.push("AGENTS.md (already exists)");
|
|
59839
|
+
}
|
|
59840
|
+
const stewardDir = join17(cwd, ".steward");
|
|
59841
|
+
if (!existsSync19(stewardDir)) {
|
|
59842
|
+
mkdirSync11(stewardDir, { recursive: true });
|
|
59843
|
+
}
|
|
59844
|
+
const hooksPath = join17(stewardDir, "hooks.json");
|
|
59845
|
+
if (!existsSync19(hooksPath)) {
|
|
59846
|
+
writeFileSync5(hooksPath, DUMMY_HOOKS_JSON, "utf-8");
|
|
59847
|
+
created.push(".steward/hooks.json");
|
|
59848
|
+
} else {
|
|
59849
|
+
skipped.push(".steward/hooks.json (already exists)");
|
|
59850
|
+
}
|
|
59851
|
+
const skillsDir = join17(cwd, ".agents", "skills");
|
|
59852
|
+
if (!existsSync19(skillsDir)) {
|
|
59853
|
+
mkdirSync11(skillsDir, { recursive: true });
|
|
59854
|
+
created.push(".agents/skills/");
|
|
59855
|
+
} else {
|
|
59856
|
+
skipped.push(".agents/skills/ (already exists)");
|
|
59857
|
+
}
|
|
59858
|
+
const summaryParts = [];
|
|
59859
|
+
if (created.length > 0) {
|
|
59860
|
+
summaryParts.push(`Created: ${created.join(", ")}`);
|
|
59861
|
+
}
|
|
59862
|
+
if (skipped.length > 0) {
|
|
59863
|
+
summaryParts.push(`Preserved: ${skipped.join(", ")}`);
|
|
59864
|
+
}
|
|
59865
|
+
return {
|
|
59866
|
+
handled: true,
|
|
59867
|
+
message: `Project initialized successfully!
|
|
59868
|
+
${summaryParts.join(`
|
|
59869
|
+
`)}`
|
|
59870
|
+
};
|
|
59871
|
+
} catch (err) {
|
|
59872
|
+
return {
|
|
59873
|
+
handled: true,
|
|
59874
|
+
message: `Failed to initialize project: ${err instanceof Error ? err.message : String(err)}`
|
|
59875
|
+
};
|
|
59876
|
+
}
|
|
59877
|
+
}
|
|
59878
|
+
};
|
|
59879
|
+
|
|
59880
|
+
// src/app/commands/usage/index.ts
|
|
59881
|
+
function formatNumber(num) {
|
|
59882
|
+
return (num ?? 0).toLocaleString();
|
|
59883
|
+
}
|
|
59884
|
+
function formatDuration(isoDateString) {
|
|
59885
|
+
if (!isoDateString)
|
|
59886
|
+
return "0s";
|
|
59887
|
+
const start = new Date(isoDateString).getTime();
|
|
59888
|
+
if (isNaN(start))
|
|
59889
|
+
return "0s";
|
|
59890
|
+
const elapsedSec = Math.max(0, Math.floor((Date.now() - start) / 1000));
|
|
59891
|
+
const hrs = Math.floor(elapsedSec / 3600);
|
|
59892
|
+
const mins = Math.floor(elapsedSec % 3600 / 60);
|
|
59893
|
+
const secs = elapsedSec % 60;
|
|
59894
|
+
if (hrs > 0)
|
|
59895
|
+
return `${hrs}h ${mins}m ${secs}s`;
|
|
59896
|
+
if (mins > 0)
|
|
59897
|
+
return `${mins}m ${secs}s`;
|
|
59898
|
+
return `${secs}s`;
|
|
59899
|
+
}
|
|
59900
|
+
function getContextWindowLimit(modelId, provider) {
|
|
59901
|
+
const m = modelId.toLowerCase();
|
|
59902
|
+
const p = provider.toLowerCase();
|
|
59903
|
+
if (p === "gemini" || m.includes("gemini"))
|
|
59904
|
+
return 1e6;
|
|
59905
|
+
if (p === "anthropic" || m.includes("claude"))
|
|
59906
|
+
return 200000;
|
|
59907
|
+
if (m.includes("deepseek"))
|
|
59908
|
+
return 64000;
|
|
59909
|
+
if (m.includes("gpt-4") || m.includes("o1") || m.includes("o3") || m.includes("llama"))
|
|
59910
|
+
return 128000;
|
|
59911
|
+
return 128000;
|
|
59912
|
+
}
|
|
59913
|
+
function renderProgressBar(current, max, length = 16) {
|
|
59914
|
+
if (max <= 0)
|
|
59915
|
+
return `[${"░".repeat(length)}] 0%`;
|
|
59916
|
+
const ratio = Math.min(Math.max(current / max, 0), 1);
|
|
59917
|
+
const percent = Math.round(ratio * 100);
|
|
59918
|
+
const filled = Math.round(ratio * length);
|
|
59919
|
+
const empty = length - filled;
|
|
59920
|
+
return `[${"█".repeat(filled)}${"░".repeat(empty)}] ${percent}%`;
|
|
59921
|
+
}
|
|
59922
|
+
function estimateUniversalCost(usage, modelId, provider) {
|
|
59923
|
+
const p = provider.toLowerCase();
|
|
59924
|
+
const m = modelId.toLowerCase();
|
|
59925
|
+
if (p === "ollama" || m.includes("local")) {
|
|
59926
|
+
return "$0.00 USD [Local / Offline]";
|
|
59927
|
+
}
|
|
59928
|
+
const isLightweight = m.includes("flash") || m.includes("mini") || m.includes("haiku") || m.includes("8b") || m.includes("deepseek");
|
|
59929
|
+
const inputPerMillion = isLightweight ? 0.15 : 3;
|
|
59930
|
+
const outputPerMillion = isLightweight ? 0.6 : 15;
|
|
59931
|
+
const cachePerMillion = isLightweight ? 0.075 : 0.3;
|
|
59932
|
+
const baseCost = usage.inputTokens * inputPerMillion / 1e6;
|
|
59933
|
+
const outputCost = usage.outputTokens * outputPerMillion / 1e6;
|
|
59934
|
+
const cacheCost = (usage.cacheReadTokens ?? 0) * cachePerMillion / 1e6;
|
|
59935
|
+
const total = baseCost + outputCost + cacheCost;
|
|
59936
|
+
if (total === 0) {
|
|
59937
|
+
return "$0.00 USD [estimated]";
|
|
59938
|
+
}
|
|
59939
|
+
const formatted = total < 0.005 ? "< $0.01" : `$${total.toFixed(2)}`;
|
|
59940
|
+
return `~${formatted} USD [estimated benchmark]`;
|
|
59941
|
+
}
|
|
59942
|
+
var usageCommand = {
|
|
59943
|
+
name: "usage",
|
|
59944
|
+
description: "Displays token usage metrics and statistics for the current session",
|
|
59945
|
+
usage: "/usage",
|
|
59946
|
+
async execute(_args, context) {
|
|
59947
|
+
const sessionData = context.session.session;
|
|
59948
|
+
const model = context.session.getModel();
|
|
59949
|
+
const usage = context.session.getUsage();
|
|
59950
|
+
const turns = sessionData?.turns ?? [];
|
|
59951
|
+
const turnsCount = turns.length;
|
|
59952
|
+
let toolCallsCount = 0;
|
|
59953
|
+
for (const turn of turns) {
|
|
59954
|
+
if (Array.isArray(turn.messages)) {
|
|
59955
|
+
for (const msg of turn.messages) {
|
|
59956
|
+
if (msg.role === "tool") {
|
|
59957
|
+
toolCallsCount++;
|
|
59958
|
+
} else if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
59959
|
+
toolCallsCount += msg.content.filter((c) => c?.type === "tool-call").length;
|
|
59960
|
+
}
|
|
59961
|
+
}
|
|
59962
|
+
}
|
|
59963
|
+
}
|
|
59964
|
+
const contextLimit = getContextWindowLimit(model.modelId, model.provider);
|
|
59965
|
+
const contextBar = renderProgressBar(usage.totalTokens, contextLimit);
|
|
59966
|
+
const duration = formatDuration(sessionData?.createdAt);
|
|
59967
|
+
const costEstimate = estimateUniversalCost(usage, model.modelId, model.provider);
|
|
59968
|
+
const lines = [
|
|
59969
|
+
`Session Usage & Analytics:`,
|
|
59970
|
+
`• Active Model: ${model.modelId} (${model.provider}${model.effort !== "medium" ? `, effort: ${model.effort}` : ""})`,
|
|
59971
|
+
`• Session Duration: ${duration}`,
|
|
59972
|
+
`• Activity: ${formatNumber(turnsCount)} turns (${formatNumber(toolCallsCount)} tool executions)`,
|
|
59973
|
+
`• Context Window: ${contextBar} (${formatNumber(usage.totalTokens)} / ${formatNumber(contextLimit)} tokens)`,
|
|
59974
|
+
``,
|
|
59975
|
+
`Token Breakdown:`,
|
|
59976
|
+
`• Input Tokens: ${formatNumber(usage.inputTokens)}`,
|
|
59977
|
+
`• Output Tokens: ${formatNumber(usage.outputTokens)}`
|
|
59978
|
+
];
|
|
59979
|
+
if (usage.reasoningTokens && usage.reasoningTokens > 0) {
|
|
59980
|
+
lines.push(`• Reasoning Tokens: ${formatNumber(usage.reasoningTokens)}`);
|
|
59981
|
+
}
|
|
59982
|
+
if (usage.cacheReadTokens && usage.cacheReadTokens > 0) {
|
|
59983
|
+
const totalIn = (usage.inputTokens || 0) + (usage.cacheReadTokens || 0);
|
|
59984
|
+
const hitRate = totalIn > 0 ? Math.round(usage.cacheReadTokens / totalIn * 100) : 0;
|
|
59985
|
+
lines.push(`• Cache Read Tokens: ${formatNumber(usage.cacheReadTokens)} (${hitRate}% hit rate)`);
|
|
59986
|
+
}
|
|
59987
|
+
if (usage.cacheWriteTokens && usage.cacheWriteTokens > 0) {
|
|
59988
|
+
lines.push(`• Cache Write Tokens: ${formatNumber(usage.cacheWriteTokens)}`);
|
|
59989
|
+
}
|
|
59990
|
+
lines.push(`• Total Tokens: ${formatNumber(usage.totalTokens)}`);
|
|
59991
|
+
lines.push(``);
|
|
59992
|
+
lines.push(`Estimated Cost:`);
|
|
59993
|
+
lines.push(`• Session Spend: ${costEstimate}`);
|
|
59994
|
+
return {
|
|
59995
|
+
handled: true,
|
|
59996
|
+
message: lines.join(`
|
|
59997
|
+
`)
|
|
59998
|
+
};
|
|
59999
|
+
}
|
|
60000
|
+
};
|
|
60001
|
+
|
|
59488
60002
|
// src/app/commands/effort/index.ts
|
|
59489
60003
|
var EFFORT_HELP = `Reasoning Effort Levels:
|
|
59490
60004
|
none : disable reasoning/thinking
|
|
@@ -59954,6 +60468,10 @@ class CommandRegistry {
|
|
|
59954
60468
|
}
|
|
59955
60469
|
}
|
|
59956
60470
|
var builtInCommands = [
|
|
60471
|
+
initCommand,
|
|
60472
|
+
copyCommand,
|
|
60473
|
+
exportCommand,
|
|
60474
|
+
usageCommand,
|
|
59957
60475
|
rewindCommand,
|
|
59958
60476
|
modelCommand,
|
|
59959
60477
|
loginCommand,
|
|
@@ -62102,7 +62620,7 @@ var pn = x.lex;
|
|
|
62102
62620
|
|
|
62103
62621
|
// src/packages/tui/src/format/highlight.ts
|
|
62104
62622
|
var import_cli_highlight = __toESM(require_dist(), 1);
|
|
62105
|
-
import { extname, basename } from "node:path";
|
|
62623
|
+
import { extname, basename as basename2 } from "node:path";
|
|
62106
62624
|
var EXTENSION_MAP = {
|
|
62107
62625
|
".ts": "typescript",
|
|
62108
62626
|
".tsx": "typescript",
|
|
@@ -62147,7 +62665,7 @@ var FILENAME_MAP = {
|
|
|
62147
62665
|
function detectLanguageFromPath(filePath) {
|
|
62148
62666
|
if (!filePath)
|
|
62149
62667
|
return;
|
|
62150
|
-
const name =
|
|
62668
|
+
const name = basename2(filePath).toLowerCase();
|
|
62151
62669
|
if (FILENAME_MAP[name])
|
|
62152
62670
|
return FILENAME_MAP[name];
|
|
62153
62671
|
const ext = extname(filePath).toLowerCase();
|
|
@@ -64079,13 +64597,13 @@ class UpdateCheckerService {
|
|
|
64079
64597
|
}
|
|
64080
64598
|
}
|
|
64081
64599
|
// src/app/utils/open-url.ts
|
|
64082
|
-
import { spawn as
|
|
64083
|
-
import { existsSync as
|
|
64600
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
64601
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
64084
64602
|
function openUrl(url) {
|
|
64085
64603
|
const platform = process.platform;
|
|
64086
64604
|
try {
|
|
64087
|
-
if (process.env.TERMUX_VERSION ||
|
|
64088
|
-
const child =
|
|
64605
|
+
if (process.env.TERMUX_VERSION || existsSync20("/data/data/com.termux/files/usr/bin/termux-open-url")) {
|
|
64606
|
+
const child = spawn5("termux-open-url", [url], {
|
|
64089
64607
|
detached: true,
|
|
64090
64608
|
stdio: "ignore"
|
|
64091
64609
|
});
|
|
@@ -64093,7 +64611,7 @@ function openUrl(url) {
|
|
|
64093
64611
|
return;
|
|
64094
64612
|
}
|
|
64095
64613
|
if (platform === "win32") {
|
|
64096
|
-
const child =
|
|
64614
|
+
const child = spawn5("cmd.exe", ["/c", "start", '""', url], {
|
|
64097
64615
|
detached: true,
|
|
64098
64616
|
stdio: "ignore"
|
|
64099
64617
|
});
|
|
@@ -64101,14 +64619,14 @@ function openUrl(url) {
|
|
|
64101
64619
|
return;
|
|
64102
64620
|
}
|
|
64103
64621
|
if (platform === "darwin") {
|
|
64104
|
-
const child =
|
|
64622
|
+
const child = spawn5("open", [url], {
|
|
64105
64623
|
detached: true,
|
|
64106
64624
|
stdio: "ignore"
|
|
64107
64625
|
});
|
|
64108
64626
|
child.unref();
|
|
64109
64627
|
return;
|
|
64110
64628
|
}
|
|
64111
|
-
const child =
|
|
64629
|
+
const child = spawn5("xdg-open", [url], {
|
|
64112
64630
|
detached: true,
|
|
64113
64631
|
stdio: "ignore"
|
|
64114
64632
|
});
|
|
@@ -65902,7 +66420,7 @@ async function applyCommandResult(text, cmdResult, ctx) {
|
|
|
65902
66420
|
}
|
|
65903
66421
|
|
|
65904
66422
|
// src/app/utils/bash.ts
|
|
65905
|
-
import { spawn as
|
|
66423
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
65906
66424
|
async function executeDirectBash(options) {
|
|
65907
66425
|
const { command, cwd = process.cwd(), abortSignal, onChunk } = options;
|
|
65908
66426
|
const { shell, args } = getPlatformShell();
|
|
@@ -65915,7 +66433,7 @@ async function executeDirectBash(options) {
|
|
|
65915
66433
|
let output = "";
|
|
65916
66434
|
let isSettled = false;
|
|
65917
66435
|
try {
|
|
65918
|
-
child =
|
|
66436
|
+
child = spawn6(shell, childArgs, {
|
|
65919
66437
|
cwd,
|
|
65920
66438
|
env: process.env,
|
|
65921
66439
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -66134,7 +66652,7 @@ class TUIApp {
|
|
|
66134
66652
|
source: this.session.session.turns.length > 0 ? "resume" : "startup"
|
|
66135
66653
|
});
|
|
66136
66654
|
const logPath = getSessionLogPath(this.session.session.date, this.session.session.id);
|
|
66137
|
-
if (this.session.session.turns.length > 0 ||
|
|
66655
|
+
if (this.session.session.turns.length > 0 || existsSync21(logPath)) {
|
|
66138
66656
|
renderTranscript(this.engine, this.session.session, this.header);
|
|
66139
66657
|
} else {
|
|
66140
66658
|
this.engine.commit("header", this.header.render());
|
|
@@ -66240,7 +66758,8 @@ class TUIApp {
|
|
|
66240
66758
|
if (defaultCommandRegistry.isCommand(text)) {
|
|
66241
66759
|
const cmdResult = await defaultCommandRegistry.execute(text, {
|
|
66242
66760
|
session: this.session,
|
|
66243
|
-
cwd: this.cwd
|
|
66761
|
+
cwd: this.cwd,
|
|
66762
|
+
getScreenLines: () => this.engine.history.getEntries().filter((e) => e.kind !== "logo").flatMap((e) => e.lines)
|
|
66244
66763
|
});
|
|
66245
66764
|
await applyCommandResult(text, cmdResult, {
|
|
66246
66765
|
engine: this.engine,
|