archymedes-cli 2.2.0 → 2.5.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/README.md +17 -7
- package/dist/{archymedes-76z6n643.mjs → archymedes-06fh0z84.mjs} +233 -12
- package/dist/archymedes-130jt0wr.mjs +1231 -0
- package/dist/{archymedes-pg3pvh9r.mjs → archymedes-138genyy.mjs} +214 -5
- package/dist/{archymedes-sc3mwttb.mjs → archymedes-2kvjaajw.mjs} +66 -25
- package/dist/archymedes-5vhvv8kr.mjs +1169 -0
- package/dist/{archymedes-wjaqydyg.mjs → archymedes-a3ms7jvr.mjs} +45 -29
- package/dist/{archymedes-cdgejs3p.mjs → archymedes-bmtdqqwt.mjs} +17 -44
- package/dist/{archymedes-npcrm9a0.mjs → archymedes-d3azqkay.mjs} +537 -9
- package/dist/archymedes-main.mjs +31452 -25912
- package/dist/archymedes-w4bjfye7.mjs +280 -0
- package/dist/{editor-screen-1b3z96tn.mjs → editor-screen-e01mn7dd.mjs} +62 -26
- package/dist/{factory-96yy6mxr.mjs → factory-hyv8agr0.mjs} +1 -1
- package/dist/{file-screen-m7q1xcer.mjs → file-screen-stcxznwy.mjs} +10 -8
- package/dist/guide-screen-w5nb9sry.mjs +273 -0
- package/dist/pager-screen-aamt6xv0.mjs +155 -0
- package/dist/projection-x6x6hm9t.mjs +248 -0
- package/dist/workspace-screen-62k6hnq7.mjs +70 -0
- package/package.json +1 -1
- package/dist/archymedes-5zn4zp84.mjs +0 -646
- package/dist/archymedes-r54qthg5.mjs +0 -495
- package/dist/guide-screen-aj9n9v80.mjs +0 -569
- package/dist/workspace-screen-w88mcw4w.mjs +0 -241
|
@@ -1,4 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
import {
|
|
2
|
+
GUTTER,
|
|
3
|
+
clip,
|
|
4
|
+
note,
|
|
5
|
+
panel,
|
|
6
|
+
rule
|
|
7
|
+
} from "./archymedes-bmtdqqwt.mjs";
|
|
8
|
+
import {
|
|
9
|
+
BOLD,
|
|
10
|
+
DIM,
|
|
11
|
+
UNICODE_GLYPHS,
|
|
12
|
+
paint,
|
|
13
|
+
paintAll,
|
|
14
|
+
visibleWidth
|
|
15
|
+
} from "./archymedes-130jt0wr.mjs";
|
|
16
|
+
|
|
17
|
+
// packages/archymedes-cli/src/terminal/viewport.ts
|
|
2
18
|
function newViewport(lines, height) {
|
|
3
19
|
return clamp({ lines, height: Math.max(1, Math.floor(height)), top: 0 });
|
|
4
20
|
}
|
|
@@ -40,12 +56,18 @@ function applyViewport(state, action) {
|
|
|
40
56
|
function visibleLines(state) {
|
|
41
57
|
return state.lines.slice(state.top, state.top + state.height);
|
|
42
58
|
}
|
|
59
|
+
function atTop(state) {
|
|
60
|
+
return state.top <= 0;
|
|
61
|
+
}
|
|
62
|
+
function atBottom(state) {
|
|
63
|
+
return state.top >= maxTop(state);
|
|
64
|
+
}
|
|
43
65
|
function scrollFraction(state) {
|
|
44
66
|
const furthest = maxTop(state);
|
|
45
67
|
return furthest === 0 ? 1 : state.top / furthest;
|
|
46
68
|
}
|
|
47
69
|
|
|
48
|
-
// packages/archymedes-cli/src/i18n.ts
|
|
70
|
+
// packages/archymedes-cli/src/platform/i18n.ts
|
|
49
71
|
var CONTROL_LANGUAGES = {
|
|
50
72
|
en: "English",
|
|
51
73
|
zh: "中文 (Mandarin)",
|
|
@@ -532,7 +554,7 @@ function t(language, key) {
|
|
|
532
554
|
return MESSAGES[key][language] ?? MESSAGES[key].en;
|
|
533
555
|
}
|
|
534
556
|
|
|
535
|
-
// packages/archymedes-cli/src/commands.ts
|
|
557
|
+
// packages/archymedes-cli/src/catalog/commands.ts
|
|
536
558
|
function defineCommands(table) {
|
|
537
559
|
return Object.entries(table).map(([name, command]) => ({ name, ...command }));
|
|
538
560
|
}
|
|
@@ -555,19 +577,22 @@ var COMMANDS2 = defineCommands({
|
|
|
555
577
|
"/route": { args: "[all|summary|plan]", description: "The hosted exchange's routing decision: chosen model, alternatives, policy, est vs actual cost. `plan` forecasts the next turn before spending" },
|
|
556
578
|
"/clear": { description: "Start a fresh thread" },
|
|
557
579
|
"/memory": { args: "[add|replace|recall|forget]", description: "Bounded, relevant memory across sessions — # <fact> remembers for this project" },
|
|
558
|
-
"/history": { args: "[<id> | search <text> |
|
|
580
|
+
"/history": { args: "[list | <id> | search <text> | delete <id>|--all | status]", description: "Go back to an earlier conversation — opens a picker (Del deletes one); list prints them, search finds one" },
|
|
581
|
+
"/resume": { args: "[<id> | latest]", description: "Continue an earlier conversation in this folder (picker, or straight to one)" },
|
|
559
582
|
"/export": { args: "[markdown|json|support]", description: "Write a redacted session transcript or compact support artifact" },
|
|
560
583
|
"/expand": { args: "[N | all | list]", description: "Show a folded block — written code, a test run, a long result — in full" },
|
|
561
584
|
"/slow": { args: "[on|strict|off]", description: "Spend at a slower pace: fewer model rounds, smaller replies, a pause between turns" },
|
|
562
585
|
"/workspace": { description: "Open the control panel: every tab and watched job, live, on one screen" },
|
|
563
586
|
"/watch": { args: "[id|show <id>|stop <id>]", description: "Follow a background job's output without giving up the prompt" },
|
|
564
|
-
"/theme": { args: "[name|list|where]", description: "
|
|
587
|
+
"/theme": { args: "[name|list|where]", description: "Pick the colours from a list, or name one — blueprint, parchment, chalkboard, high-contrast, rainbow, or a .tss file of your own" },
|
|
565
588
|
"/tab": { args: "[new|next|prev|close|rename|N]", description: "Several pieces of work, one at a time — only the tab in front runs (/detach for parallel)" },
|
|
566
589
|
"/pull": { args: "[dir]", description: "Copy sandbox files here" },
|
|
567
590
|
"/where": { description: "Show the current workspace" },
|
|
591
|
+
"/diagnostics": { args: "[glob]", description: "Language server diagnostics for the workspace — errors, warnings, info from installed language servers" },
|
|
568
592
|
"/tools": { description: "What the agent can call, and which skills, plugins, MCP servers or hooks a project added" },
|
|
569
593
|
"/providers": { description: "Which model providers are configured" },
|
|
570
|
-
"/settings": { description: "
|
|
594
|
+
"/settings": { description: "Theme, code colours, simple mode, startup, free-mode savings, keys and models" },
|
|
595
|
+
"/upgrade": { description: "Use your own OpenRouter API key instead of the shared free gateway" },
|
|
571
596
|
"/update": { args: "[auto|check|off]", description: "Update Archymedes now, or choose whether it updates itself" },
|
|
572
597
|
"/voice": { args: "[audio-file]", description: "Record or transcribe an editable voice prompt" },
|
|
573
598
|
"/wander": { args: "[topic|random|daily|weekly]", description: "Run a bounded research lab and grade what it finds" },
|
|
@@ -577,15 +602,518 @@ var COMMANDS2 = defineCommands({
|
|
|
577
602
|
"/cost": { description: "Token and cost breakdown for this session" },
|
|
578
603
|
"/balance": { args: "[amount] [currency] | clear", description: "Your Archymedes credit balance on the exchange, or a locally tracked spend limit on a direct provider" },
|
|
579
604
|
"/scan": { args: "[glob]", description: "Deterministic secret scan of the workspace, worst severity first — no model turn needed" },
|
|
580
|
-
"/sessions": { description: "
|
|
605
|
+
"/sessions": { description: "Pick an earlier conversation in this project (an alias for /history)" },
|
|
581
606
|
"/palette": { description: "Search every command by name or by what it does" },
|
|
582
607
|
"/keys": { description: "Every shortcut in full, plus line-editing keys and which chords this terminal may not deliver — /help lists them inline, next to each command" },
|
|
583
608
|
"/guide": { args: "[topic|search <text>|all]", description: "The user guide, as a screen you can browse — or print one topic with /guide <topic>" },
|
|
584
609
|
"/files": { description: "Browse the project tree — expand folders, preview a file, pick one to @mention" },
|
|
585
610
|
"/edit": { args: "<path>", description: "Open a file in the built-in editor — through the workspace, so it works in a sandbox too" },
|
|
586
|
-
"/cat": { args: "<path>", description: "Print a file into the transcript — markdown rendered like a document, code numbered and highlighted" },
|
|
611
|
+
"/cat": { args: "<path>", description: "Print a file into the transcript — markdown rendered like a document, code numbered and highlighted, PNG images drawn" },
|
|
612
|
+
"/find": { args: "<text> | next | prev | off", description: "Search this tab's transcript in the fixed workspace; repeat /find for the next match" },
|
|
613
|
+
"/pager": { description: "Open this tab's transcript in $PAGER (less -R) to search, select and save it" },
|
|
587
614
|
"/help": { args: "[all|work|review|steer|parallel|learn|setup]", description: "Commands grouped by purpose" },
|
|
588
615
|
"/exit": { description: "Leave" }
|
|
589
616
|
});
|
|
617
|
+
function renderCommandHelp(language = "en", shortcuts = new Map, options = {}) {
|
|
618
|
+
const width = Math.max(...COMMANDS2.map((command) => command.name.length + (command.args ? command.args.length + 1 : 0)));
|
|
619
|
+
const shortcutWidth = Math.max(0, ...[...shortcuts.values()].map((label) => label.length));
|
|
620
|
+
const mark = options.mark ?? (() => " ");
|
|
621
|
+
return COMMANDS2.map((command) => {
|
|
622
|
+
const head = command.args ? `${command.name} ${command.args}` : command.name;
|
|
623
|
+
const shortcut = shortcuts.get(command.name) ?? "";
|
|
624
|
+
const shortcutColumn = shortcutWidth > 0 ? shortcut.padEnd(shortcutWidth + 2) : "";
|
|
625
|
+
return `${mark(command.name)}${head.padEnd(width + 2)}${shortcutColumn}${commandDescription(language, command.name, command.description)}`;
|
|
626
|
+
}).join(`
|
|
627
|
+
`);
|
|
628
|
+
}
|
|
629
|
+
function suggestionsFor(line, models = []) {
|
|
630
|
+
const model = /^\/models?\s+(\S*)$/.exec(line);
|
|
631
|
+
if (model) {
|
|
632
|
+
const fragment = model[1].toLowerCase();
|
|
633
|
+
const matched = models.filter((id) => id.toLowerCase().includes(fragment));
|
|
634
|
+
return matched.length === 1 && matched[0].toLowerCase() === fragment ? [] : matched.map((id) => ({ command: id, description: "" }));
|
|
635
|
+
}
|
|
636
|
+
if (!/^\/\S*$/.test(line))
|
|
637
|
+
return [];
|
|
638
|
+
const matches = COMMANDS2.filter((command) => command.name.startsWith(line));
|
|
639
|
+
if (matches.length === 1 && matches[0].name === line)
|
|
640
|
+
return [];
|
|
641
|
+
return matches.map((command) => ({ command: command.name, ...command.args ? { args: command.args } : {}, description: command.description }));
|
|
642
|
+
}
|
|
643
|
+
function inlineCompletion(line, models = []) {
|
|
644
|
+
const none = { suffix: "", alternatives: 0 };
|
|
645
|
+
const model = /^(\/models?\s+)(\S+)$/.exec(line);
|
|
646
|
+
if (model) {
|
|
647
|
+
const fragment = model[2].toLowerCase();
|
|
648
|
+
const matched2 = models.filter((id) => id.toLowerCase().startsWith(fragment)).sort((a, b) => a.length - b.length);
|
|
649
|
+
if (matched2.length === 0 || matched2[0].length === fragment.length)
|
|
650
|
+
return none;
|
|
651
|
+
return { suffix: matched2[0].slice(model[2].length), alternatives: matched2.length - 1 };
|
|
652
|
+
}
|
|
653
|
+
if (!/^\/\S*$/.test(line) || line === "/")
|
|
654
|
+
return none;
|
|
655
|
+
const matched = COMMANDS2.map((command) => command.name).filter((name) => name.startsWith(line)).sort((left, right) => left.length - right.length);
|
|
656
|
+
if (matched.length === 0 || matched[0] === line)
|
|
657
|
+
return none;
|
|
658
|
+
return { suffix: matched[0].slice(line.length), alternatives: matched.length - 1 };
|
|
659
|
+
}
|
|
660
|
+
function isKnownCommand(name) {
|
|
661
|
+
return COMMANDS2.some((command) => command.name === name);
|
|
662
|
+
}
|
|
663
|
+
var MODE_NAMES = ["plan", "build", "auto", "defender"];
|
|
664
|
+
function isModeName(value) {
|
|
665
|
+
return MODE_NAMES.includes(value);
|
|
666
|
+
}
|
|
667
|
+
function parseModeCommand(input) {
|
|
668
|
+
const bareModeCommand = input.startsWith("/") ? input.slice(1) : "";
|
|
669
|
+
if (isModeName(bareModeCommand))
|
|
670
|
+
return { type: "switch", mode: bareModeCommand };
|
|
671
|
+
const match = /^\/mode(?:\s+(\S+))?$/.exec(input);
|
|
672
|
+
if (!match)
|
|
673
|
+
return null;
|
|
674
|
+
if (!match[1])
|
|
675
|
+
return { type: "show" };
|
|
676
|
+
return isModeName(match[1]) ? { type: "switch", mode: match[1] } : { type: "invalid" };
|
|
677
|
+
}
|
|
678
|
+
function editDistance(left, right) {
|
|
679
|
+
const previous = Array.from({ length: right.length + 1 }, (_unused, index) => index);
|
|
680
|
+
for (let row = 1;row <= left.length; row += 1) {
|
|
681
|
+
let diagonal = previous[0];
|
|
682
|
+
previous[0] = row;
|
|
683
|
+
for (let column = 1;column <= right.length; column += 1) {
|
|
684
|
+
const candidate = Math.min(previous[column] + 1, previous[column - 1] + 1, diagonal + (left[row - 1] === right[column - 1] ? 0 : 1));
|
|
685
|
+
diagonal = previous[column];
|
|
686
|
+
previous[column] = candidate;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
return previous[right.length];
|
|
690
|
+
}
|
|
691
|
+
function suggestCommand(name) {
|
|
692
|
+
const ranked = COMMANDS2.map((command) => ({ name: command.name, distance: editDistance(name, command.name) })).sort((left, right) => left.distance - right.distance);
|
|
693
|
+
const best = ranked[0];
|
|
694
|
+
return best && best.distance <= 2 ? best.name : undefined;
|
|
695
|
+
}
|
|
696
|
+
var KEYBOARD_SHORTCUTS = [
|
|
697
|
+
["menu-move", "↑ / ↓", "Move through any menu — settings, models, the palette"],
|
|
698
|
+
["menu-number", "1-9", "Jump straight to a numbered row in a menu"],
|
|
699
|
+
["menu-take", "Enter / Esc", "Take the highlighted row / leave the menu"],
|
|
700
|
+
["complete-command", "Tab", "Complete a slash command"],
|
|
701
|
+
["complete-path", "@ + path", "Complete a project file mention"],
|
|
702
|
+
["history", "Up / Down", "Search persistent prompt history"],
|
|
703
|
+
["line-ends", "Home / End", "Move to the start / end of the input"],
|
|
704
|
+
["delete", "Alt-Backspace / Ctrl-U", "Delete the previous word / whole input"],
|
|
705
|
+
["clear", "Ctrl-L", "Clear and redraw the terminal"],
|
|
706
|
+
["interrupt", "Ctrl-C", "Interrupt the current turn"],
|
|
707
|
+
["voice", "/voice", "Record a prompt from the microphone"],
|
|
708
|
+
["expand", "Ctrl-O", "Unfold the most recent folded block"],
|
|
709
|
+
["memory", "# fact", "Remember a fact for every future session in this project"]
|
|
710
|
+
];
|
|
711
|
+
function renderKeyboardShortcuts(language = "en") {
|
|
712
|
+
const width = Math.max(...KEYBOARD_SHORTCUTS.map(([, key]) => key.length));
|
|
713
|
+
return KEYBOARD_SHORTCUTS.map(([id, key, description]) => ` ${key.padEnd(width + 2)}${keyboardDescription(language, id, description)}`).join(`
|
|
714
|
+
`);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// packages/archymedes-cli/src/render/guide.ts
|
|
718
|
+
var GUIDE_TOPICS = [
|
|
719
|
+
{
|
|
720
|
+
id: "start",
|
|
721
|
+
title: "Getting started",
|
|
722
|
+
summary: "What Archymedes is, and the first thing to type.",
|
|
723
|
+
body: [
|
|
724
|
+
"Archymedes is a coding agent that works in a scrolling terminal transcript. You type what you want in plain language; it reads files, runs commands and edits code, and shows you what it did as it goes.",
|
|
725
|
+
"Everything it prints stays in your terminal's scrollback, so you can scroll up, select text and pipe a session to a file the way you would with any other command.",
|
|
726
|
+
"Type a request and press Enter. Slash commands do everything else — press / to see them, or open the searchable list.",
|
|
727
|
+
"One rule everywhere: Esc takes you back. Out of a menu, the guide, the file browser, the pager or the editor, Esc returns you to the chat prompt. A lone ? on the prompt shows the short help.",
|
|
728
|
+
"Archymedes starts in simple mode — one line of help and nothing else to learn. Turn ARCHYMEDES_SIMPLE off in /settings for the full banner, starter ideas and the grouped /help."
|
|
729
|
+
],
|
|
730
|
+
examples: [
|
|
731
|
+
{ input: "fix the failing test in cart.ts", effect: "an ordinary request; Archymedes reads, edits and runs the tests" },
|
|
732
|
+
{ input: "?", effect: "the short help: the handful of commands worth knowing first" },
|
|
733
|
+
{ input: "/help all", effect: "every command, with one line each" },
|
|
734
|
+
{ input: "/palette", effect: "search commands by name or by what they do" },
|
|
735
|
+
{ input: "/guide", effect: "open this guide as a screen — arrows to move, / to search, Esc to come back" },
|
|
736
|
+
{ input: "/guide tabs", effect: "print one topic into the transcript, to keep or to copy" },
|
|
737
|
+
{ input: "/exit", effect: "leave; the session is saved and can be resumed" }
|
|
738
|
+
],
|
|
739
|
+
covers: ["/help", "/palette", "/exit", "/keys", "/guide"]
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
id: "modes",
|
|
743
|
+
title: "Permission modes",
|
|
744
|
+
summary: "How much Archymedes may do without asking.",
|
|
745
|
+
body: [
|
|
746
|
+
"Four modes, and you can change them at any time. Plan reads and reasons but cannot write or run commands. Build is the default: edits are proposed and you approve them. Auto applies ordinary workspace edits without asking, while anything sensitive or reaching outside the workspace still stops for you. Defender turns Archymedes into a security reviewer — full tool access to actually scan and fix, but never auto-approved, the same posture as Build.",
|
|
747
|
+
"Defender also looks outward: when web search is configured it checks current advisories against what the project actually depends on, remembers durable findings in project memory so the next review builds on this one, and closes out with concrete, costed remediation resources fitted to how the project actually hosts and deploys — not a generic checklist.",
|
|
748
|
+
"/scan runs the deterministic half of defender's own secrets playbook directly — no model turn, no mode switch needed. It is the same pattern-matching scan_secrets uses, worst severity first: a private key or a live cloud credential ahead of a merely credential-shaped variable name.",
|
|
749
|
+
"The mode is per tab, so one piece of work can be on a short leash while another runs freely."
|
|
750
|
+
],
|
|
751
|
+
examples: [
|
|
752
|
+
{ input: "/plan", effect: "read-only: no writes, no commands" },
|
|
753
|
+
{ input: "/build", effect: "edits ask first (the default)" },
|
|
754
|
+
{ input: "/auto", effect: "ordinary edits apply; sensitive actions still ask" },
|
|
755
|
+
{ input: "/defender", effect: "security review — find and fix real issues; every change still asks" },
|
|
756
|
+
{ input: "/mode", effect: "show which mode you are in" },
|
|
757
|
+
{ input: "/scan", effect: "pattern-based secret scan, worst severity first, no model call" },
|
|
758
|
+
{ input: "/scan src/**", effect: "same scan, limited to files matching the glob" }
|
|
759
|
+
],
|
|
760
|
+
covers: ["/mode", "/plan", "/build", "/auto", "/defender", "/scan"]
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
id: "tabs",
|
|
764
|
+
title: "Tabs: several pieces of work, one at a time",
|
|
765
|
+
summary: "Each tab keeps its own conversation, model, cost and machine — the tab in front is the one that runs.",
|
|
766
|
+
body: [
|
|
767
|
+
"A tab is a separate piece of work with its own conversation, its own running total, its own mode — and its own model and location. One tab can be on a large model against your checkout while another runs a cheaper or open model inside a throwaway remote sandbox.",
|
|
768
|
+
"Only the tab in front runs. Switching away does not leave a turn working in the background — it pauses that piece of work and hands the terminal to another one. That is a deliberate limit rather than a missing feature: a terminal transcript has one bottom, and two agents printing into it at once would interleave into something neither of them said. (Archymedes's desktop window is different — it has a transcript per tab, so its tabs really do run at the same time.)",
|
|
769
|
+
"When you want work running while you do something else, that is what /detach and /jobs are for: a detached job keeps going after you close the terminal, and /watch streams it into the session without taking the prompt.",
|
|
770
|
+
"Tabs keep what they printed. Leave one and come back and the last lines reappear, so you can pick up where you were instead of facing a bare prompt.",
|
|
771
|
+
"The strip above the prompt shows every tab with its model and a mark for where it runs. Closing a tab that started its own sandbox stops that sandbox — and only that one."
|
|
772
|
+
],
|
|
773
|
+
examples: [
|
|
774
|
+
{ input: "/tab new review", effect: "a second tab, same model, same machine" },
|
|
775
|
+
{ input: "/tab new fast --model claude-haiku-4-5-20251001", effect: "a tab on a different model" },
|
|
776
|
+
{ input: "/tab new risky --sandbox e2b", effect: "a tab whose edits land in a remote sandbox, not on your disk" },
|
|
777
|
+
{ input: "/tab 2", effect: "switch to tab 2 and replay where you left off" },
|
|
778
|
+
{ input: "/tab close 2", effect: "close it, stopping its sandbox if it started one" },
|
|
779
|
+
{ input: "/detach run the full test suite", effect: "work that runs while you use another tab" }
|
|
780
|
+
],
|
|
781
|
+
covers: ["/tab"]
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
id: "models",
|
|
785
|
+
title: "Models and providers",
|
|
786
|
+
summary: "Choosing what runs, and what it costs.",
|
|
787
|
+
body: [
|
|
788
|
+
"Archymedes talks to several providers. The picker lists what you can use, with prices, and offers to save a key for anything you cannot use yet. Switching model keeps the conversation — you are changing who answers, not starting over.",
|
|
789
|
+
"The list is the union of what this build knows the prices of and what each provider says it has today, so a model released last week is still offerable — it is simply shown without a price rather than hidden. The live half is cached for six hours; /models refresh asks again.",
|
|
790
|
+
"A model switch applies to the tab you are in. Opening a tab with --model is how you compare two models on the same problem.",
|
|
791
|
+
"Type in /model to narrow a long list; Esc clears the search before it leaves.",
|
|
792
|
+
"archymedes --free needs no key: it runs zero-priced tool models (openrouter/free or an exact model:free ID) through the Archymedes free gateway, which holds the shared OpenRouter key server-side and enforces a daily allowance — 100,000 tokens and 300 requests per UTC day, 20 a minute; tokens are the budget that usually runs out first. Until that gateway is live, free mode uses your own free OpenRouter key (50 free-model requests a day, 1,000 once the account has bought 10 credits). Every request carries a zero price cap and never falls back to a paid provider. When the allowance runs out, it says so and when it resets.",
|
|
793
|
+
"Type /upgrade (or /upgrades) to use your own OpenRouter API key instead: free-mode limits stop applying and requests go straight to OpenRouter. With your own key, --free talks to OpenRouter directly.",
|
|
794
|
+
"For any other OpenRouter model — paid or free — use --provider openrouter with the same key (e.g. --provider openrouter --model openrouter/auto). That path has no price cap and needs no Archymedes Cloud account.",
|
|
795
|
+
"Paste a TYPESAFE_API_KEY in archymedes settings for a Jev second opinion: it annotates approvals with a tool-fit reading and prints a post-turn verdict with probabilities. Advisory only — it never approves, denies, or blocks."
|
|
796
|
+
],
|
|
797
|
+
examples: [
|
|
798
|
+
{ input: "/models", effect: "the list, with prices" },
|
|
799
|
+
{ input: "/models refresh", effect: "ask every provider what it has right now" },
|
|
800
|
+
{ input: "/model", effect: "the picker" },
|
|
801
|
+
{ input: "/model claude-sonnet-5", effect: "switch straight to one, keeping the transcript" },
|
|
802
|
+
{ input: "/fallback openai:gpt-5.4-mini", effect: "retry transient provider failures on this model, only before any output or tool action" },
|
|
803
|
+
{ input: "/providers", effect: "which providers are configured" }
|
|
804
|
+
],
|
|
805
|
+
covers: ["/models", "/model", "/fallback", "/providers", "/settings", "/upgrade"]
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
id: "where",
|
|
809
|
+
title: "Where the work happens",
|
|
810
|
+
summary: "This machine, a container, or a remote sandbox.",
|
|
811
|
+
body: [
|
|
812
|
+
"By default Archymedes edits the directory you started it in. Started with --sandbox e2b it works inside a throwaway remote machine instead, and --sandbox docker uses a local container. Files written there never touch your disk unless you ask for them.",
|
|
813
|
+
"Sandboxes are per tab, so 'try this somewhere I don't mind breaking' is one command rather than a second terminal.",
|
|
814
|
+
"One thing worth knowing: background jobs always run on your machine, even when the session that started them is sandboxed. Archymedes says so each time you start one."
|
|
815
|
+
],
|
|
816
|
+
examples: [
|
|
817
|
+
{ input: "archymedes --sandbox e2b", effect: "the whole session runs in a remote sandbox" },
|
|
818
|
+
{ input: "/tab new spike --sandbox docker", effect: "one tab in a local container" },
|
|
819
|
+
{ input: "/where", effect: "which machine the current tab is using" },
|
|
820
|
+
{ input: "/pull", effect: "copy files out of a sandbox and onto your disk" }
|
|
821
|
+
],
|
|
822
|
+
covers: ["/where", "/pull"]
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
id: "reading",
|
|
826
|
+
title: "Reading what Archymedes did",
|
|
827
|
+
summary: "Code, diffs, test results, and folded detail.",
|
|
828
|
+
body: [
|
|
829
|
+
"When Archymedes writes or edits a file it shows the code, numbered, under the tool line. When it runs tests it re-lays the output into sections with the failures pulled out. When output is long it is folded rather than truncated, and the whole of it is one command away.",
|
|
830
|
+
"The diff shows the actual patch since the last checkpoint — the lines, not a count of them.",
|
|
831
|
+
"/diagnostics asks the language servers installed on this machine what they make of the code: errors, warnings and hints, file by file. It runs no model and changes nothing. A language whose server is not installed is listed as unavailable rather than passed over in silence."
|
|
832
|
+
],
|
|
833
|
+
examples: [
|
|
834
|
+
{ input: "/diff", effect: "the real patch, file by file" },
|
|
835
|
+
{ input: "/diff stat", effect: "just the summary of how much changed" },
|
|
836
|
+
{ input: "/expand", effect: "unfold the last folded block" },
|
|
837
|
+
{ input: "/expand list", effect: "everything that has been folded this session" },
|
|
838
|
+
{ input: "/undo", effect: "revert the last turn — files, conversation, or both" },
|
|
839
|
+
{ input: "/retry", effect: "repeat a failed request when nothing had run or changed" },
|
|
840
|
+
{ input: "/continue", effect: "finish an interrupted task without repeating completed work" },
|
|
841
|
+
{ input: "/todos", effect: "the agent's current plan" },
|
|
842
|
+
{ input: "/task", effect: "the whole task at a glance: request, plan, changes, verification, blockers" },
|
|
843
|
+
{ input: "/route", effect: "the hosted exchange's routing decision for the last turn — chosen model, alternatives, cost vs estimate" },
|
|
844
|
+
{ input: "/diagnostics", effect: "errors and warnings from installed language servers, no model call" },
|
|
845
|
+
{ input: "/diagnostics src/**", effect: "the same, limited to files matching the glob" }
|
|
846
|
+
],
|
|
847
|
+
covers: ["/diff", "/expand", "/undo", "/retry", "/continue", "/todos", "/task", "/route", "/diagnostics"]
|
|
848
|
+
},
|
|
849
|
+
{
|
|
850
|
+
id: "memory",
|
|
851
|
+
title: "Memory",
|
|
852
|
+
summary: "Facts worth keeping between sessions.",
|
|
853
|
+
body: [
|
|
854
|
+
"Telling Archymedes the same thing every session costs you tokens and patience. A remembered fact is prepended to the conversation instead.",
|
|
855
|
+
"Memory is a plain markdown file you can open, edit, commit or delete. Project memory belongs to the repository; personal memory follows you across projects. Nothing is remembered unless you say so."
|
|
856
|
+
],
|
|
857
|
+
examples: [
|
|
858
|
+
{ input: "# we use bun, not npm", effect: "remember a fact about this project" },
|
|
859
|
+
{ input: "/memory", effect: "list what is remembered" },
|
|
860
|
+
{ input: "/memory add --user I prefer terse explanations", effect: "remember something about you, everywhere" },
|
|
861
|
+
{ input: "/memory forget 2", effect: "drop one" }
|
|
862
|
+
],
|
|
863
|
+
covers: ["/memory"]
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
id: "history",
|
|
867
|
+
title: "History",
|
|
868
|
+
summary: "Past conversations, searchable and resumable.",
|
|
869
|
+
body: [
|
|
870
|
+
"Every session is saved, so you can always go back to a normal conversation. /history (or /resume) opens a list of your earlier chats in this folder — arrows to choose, Enter to continue it with its whole context, Esc to stay where you are.",
|
|
871
|
+
"When you start Archymedes in a folder with a recent chat, it offers to continue it: press Enter on the empty prompt to pick it up, or just type to start fresh. ARCHYMEDES_RESUME in /settings changes that to always or never.",
|
|
872
|
+
"Chats belong to the folder they were started in: the list only ever shows this project's chats. In the picker, Del (or Ctrl+D) deletes the highlighted chat after asking — the chat you are in can't be deleted while it is open."
|
|
873
|
+
],
|
|
874
|
+
examples: [
|
|
875
|
+
{ input: "/history", effect: "pick an earlier chat and continue it" },
|
|
876
|
+
{ input: "/resume latest", effect: "continue the most recent one straight away" },
|
|
877
|
+
{ input: "/history list", effect: "print recent sessions, newest first" },
|
|
878
|
+
{ input: "/history search failing tests", effect: "find one by what you typed" },
|
|
879
|
+
{ input: "/history delete --all", effect: "delete every other chat in this folder, after a yes (Del in the picker deletes one)" },
|
|
880
|
+
{ input: "archymedes --resume", effect: "start Archymedes on the picker" },
|
|
881
|
+
{ input: "/export support", effect: "write a compact redacted artifact safe to attach to a support request" },
|
|
882
|
+
{ input: "/clear", effect: "start a fresh thread in this tab" }
|
|
883
|
+
],
|
|
884
|
+
covers: ["/history", "/sessions", "/resume", "/export", "/clear"]
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
id: "cost",
|
|
888
|
+
title: "Cost and pace",
|
|
889
|
+
summary: "Knowing what you are spending, and spending it slower.",
|
|
890
|
+
body: [
|
|
891
|
+
"Every turn is priced in your own currency, and the running total is always on screen. Start with --budget N to approve a cap that is then enforced rather than merely reported.",
|
|
892
|
+
"Slow mode is the other half: it limits how many model rounds and tool calls a turn may use, shortens replies, and pauses between turns. It caps the rate, not the total, and asks before an unusually expensive turn."
|
|
893
|
+
],
|
|
894
|
+
examples: [
|
|
895
|
+
{ input: "/cost", effect: "the breakdown for this session" },
|
|
896
|
+
{ input: "archymedes --budget 20", effect: "approve and enforce a cap" },
|
|
897
|
+
{ input: "/slow", effect: "spend at a gentler pace" },
|
|
898
|
+
{ input: "/slow strict", effect: "slower still, with a pause between turns" },
|
|
899
|
+
{ input: "/slow off", effect: "back to full speed" }
|
|
900
|
+
],
|
|
901
|
+
covers: ["/cost", "/slow"]
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
id: "balance",
|
|
905
|
+
title: "Tracking a spend balance",
|
|
906
|
+
summary: "A local budget the prompt draws down as you work.",
|
|
907
|
+
body: [
|
|
908
|
+
"Archymedes has no hosted wallet. /balance sets a figure — the amount you have decided this project can spend — and after every completed turn Archymedes subtracts that turn's measured cost from it. It is an estimate you own, not a provider statement.",
|
|
909
|
+
"The balance is kept in whatever currency your session shows costs in. /balance 5000 uses that currency; /balance 50 usd or /balance 5000 inr sets it explicitly. /balance on its own shows what remains and what this session has used so far. /balance clear stops tracking.",
|
|
910
|
+
"Once a balance is being tracked, Archymedes warns calmly when it gets low, drops below the critical threshold, or falls unusually fast between turns. Before a demanding task it compares its token-based estimate with the balance and stops before contacting the model when even the conservative estimate cannot fit.",
|
|
911
|
+
"The thresholds default to small USD amounts converted to your currency; ARCHYMEDES_LOW_BALANCE and ARCHYMEDES_CRITICAL_BALANCE override them in /settings."
|
|
912
|
+
],
|
|
913
|
+
examples: [
|
|
914
|
+
{ input: "/balance 5000", effect: "track a 5,000 balance in the display currency" },
|
|
915
|
+
{ input: "/balance 50 usd", effect: "track a balance in a specific currency" },
|
|
916
|
+
{ input: "/balance", effect: "what remains, and what this session has used" },
|
|
917
|
+
{ input: "/balance clear", effect: "stop tracking a balance" }
|
|
918
|
+
],
|
|
919
|
+
covers: ["/balance", "/cost"]
|
|
920
|
+
},
|
|
921
|
+
{
|
|
922
|
+
id: "updating",
|
|
923
|
+
title: "Keeping Archymedes current",
|
|
924
|
+
summary: "Automatic updates by default, with explicit check-only and off controls.",
|
|
925
|
+
body: [
|
|
926
|
+
"Archymedes looks for a new version once a day, at the moment a session starts and never in the middle of one. The check is bounded at three seconds and its failure is silent: an unreachable registry costs one attempt a day, not a slow prompt.",
|
|
927
|
+
"By default Archymedes installs a newer release automatically through whichever package manager installed it; it does not wait for another acceptance prompt. /update check changes that to notification-only behavior, and /update off stops it looking at all.",
|
|
928
|
+
"Automatic installs never happen in a piped or headless run, or in CI, because automation may depend on an exact installed version. A version that fails to install is remembered and not retried, because an update mechanism that fails loudly every launch is one people uninstall.",
|
|
929
|
+
"An install replaces the program on disk; the session you are in keeps running the version it started with until you restart it."
|
|
930
|
+
],
|
|
931
|
+
examples: [
|
|
932
|
+
{ input: "/update", effect: "install the newest version now" },
|
|
933
|
+
{ input: "/update check", effect: "tell you about updates without installing" },
|
|
934
|
+
{ input: "/update off", effect: "stop looking for updates" }
|
|
935
|
+
],
|
|
936
|
+
covers: ["/update"]
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
id: "background",
|
|
940
|
+
title: "Background work",
|
|
941
|
+
summary: "Jobs that outlive the prompt, and research that runs itself.",
|
|
942
|
+
body: [
|
|
943
|
+
"Work can be sent to the background and keeps running after you close the terminal. A watched job streams its output into the session without taking the prompt, so you can read it when you want to and carry on meanwhile.",
|
|
944
|
+
"This — not tabs — is how two things run at once in the terminal. A tab you switch away from is paused; a detached job keeps working.",
|
|
945
|
+
"Wander runs a bounded research lab on a topic, gathers evidence first, and grades its own claims. It can run once or on a schedule.",
|
|
946
|
+
"A job that needs approval says so and waits — attach to it to answer."
|
|
947
|
+
],
|
|
948
|
+
examples: [
|
|
949
|
+
{ input: "/detach update the changelog", effect: "start it in the background" },
|
|
950
|
+
{ input: "/jobs", effect: "what is running, queued or finished" },
|
|
951
|
+
{ input: "/watch 20260814T0132Z-ab12cd", effect: "follow its output without giving up the prompt" },
|
|
952
|
+
{ input: "/watch show <id>", effect: "read what it has said so far" },
|
|
953
|
+
{ input: "/attach <id>", effect: "take the prompt to answer an approval" },
|
|
954
|
+
{ input: "/wander daily coral reefs", effect: "a research lab, every day" }
|
|
955
|
+
],
|
|
956
|
+
covers: ["/detach", "/jobs", "/attach", "/watch", "/wander"]
|
|
957
|
+
},
|
|
958
|
+
{
|
|
959
|
+
id: "panel",
|
|
960
|
+
title: "The control panel",
|
|
961
|
+
summary: "Every tab and job, live, on one screen.",
|
|
962
|
+
body: [
|
|
963
|
+
"The workspace is a full-screen view of everything at once: each tab as a pane showing its live output, what model it is running and where, and each watched job alongside them.",
|
|
964
|
+
"It is a view — it reads the session and changes nothing — so leaving it puts you back exactly where you were.",
|
|
965
|
+
"Inside it: 1-9 select a pane, arrows or Tab move between them, arrows or j/k scroll, g and G jump to the ends, Esc comes back to the chat."
|
|
966
|
+
],
|
|
967
|
+
examples: [
|
|
968
|
+
{ input: "/workspace", effect: "open the panel" },
|
|
969
|
+
{ input: "q", effect: "leave it and return to the prompt" }
|
|
970
|
+
],
|
|
971
|
+
covers: ["/workspace"]
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
id: "look",
|
|
975
|
+
title: "How it looks",
|
|
976
|
+
summary: "Themes, symbols, the fixed workspace, and terminals that need help.",
|
|
977
|
+
body: [
|
|
978
|
+
"Archymedes ships a blueprint theme and three others, and reads themes you write yourself as .tss files in .archymedes/themes — the same format TermUI apps use, so a palette written once works in both. /theme <name> also saves the choice, so it is still your theme tomorrow.",
|
|
979
|
+
"Code is coloured like VS Code — Dark+ on a dark theme, Light+ on a light one — with numbered lines in code blocks and green and red bands on changed lines in diffs. In /settings, Code colours set to theme paints code in the theme's own palette instead, and Line numbers in code blocks turns the numbers off.",
|
|
980
|
+
"Sessions open in the fixed workspace: a mode rail and composer stay anchored while the transcript scrolls underneath. Read history with the mouse wheel or Page Up / Page Down, move a line at a time with Alt+Up / Alt+Down, jump to the top with Ctrl+Home, and press Escape to return to live output. Menus open inside the transcript area so the composer never jumps. Prefer the plain terminal log? Use --layout scrollback, ARCHYMEDES_LAYOUT=scrollback, or /layout. ARCHYMEDES_MOUSE=0 keeps the terminal's own text selection instead of wheel scrolling.",
|
|
981
|
+
"If your terminal draws question marks instead of symbols, --ascii switches to characters every terminal has. If you would rather have a status line pinned to the bottom row, --pin does that, at the cost of your terminal's scrollback. ARCHYMEDES_NO_MOTION=1 keeps the opening geometry and menu focus still."
|
|
982
|
+
],
|
|
983
|
+
examples: [
|
|
984
|
+
{ input: "/theme list", effect: "every theme available, including your own" },
|
|
985
|
+
{ input: "/theme chalkboard", effect: "change the colours immediately" },
|
|
986
|
+
{ input: "/theme where", effect: "where to put a theme file of your own" },
|
|
987
|
+
{ input: "/layout fixed", effect: "mode rail, anchored composer, menus in the transcript" },
|
|
988
|
+
{ input: "/find TypeError", effect: "search this tab's history; /find again for the next match, Esc to return" },
|
|
989
|
+
{ input: "/pager", effect: "open the whole transcript to scroll and search — in less, or the built-in pager on Windows (Esc back)" },
|
|
990
|
+
{ input: "/settings", effect: "theme, code colours, line numbers, simple mode, startup and free-mode savings" },
|
|
991
|
+
{ input: "/layout scrollback", effect: "return to the ordinary terminal log" },
|
|
992
|
+
{ input: "archymedes --ascii", effect: "plain characters for terminals that mangle symbols" }
|
|
993
|
+
],
|
|
994
|
+
covers: ["/theme", "/layout", "/find", "/pager", "/settings"]
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
id: "extend",
|
|
998
|
+
title: "Tools and voice",
|
|
999
|
+
summary: "What the agent can call, and talking to it.",
|
|
1000
|
+
body: [
|
|
1001
|
+
"Archymedes has a fixed set of built-in tools, and a project can add more through skills, plugins, MCP servers and hooks. The tools command shows exactly what is available in this project.",
|
|
1002
|
+
"A prompt you type often can be saved as a command: `.archymedes/commands/review.md` becomes `/review`, shared with everyone on the project, and the same file in your config directory's `commands` folder is yours alone. `$ARGUMENTS` in the file is replaced by whatever you type after the name. Built-in commands cannot be replaced.",
|
|
1003
|
+
"Voice input records or transcribes a prompt you can edit before sending.",
|
|
1004
|
+
"The file picker is the project as a tree rather than a flat completion list: expand a folder, preview a file's contents, and pick one to drop an @mention into the line you're writing.",
|
|
1005
|
+
"Mentioning an image — `what is wrong in @screenshots/bug.png?` — sends the picture itself with that request (PNG, JPEG, GIF or WebP, up to four, 5 MB each, from a local workspace). The model must accept images; the saved conversation keeps a note of the attachment rather than the image.",
|
|
1006
|
+
"`/edit <path>` opens a file in the built-in editor. It reads and writes through the workspace like every tool does, so it edits the sandbox's copy in a sandboxed session rather than a same-named file on this machine, and it only writes when you save — quitting really is a discard."
|
|
1007
|
+
],
|
|
1008
|
+
examples: [
|
|
1009
|
+
{ input: "/tools", effect: "every tool, skill, plugin, MCP server and hook" },
|
|
1010
|
+
{ input: "/review src/auth.ts", effect: "run .archymedes/commands/review.md with src/auth.ts as $ARGUMENTS" },
|
|
1011
|
+
{ input: "/voice", effect: "record a prompt, then edit it before sending" },
|
|
1012
|
+
{ input: "/cat README.md", effect: "read a file in the transcript, with markdown formatting or numbered code" },
|
|
1013
|
+
{ input: "/cat docs/diagram.png", effect: "draw a PNG in the transcript (ARCHYMEDES_IMAGES=kitty for real pixels in scrollback)" },
|
|
1014
|
+
{ input: "/files", effect: "browse the project tree and @mention a file" },
|
|
1015
|
+
{ input: "/edit src/app.ts", effect: "open a file in the built-in editor" }
|
|
1016
|
+
],
|
|
1017
|
+
covers: ["/tools", "/voice", "/files", "/edit", "/cat"]
|
|
1018
|
+
}
|
|
1019
|
+
];
|
|
1020
|
+
function findTopic(id) {
|
|
1021
|
+
const wanted = id.trim().toLowerCase();
|
|
1022
|
+
return GUIDE_TOPICS.find((topic) => topic.id === wanted) ?? GUIDE_TOPICS.find((topic) => topic.title.toLowerCase().includes(wanted));
|
|
1023
|
+
}
|
|
1024
|
+
function searchTopics(query) {
|
|
1025
|
+
const needle = query.trim().toLowerCase();
|
|
1026
|
+
if (!needle)
|
|
1027
|
+
return GUIDE_TOPICS;
|
|
1028
|
+
return GUIDE_TOPICS.filter((topic) => [topic.title, topic.summary, ...topic.body, ...(topic.examples ?? []).flatMap((example) => [example.input, example.effect])].some((text) => text.toLowerCase().includes(needle)));
|
|
1029
|
+
}
|
|
1030
|
+
function wrapText(text, width) {
|
|
1031
|
+
const measure = Math.max(1, width);
|
|
1032
|
+
const lines = [];
|
|
1033
|
+
let current = "";
|
|
1034
|
+
for (const word of text.split(/\s+/).filter(Boolean)) {
|
|
1035
|
+
if (current === "") {
|
|
1036
|
+
current = word;
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
if (visibleWidth(current) + 1 + visibleWidth(word) <= measure)
|
|
1040
|
+
current = `${current} ${word}`;
|
|
1041
|
+
else {
|
|
1042
|
+
lines.push(current);
|
|
1043
|
+
current = word;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
if (current !== "")
|
|
1047
|
+
lines.push(current);
|
|
1048
|
+
return lines;
|
|
1049
|
+
}
|
|
1050
|
+
function renderGuideIndex(style) {
|
|
1051
|
+
const glyphs = style.glyphs ?? UNICODE_GLYPHS;
|
|
1052
|
+
const available = Math.max(8, style.width - GUTTER.length);
|
|
1053
|
+
const longest = Math.max(...GUIDE_TOPICS.map((topic) => topic.id.length));
|
|
1054
|
+
const rows = GUIDE_TOPICS.map((topic) => {
|
|
1055
|
+
const id = paint(topic.id.padEnd(longest + 2), style.depth === "none" ? "" : DIM, style.depth);
|
|
1056
|
+
return `${GUTTER}${id}${clip(topic.title, Math.max(0, available - longest - 2), glyphs)}`;
|
|
1057
|
+
});
|
|
1058
|
+
return [
|
|
1059
|
+
rule(style, { label: "archymedes guide", tone: "accent" }),
|
|
1060
|
+
...rows,
|
|
1061
|
+
"",
|
|
1062
|
+
note(clip("/guide <topic> · /guide search <text> · /guide all", Math.max(0, style.width - GUTTER.length * 2), glyphs), style)
|
|
1063
|
+
].join(`
|
|
1064
|
+
`);
|
|
1065
|
+
}
|
|
1066
|
+
function renderGuideTopic(topic, style) {
|
|
1067
|
+
const glyphs = style.glyphs ?? UNICODE_GLYPHS;
|
|
1068
|
+
const measure = Math.max(20, style.width - GUTTER.length * 2);
|
|
1069
|
+
const parts = [
|
|
1070
|
+
rule(style, { label: topic.title, tone: "accent" }),
|
|
1071
|
+
""
|
|
1072
|
+
];
|
|
1073
|
+
for (const paragraph of topic.body) {
|
|
1074
|
+
for (const line of wrapText(paragraph, measure))
|
|
1075
|
+
parts.push(`${GUTTER}${line}`);
|
|
1076
|
+
parts.push("");
|
|
1077
|
+
}
|
|
1078
|
+
if (topic.examples && topic.examples.length > 0) {
|
|
1079
|
+
const inner = Math.max(8, style.width - GUTTER.length - 4);
|
|
1080
|
+
const longest = Math.max(...topic.examples.map((example) => visibleWidth(example.input)));
|
|
1081
|
+
const bold = (text) => paintAll(text, [style.depth === "none" ? "" : BOLD], style.depth);
|
|
1082
|
+
const dim = (text) => paint(text, style.depth === "none" ? "" : DIM, style.depth);
|
|
1083
|
+
const stacked = longest > Math.floor(inner / 2);
|
|
1084
|
+
const rows = topic.examples.flatMap((example) => stacked ? [bold(example.input), ` ${dim(clip(example.effect, inner - 2, glyphs))}`] : [`${bold(example.input.padEnd(longest + 2))}${dim(clip(example.effect, inner - longest - 2, glyphs))}`]);
|
|
1085
|
+
parts.push(panel(rows, style, { title: "try", tone: "accent" }));
|
|
1086
|
+
}
|
|
1087
|
+
const next = GUIDE_TOPICS[GUIDE_TOPICS.indexOf(topic) + 1];
|
|
1088
|
+
if (next) {
|
|
1089
|
+
parts.push(note(clip(`next: /guide ${next.id} ${glyphs.middot} ${next.title}`, Math.max(0, style.width - GUTTER.length * 2), glyphs), style));
|
|
1090
|
+
}
|
|
1091
|
+
return parts.join(`
|
|
1092
|
+
`);
|
|
1093
|
+
}
|
|
1094
|
+
function renderWholeGuide(style) {
|
|
1095
|
+
return [
|
|
1096
|
+
renderGuideIndex(style),
|
|
1097
|
+
"",
|
|
1098
|
+
...GUIDE_TOPICS.map((topic) => renderGuideTopic(topic, style))
|
|
1099
|
+
].join(`
|
|
1100
|
+
`);
|
|
1101
|
+
}
|
|
1102
|
+
function parseGuideCommand(input) {
|
|
1103
|
+
const match = /^\/(?:guide|tutorial)(?:\s+([\s\S]*))?$/.exec(input.trim());
|
|
1104
|
+
if (!match)
|
|
1105
|
+
return null;
|
|
1106
|
+
const rest = (match[1] ?? "").trim().replace(/\s+/g, " ");
|
|
1107
|
+
if (!rest)
|
|
1108
|
+
return { kind: "index" };
|
|
1109
|
+
const [verb, ...words] = rest.split(" ");
|
|
1110
|
+
if (verb.toLowerCase() === "all")
|
|
1111
|
+
return { kind: "all" };
|
|
1112
|
+
if (verb.toLowerCase() === "search") {
|
|
1113
|
+
const query = words.join(" ").trim();
|
|
1114
|
+
return query ? { kind: "search", query } : { kind: "index" };
|
|
1115
|
+
}
|
|
1116
|
+
return findTopic(rest) ? { kind: "topic", id: findTopic(rest).id } : { kind: "unknown", id: rest };
|
|
1117
|
+
}
|
|
590
1118
|
|
|
591
|
-
export { newViewport, applyViewport, visibleLines, scrollFraction, CONTROL_LANGUAGES, resolveControlLanguage, controlLabel,
|
|
1119
|
+
export { newViewport, maxTop, applyViewport, visibleLines, atTop, atBottom, scrollFraction, CONTROL_LANGUAGES, resolveControlLanguage, controlLabel, t, COMMANDS2 as COMMANDS, renderCommandHelp, suggestionsFor, inlineCompletion, isKnownCommand, parseModeCommand, suggestCommand, renderKeyboardShortcuts, GUIDE_TOPICS, findTopic, searchTopics, wrapText, renderGuideIndex, renderGuideTopic, renderWholeGuide, parseGuideCommand };
|