croo-cli 0.1.0 → 0.1.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/README.md +1 -0
- package/dist/cli.js +78 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ croo --continue # resume the last session in this directory
|
|
|
34
34
|
croo --profile local # use a named profile
|
|
35
35
|
croo --yolo # skip permission prompts (be careful)
|
|
36
36
|
croo --no-pet # no frog
|
|
37
|
+
croo --demo # look around with a fake model, no API key
|
|
37
38
|
```
|
|
38
39
|
|
|
39
40
|
Slash commands: `/help`, `/model` (list, `/model <name>` to switch, `/model add`), `/clear`, `/config`, `/exit`.
|
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import readline from "readline";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "croo-cli",
|
|
9
|
-
version: "0.1.
|
|
9
|
+
version: "0.1.1",
|
|
10
10
|
description: "Croo: a quick, minimal, open-source terminal AI coding agent. Bring your own API key.",
|
|
11
11
|
type: "module",
|
|
12
12
|
bin: {
|
|
@@ -59,7 +59,7 @@ var package_default = {
|
|
|
59
59
|
|
|
60
60
|
// src/args.ts
|
|
61
61
|
function parseArgs(argv) {
|
|
62
|
-
const a = { prompt: "", yolo: false, noPet: false, continue: false, help: false, version: false };
|
|
62
|
+
const a = { prompt: "", yolo: false, noPet: false, demo: false, continue: false, help: false, version: false };
|
|
63
63
|
const rest = [];
|
|
64
64
|
for (let i = 0; i < argv.length; i++) {
|
|
65
65
|
const x = argv[i];
|
|
@@ -67,6 +67,7 @@ function parseArgs(argv) {
|
|
|
67
67
|
else if (x.startsWith("--profile=")) a.profile = x.slice("--profile=".length);
|
|
68
68
|
else if (x === "--yolo") a.yolo = true;
|
|
69
69
|
else if (x === "--no-pet") a.noPet = true;
|
|
70
|
+
else if (x === "--demo") a.demo = true;
|
|
70
71
|
else if (x === "--continue" || x === "-c") a.continue = true;
|
|
71
72
|
else if (x === "--help" || x === "-h") a.help = true;
|
|
72
73
|
else if (x === "--version" || x === "-v") a.version = true;
|
|
@@ -85,6 +86,7 @@ Options:
|
|
|
85
86
|
-c, --continue resume the last session in this directory
|
|
86
87
|
--yolo do not ask before writing files or running commands
|
|
87
88
|
--no-pet hide the frog
|
|
89
|
+
--demo try the interface with a scripted fake model (no API key)
|
|
88
90
|
-v, --version print version
|
|
89
91
|
-h, --help show this help
|
|
90
92
|
|
|
@@ -621,6 +623,59 @@ function buildSystemPrompt(cwd2, now = /* @__PURE__ */ new Date()) {
|
|
|
621
623
|
return parts.join("\n\n");
|
|
622
624
|
}
|
|
623
625
|
|
|
626
|
+
// src/providers/demo.ts
|
|
627
|
+
var DEMO_PROFILE = { type: "openai", model: "demo (no API)" };
|
|
628
|
+
var sleep = (ms, signal) => new Promise((resolve) => {
|
|
629
|
+
const t = setTimeout(resolve, ms);
|
|
630
|
+
signal?.addEventListener("abort", () => (clearTimeout(t), resolve()), { once: true });
|
|
631
|
+
});
|
|
632
|
+
var INTRO = `## Hello from the demo
|
|
633
|
+
I am **not** a real model, so no API key is needed. Here is what Croo's output looks like:
|
|
634
|
+
|
|
635
|
+
- lists with bullets
|
|
636
|
+
- inline \`code\` and **bold**
|
|
637
|
+
|
|
638
|
+
\`\`\`ts
|
|
639
|
+
const frog = "ribbit"
|
|
640
|
+
console.log(frog)
|
|
641
|
+
\`\`\`
|
|
642
|
+
|
|
643
|
+
Let me look around, then propose a file so you can try the permission prompt.
|
|
644
|
+
`;
|
|
645
|
+
var OUTRO = `All done. That was the demo: tool cards, a diff prompt, streaming and the spinner.
|
|
646
|
+
Run \`croo\` without \`--demo\` to set up a real provider.
|
|
647
|
+
`;
|
|
648
|
+
async function stream(text, onText, signal) {
|
|
649
|
+
for (let i = 0; i < text.length && !signal?.aborted; i += 6) {
|
|
650
|
+
onText(text.slice(i, i + 6));
|
|
651
|
+
await sleep(12, signal);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
var lastUser = (req) => req.messages.map((m) => m.role).lastIndexOf("user");
|
|
655
|
+
function demoProvider() {
|
|
656
|
+
return {
|
|
657
|
+
async chat(req, onText) {
|
|
658
|
+
const usage = { input: 120, output: 80 };
|
|
659
|
+
await sleep(900, req.signal);
|
|
660
|
+
const last = req.messages[req.messages.length - 1];
|
|
661
|
+
if (last?.role === "user") {
|
|
662
|
+
await stream(INTRO, onText, req.signal);
|
|
663
|
+
return { text: INTRO, usage, toolCalls: [{ id: "demo1", name: "list_files", args: {} }] };
|
|
664
|
+
}
|
|
665
|
+
const wrote = req.messages.some((m, i) => m.role === "tool" && m.name === "write_file" && i > lastUser(req));
|
|
666
|
+
if (!wrote) {
|
|
667
|
+
return {
|
|
668
|
+
text: "",
|
|
669
|
+
usage,
|
|
670
|
+
toolCalls: [{ id: "demo2", name: "write_file", args: { path: "croo-demo.txt", content: "ribbit\nCroo was here.\n" } }]
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
await stream(OUTRO, onText, req.signal);
|
|
674
|
+
return { text: OUTRO, usage, toolCalls: [] };
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
|
|
624
679
|
// src/providers/sse.ts
|
|
625
680
|
async function* sseData(res) {
|
|
626
681
|
if (!res.body) throw new Error("Response has no body.");
|
|
@@ -1266,7 +1321,7 @@ function collapseContext(lines, context = 2) {
|
|
|
1266
1321
|
}
|
|
1267
1322
|
|
|
1268
1323
|
// src/ui/render.ts
|
|
1269
|
-
var
|
|
1324
|
+
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1270
1325
|
var fmtTokens = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(n >= 1e4 ? 0 : 1)}k` : String(n);
|
|
1271
1326
|
var Ui = class {
|
|
1272
1327
|
constructor(o) {
|
|
@@ -1319,9 +1374,9 @@ var Ui = class {
|
|
|
1319
1374
|
};
|
|
1320
1375
|
hideCursor();
|
|
1321
1376
|
try {
|
|
1322
|
-
await
|
|
1377
|
+
await sleep2(550);
|
|
1323
1378
|
redraw(frames[1]);
|
|
1324
|
-
await
|
|
1379
|
+
await sleep2(140);
|
|
1325
1380
|
redraw(frames[0]);
|
|
1326
1381
|
} finally {
|
|
1327
1382
|
showCursor();
|
|
@@ -1509,13 +1564,21 @@ async function main() {
|
|
|
1509
1564
|
}
|
|
1510
1565
|
if (args.help) return console.log(HELP);
|
|
1511
1566
|
if (args.version) return console.log(VERSION);
|
|
1512
|
-
let cfg = loadConfig();
|
|
1513
|
-
if (!cfg || !Object.keys(cfg.profiles).length) {
|
|
1567
|
+
let cfg = args.demo ? null : loadConfig();
|
|
1568
|
+
if (!args.demo && (!cfg || !Object.keys(cfg.profiles).length)) {
|
|
1569
|
+
if (process.stdin.isTTY) {
|
|
1570
|
+
console.log(theme.accent(theme.bold("Welcome to Croo.")) + " No config yet.");
|
|
1571
|
+
const choice = await ask("Set up a provider now, or look around in demo mode (no API key)? [setup/demo]: ");
|
|
1572
|
+
if (choice.trim().toLowerCase().startsWith("d")) args.demo = true;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
if (args.demo) {
|
|
1576
|
+
cfg = { ...defaultConfig(), activeProfile: "demo", profiles: { demo: DEMO_PROFILE } };
|
|
1577
|
+
} else if (!cfg || !Object.keys(cfg.profiles).length) {
|
|
1514
1578
|
if (!process.stdin.isTTY) {
|
|
1515
1579
|
console.error(`No config found. Run \`croo\` in a terminal to set one up, or create ${configPath()}.`);
|
|
1516
1580
|
process.exit(1);
|
|
1517
1581
|
}
|
|
1518
|
-
console.log(theme.accent(theme.bold("Welcome to Croo.")) + " No config yet, so let us set one up.");
|
|
1519
1582
|
cfg = await setupProfile(ask, cfg ?? defaultConfig());
|
|
1520
1583
|
}
|
|
1521
1584
|
let profileName = args.profile ?? cfg.activeProfile;
|
|
@@ -1525,10 +1588,11 @@ async function main() {
|
|
|
1525
1588
|
}
|
|
1526
1589
|
const ui = new Ui({ theme, pet: frogPet, showPet: cfg.pet && !args.noPet, animations: cfg.animations, ask });
|
|
1527
1590
|
const permissions = new Permissions(args.yolo);
|
|
1591
|
+
const persist = !args.demo;
|
|
1528
1592
|
const interactive = !args.prompt;
|
|
1529
1593
|
let provider;
|
|
1530
1594
|
const useProfile = (name) => {
|
|
1531
|
-
provider = createProvider(cfg.profiles[name]);
|
|
1595
|
+
provider = args.demo ? demoProvider() : createProvider(cfg.profiles[name]);
|
|
1532
1596
|
profileName = name;
|
|
1533
1597
|
};
|
|
1534
1598
|
try {
|
|
@@ -1537,7 +1601,7 @@ async function main() {
|
|
|
1537
1601
|
ui.petSays("oops", e.message);
|
|
1538
1602
|
process.exit(1);
|
|
1539
1603
|
}
|
|
1540
|
-
let session = args.continue && loadLastSession(cwd) || newSession(cwd, profileName);
|
|
1604
|
+
let session = args.continue && !args.demo && loadLastSession(cwd) || newSession(cwd, profileName);
|
|
1541
1605
|
const messages = session.messages;
|
|
1542
1606
|
if (args.continue && messages.length) ui.info(`Resumed session with ${messages.length} messages.`);
|
|
1543
1607
|
const system = buildSystemPrompt(cwd);
|
|
@@ -1581,7 +1645,7 @@ async function main() {
|
|
|
1581
1645
|
ui.idle();
|
|
1582
1646
|
current = void 0;
|
|
1583
1647
|
session.profile = profileName;
|
|
1584
|
-
saveSession(session);
|
|
1648
|
+
if (persist) saveSession(session);
|
|
1585
1649
|
}
|
|
1586
1650
|
}
|
|
1587
1651
|
function interrupt() {
|
|
@@ -1613,6 +1677,7 @@ async function main() {
|
|
|
1613
1677
|
const profile = () => cfg.profiles[profileName];
|
|
1614
1678
|
const showBanner = () => ui.banner({ version: VERSION, profile: profileName, model: profile().model, cwd });
|
|
1615
1679
|
await showBanner();
|
|
1680
|
+
if (args.demo) ui.info("Demo mode: a scripted fake model, no API calls. Type anything to start.");
|
|
1616
1681
|
const reader = getRl();
|
|
1617
1682
|
reader.setPrompt(theme.accent("\u203A "));
|
|
1618
1683
|
reader.on("SIGINT", interrupt);
|
|
@@ -1662,7 +1727,7 @@ async function main() {
|
|
|
1662
1727
|
try {
|
|
1663
1728
|
useProfile(arg);
|
|
1664
1729
|
cfg.activeProfile = arg;
|
|
1665
|
-
saveConfig(cfg);
|
|
1730
|
+
if (persist) saveConfig(cfg);
|
|
1666
1731
|
ui.info(` now using ${arg} (${profile().model})`);
|
|
1667
1732
|
} catch (e) {
|
|
1668
1733
|
ui.error(e.message);
|
|
@@ -1682,7 +1747,7 @@ async function main() {
|
|
|
1682
1747
|
}
|
|
1683
1748
|
next();
|
|
1684
1749
|
}
|
|
1685
|
-
saveSession(session);
|
|
1750
|
+
if (persist) saveSession(session);
|
|
1686
1751
|
process.exit(0);
|
|
1687
1752
|
}
|
|
1688
1753
|
main().catch((e) => {
|