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.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/dist/cli.js +78 -13
  3. 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.0",
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 sleep = (ms) => new Promise((r) => setTimeout(r, ms));
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 sleep(550);
1377
+ await sleep2(550);
1323
1378
  redraw(frames[1]);
1324
- await sleep(140);
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "croo-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Croo: a quick, minimal, open-source terminal AI coding agent. Bring your own API key.",
5
5
  "type": "module",
6
6
  "bin": {