magus-cli 0.0.0-placeholder → 7.1.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 +309 -0
- package/bin/magus.js +170 -0
- package/package.json +67 -3
- package/scripts/build-binaries.ts +113 -0
- package/scripts/capture-builtin.ts +412 -0
- package/scripts/check-optional-deps.ts +49 -0
- package/scripts/test-isolated.ts +49 -0
- package/scripts/verify-binary.ts +95 -0
- package/scripts/verify-community-registry.ts +272 -0
- package/src/__tests__/active-closure.test.ts +184 -0
- package/src/__tests__/alias-adopt.test.ts +365 -0
- package/src/__tests__/alias-parser.test.ts +417 -0
- package/src/__tests__/alias-shell-writer.test.ts +681 -0
- package/src/__tests__/alias-store.test.ts +163 -0
- package/src/__tests__/binary-signing.test.ts +120 -0
- package/src/__tests__/blackbox-active-closure.test.ts +119 -0
- package/src/__tests__/blackbox-convergence.test.ts +334 -0
- package/src/__tests__/blackbox-parity.test.ts +725 -0
- package/src/__tests__/blackbox-plugin-state.test.ts +545 -0
- package/src/__tests__/blackbox-tool-state.test.ts +669 -0
- package/src/__tests__/cache-provenance.test.ts +84 -0
- package/src/__tests__/capture-builtin.test.ts +92 -0
- package/src/__tests__/catalog-cache-store.test.ts +309 -0
- package/src/__tests__/catalog-notice.test.ts +155 -0
- package/src/__tests__/cli-ansi.test.ts +297 -0
- package/src/__tests__/cli-apply-seams.test.ts +288 -0
- package/src/__tests__/cli-live.test.ts +391 -0
- package/src/__tests__/cli-router.test.ts +240 -0
- package/src/__tests__/cli-tool-commands.test.ts +239 -0
- package/src/__tests__/cli-tools-adapter.test.ts +294 -0
- package/src/__tests__/cli-update-view.test.ts +524 -0
- package/src/__tests__/community-fetch.test.ts +545 -0
- package/src/__tests__/community-registry.test.ts +269 -0
- package/src/__tests__/community-staleness.test.ts +720 -0
- package/src/__tests__/component-badge.test.ts +98 -0
- package/src/__tests__/component-icons.test.ts +94 -0
- package/src/__tests__/content-drift.test.ts +353 -0
- package/src/__tests__/conventions-integration.test.ts +830 -0
- package/src/__tests__/conventions-manager.test.ts +1236 -0
- package/src/__tests__/doctor-bins.test.ts +43 -0
- package/src/__tests__/doctor.test.ts +463 -0
- package/src/__tests__/dotenv.test.ts +195 -0
- package/src/__tests__/enabled-not-installed.test.ts +94 -0
- package/src/__tests__/file-locking.test.ts +208 -0
- package/src/__tests__/fixtures/tool-probe/brew-list-versions-tmux.txt +2 -0
- package/src/__tests__/fixtures/tool-probe/brew-outdated-behind-homebrew-spec.txt +29 -0
- package/src/__tests__/fixtures/tool-probe/brew-outdated-tmux-current.txt +7 -0
- package/src/__tests__/fixtures/tool-probe/brew-outdated-unknown-formula.txt +6 -0
- package/src/__tests__/fixtures/tool-probe/go-version-m-tmux-mcp.txt +27 -0
- package/src/__tests__/fixtures/tool-probe/npm-dist-tags-missing-scoped.txt +4 -0
- package/src/__tests__/fixtures/tool-probe/npm-dist-tags-openai-codex.txt +4 -0
- package/src/__tests__/fixtures/tool-probe/pypi-cowsay.txt +3 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-browser-use.toml +10 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-cowsay-git.toml +8 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-cowsay-pinned.toml +8 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-graphifyy.toml +7 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-mcp.toml +6 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-serena-agent.toml +9 -0
- package/src/__tests__/fixtures/tool-probe/uv-tool-list-outdated-pinned.txt +5 -0
- package/src/__tests__/fixtures/tool-probe/uv-tool-list-outdated.txt +11 -0
- package/src/__tests__/fixtures/tool-probe/uv-tool-list.txt +22 -0
- package/src/__tests__/footer-hints.test.ts +40 -0
- package/src/__tests__/format-command.test.ts +78 -0
- package/src/__tests__/git-worktree.test.ts +108 -0
- package/src/__tests__/github-budget.test.ts +206 -0
- package/src/__tests__/gitignore-check.test.ts +71 -0
- package/src/__tests__/gitignore-detector.test.ts +176 -0
- package/src/__tests__/gitignore-fixer.test.ts +317 -0
- package/src/__tests__/gitignore-resolver.test.ts +193 -0
- package/src/__tests__/gitignore-service.test.ts +137 -0
- package/src/__tests__/go-module.test.ts +43 -0
- package/src/__tests__/hook-import-policy.test.ts +90 -0
- package/src/__tests__/hook-process.test.ts +256 -0
- package/src/__tests__/hook-registration.test.ts +224 -0
- package/src/__tests__/install-command.test.ts +81 -0
- package/src/__tests__/install-plan.test.ts +104 -0
- package/src/__tests__/launcher-signal.test.ts +308 -0
- package/src/__tests__/local-marketplace-scan.test.ts +248 -0
- package/src/__tests__/manifest.test.ts +281 -0
- package/src/__tests__/markdown-renderer.test.ts +142 -0
- package/src/__tests__/marketplace-badge.test.ts +140 -0
- package/src/__tests__/marketplace-refresh.test.ts +340 -0
- package/src/__tests__/marketplace-version.test.ts +84 -0
- package/src/__tests__/marketplaces.test.ts +128 -0
- package/src/__tests__/mate-availability.test.ts +156 -0
- package/src/__tests__/mate-catalog.test.ts +295 -0
- package/src/__tests__/mcp-registry.test.ts +158 -0
- package/src/__tests__/model-visuals.test.tsx +2466 -0
- package/src/__tests__/models-adapter.test.ts +332 -0
- package/src/__tests__/models-cli.test.ts +273 -0
- package/src/__tests__/models-core.test.ts +802 -0
- package/src/__tests__/models-manager.test.ts +500 -0
- package/src/__tests__/models-presets-marketplace.test.ts +168 -0
- package/src/__tests__/models-screen-state.test.ts +315 -0
- package/src/__tests__/open-file.test.ts +59 -0
- package/src/__tests__/owned-marketplaces.test.ts +93 -0
- package/src/__tests__/plugin-checkout.test.ts +66 -0
- package/src/__tests__/plugin-cli-argv.test.ts +109 -0
- package/src/__tests__/plugin-contents.test.ts +371 -0
- package/src/__tests__/plugin-manager-fallback.test.ts +320 -0
- package/src/__tests__/plugin-releases.test.ts +98 -0
- package/src/__tests__/plugin-requires.test.ts +187 -0
- package/src/__tests__/plugin-row-text.test.ts +345 -0
- package/src/__tests__/plugin-setup.test.ts +1016 -0
- package/src/__tests__/profile-adopt.test.ts +177 -0
- package/src/__tests__/profile-command.test.ts +110 -0
- package/src/__tests__/profile-materializer.test.ts +238 -0
- package/src/__tests__/profile-sync.test.ts +154 -0
- package/src/__tests__/registry-version-resolution.test.ts +88 -0
- package/src/__tests__/resolve-executable.test.ts +42 -0
- package/src/__tests__/resolver.test.ts +351 -0
- package/src/__tests__/scope-action.test.ts +134 -0
- package/src/__tests__/scope-squares.test.tsx +164 -0
- package/src/__tests__/selection-scope.test.ts +100 -0
- package/src/__tests__/settings-file.test.ts +179 -0
- package/src/__tests__/shell-script-callers.test.ts +92 -0
- package/src/__tests__/skill-install-files.test.ts +136 -0
- package/src/__tests__/skills-catalog-client.test.ts +48 -0
- package/src/__tests__/style-wrap.test.ts +220 -0
- package/src/__tests__/styles-manager.test.ts +1098 -0
- package/src/__tests__/styles-origins.test.ts +414 -0
- package/src/__tests__/styles-screen-state.test.ts +457 -0
- package/src/__tests__/styles-status-line.test.ts +72 -0
- package/src/__tests__/styles-sync.test.ts +452 -0
- package/src/__tests__/symlink-manager.test.ts +180 -0
- package/src/__tests__/tabbar-layout.test.ts +100 -0
- package/src/__tests__/terminology-filler.test.ts +214 -0
- package/src/__tests__/theme-adaptive-colors.test.ts +354 -0
- package/src/__tests__/theme-env.test.ts +310 -0
- package/src/__tests__/theme-resolve.test.ts +380 -0
- package/src/__tests__/tool-probe.test.ts +1400 -0
- package/src/__tests__/toolchain.test.ts +232 -0
- package/src/__tests__/ui-version-writers.test.ts +88 -0
- package/src/__tests__/update-apply.test.ts +637 -0
- package/src/__tests__/update-plan.test.ts +1396 -0
- package/src/__tests__/uppercase-keybindings.test.ts +101 -0
- package/src/__tests__/useGitignoreModal.test.ts +79 -0
- package/src/__tests__/version-check-refresh.test.ts +45 -0
- package/src/__tests__/version-snapshot.test.ts +212 -0
- package/src/__tests__/version-unknown-state.test.ts +403 -0
- package/src/__tests__/worktree-registry-resolution.test.ts +107 -0
- package/src/cli/ansi.ts +528 -0
- package/src/cli/bootstrap.ts +134 -0
- package/src/cli/doctor.ts +218 -0
- package/src/cli/hook.ts +129 -0
- package/src/cli/install.ts +382 -0
- package/src/cli/live.ts +374 -0
- package/src/cli/models.ts +297 -0
- package/src/cli/profile.ts +197 -0
- package/src/cli/prompt.ts +32 -0
- package/src/cli/router.ts +264 -0
- package/src/cli/update-view.ts +717 -0
- package/src/cli/update.ts +600 -0
- package/src/cli/upgrade.ts +97 -0
- package/src/data/alias-flags.ts +301 -0
- package/src/data/cli-tools.ts +146 -0
- package/src/data/community-styles.ts +531 -0
- package/src/data/gitignore-defaults.ts +33 -0
- package/src/data/gitignore-reasons.ts +93 -0
- package/src/data/gitignore-templates.ts +25 -0
- package/src/data/marketplaces.ts +278 -0
- package/src/data/mcp-servers.ts +51 -0
- package/src/data/models-presets.ts +329 -0
- package/src/data/predefined-profiles.ts +252 -0
- package/src/data/settings-catalog.ts +714 -0
- package/src/data/skill-repos.ts +91 -0
- package/src/data/styles/asd-ste100.md +38 -0
- package/src/data/styles/calibrated.md +24 -0
- package/src/data/styles/direct.md +23 -0
- package/src/data/styles/evidence-first.md +22 -0
- package/src/data/styles/explanatory.md +23 -0
- package/src/data/styles/index.ts +57 -0
- package/src/data/styles/no-slop.md +32 -0
- package/src/data/styles/plain-language.md +23 -0
- package/src/data/styles/structured.md +30 -0
- package/src/data/styles/terminology.md +28 -0
- package/src/data/styles/terse.md +20 -0
- package/src/main.tsx +110 -0
- package/src/markdown.d.ts +13 -0
- package/src/opentui.d.ts +203 -0
- package/src/services/active-closure.ts +117 -0
- package/src/services/alias-settings.ts +60 -0
- package/src/services/alias-shell-writer.ts +1244 -0
- package/src/services/alias-store.ts +172 -0
- package/src/services/binary-signing.ts +78 -0
- package/src/services/catalog-cache-store.ts +316 -0
- package/src/services/claude-cli.ts +304 -0
- package/src/services/claude-runner.ts +31 -0
- package/src/services/claude-settings.ts +1564 -0
- package/src/services/cli-tool-commands.ts +133 -0
- package/src/services/community-fetcher.ts +90 -0
- package/src/services/community-styles.ts +1194 -0
- package/src/services/content-drift.ts +208 -0
- package/src/services/conventions-manager.ts +945 -0
- package/src/services/doctor-bins.ts +63 -0
- package/src/services/doctor.ts +570 -0
- package/src/services/dotenv.ts +181 -0
- package/src/services/git-worktree.ts +129 -0
- package/src/services/github-budget.ts +274 -0
- package/src/services/gitignore-check.ts +63 -0
- package/src/services/gitignore-detector.ts +167 -0
- package/src/services/gitignore-fixer.ts +280 -0
- package/src/services/gitignore-resolver.ts +208 -0
- package/src/services/gitignore-service.ts +167 -0
- package/src/services/hook-registration.ts +218 -0
- package/src/services/install-plan.ts +107 -0
- package/src/services/local-marketplace.ts +657 -0
- package/src/services/manifest.ts +250 -0
- package/src/services/marketplace-catalog-git.ts +173 -0
- package/src/services/marketplace-catalog.ts +129 -0
- package/src/services/marketplace-fetcher.ts +400 -0
- package/src/services/marketplace-refresh.ts +245 -0
- package/src/services/marketplace-sync.ts +145 -0
- package/src/services/mate-availability.ts +133 -0
- package/src/services/mate-catalog.ts +265 -0
- package/src/services/mcp-registry.ts +171 -0
- package/src/services/models-core.ts +969 -0
- package/src/services/models-manager.ts +606 -0
- package/src/services/plugin-checkout.ts +186 -0
- package/src/services/plugin-manager.ts +847 -0
- package/src/services/plugin-mcp-config.ts +242 -0
- package/src/services/plugin-releases.ts +47 -0
- package/src/services/plugin-requires.ts +227 -0
- package/src/services/plugin-setup.ts +965 -0
- package/src/services/profile-adopt.ts +206 -0
- package/src/services/profile-materializer.ts +133 -0
- package/src/services/profile-sync.ts +158 -0
- package/src/services/profiles.ts +225 -0
- package/src/services/resolver.ts +377 -0
- package/src/services/settings-file.ts +69 -0
- package/src/services/settings-manager.ts +307 -0
- package/src/services/skills-manager.ts +1111 -0
- package/src/services/skillsmp-client.ts +193 -0
- package/src/services/styles-manager.ts +1294 -0
- package/src/services/symlink-manager.ts +189 -0
- package/src/services/terminology-filler.ts +266 -0
- package/src/services/tool-probe.ts +1637 -0
- package/src/services/toolchain.ts +431 -0
- package/src/services/update-engine.ts +691 -0
- package/src/services/update-plan.ts +820 -0
- package/src/services/version-check.ts +127 -0
- package/src/services/version-snapshot.ts +189 -0
- package/src/tui.tsx +112 -0
- package/src/types/bun.d.ts +27 -0
- package/src/types/gitignore.ts +68 -0
- package/src/types/index.ts +447 -0
- package/src/ui/App.tsx +535 -0
- package/src/ui/adapters/catalogNotice.ts +122 -0
- package/src/ui/adapters/cliToolsAdapter.ts +176 -0
- package/src/ui/adapters/modelsAdapter.ts +213 -0
- package/src/ui/adapters/pluginsAdapter.ts +413 -0
- package/src/ui/adapters/settingsAdapter.ts +168 -0
- package/src/ui/adapters/skillsAdapter.ts +229 -0
- package/src/ui/adapters/stylesAdapter.ts +403 -0
- package/src/ui/components/CategoryHeader.tsx +64 -0
- package/src/ui/components/EmptyFilterState.tsx +32 -0
- package/src/ui/components/FlagDetailEditor.tsx +0 -0
- package/src/ui/components/ScopeIndicator.tsx +63 -0
- package/src/ui/components/ScrollableList.tsx +95 -0
- package/src/ui/components/SearchInput.tsx +57 -0
- package/src/ui/components/StyledText.tsx +75 -0
- package/src/ui/components/TabBar.tsx +101 -0
- package/src/ui/components/layout/FooterHints.tsx +78 -0
- package/src/ui/components/layout/Panel.tsx +63 -0
- package/src/ui/components/layout/ProgressBar.tsx +44 -0
- package/src/ui/components/layout/ScopeTabs.tsx +54 -0
- package/src/ui/components/layout/ScreenLayout.tsx +321 -0
- package/src/ui/components/layout/index.ts +9 -0
- package/src/ui/components/modals/ConfirmModal.tsx +62 -0
- package/src/ui/components/modals/InputModal.tsx +75 -0
- package/src/ui/components/modals/LoadingModal.tsx +51 -0
- package/src/ui/components/modals/MessageModal.tsx +66 -0
- package/src/ui/components/modals/ModalContainer.tsx +159 -0
- package/src/ui/components/modals/SelectModal.tsx +90 -0
- package/src/ui/components/modals/index.ts +6 -0
- package/src/ui/components/primitives/ActionHints.tsx +44 -0
- package/src/ui/components/primitives/DetailSection.tsx +23 -0
- package/src/ui/components/primitives/KeyValueLine.tsx +26 -0
- package/src/ui/components/primitives/ListCategoryRow.tsx +45 -0
- package/src/ui/components/primitives/MetaText.tsx +40 -0
- package/src/ui/components/primitives/ScopeDetail.tsx +67 -0
- package/src/ui/components/primitives/ScopeSquares.tsx +58 -0
- package/src/ui/components/primitives/SelectableRow.tsx +30 -0
- package/src/ui/components/primitives/index.ts +9 -0
- package/src/ui/hooks/index.ts +2 -0
- package/src/ui/hooks/useGitignoreModal.ts +95 -0
- package/src/ui/hooks/useKeyboard.ts +24 -0
- package/src/ui/hooks/useKeyboardHandler.ts +63 -0
- package/src/ui/registry.ts +33 -0
- package/src/ui/renderers/cliToolRenderers.tsx +326 -0
- package/src/ui/renderers/gitignoreRenderers.tsx +80 -0
- package/src/ui/renderers/markdownRenderer.tsx +302 -0
- package/src/ui/renderers/mcpRenderers.tsx +149 -0
- package/src/ui/renderers/modelRenderers.tsx +1309 -0
- package/src/ui/renderers/modelVisuals.tsx +1402 -0
- package/src/ui/renderers/pluginRenderers.tsx +1089 -0
- package/src/ui/renderers/pluginRowText.ts +70 -0
- package/src/ui/renderers/pluginStatusText.ts +56 -0
- package/src/ui/renderers/profileRenderers.tsx +406 -0
- package/src/ui/renderers/settingsRenderers.tsx +218 -0
- package/src/ui/renderers/skillRenderers.tsx +479 -0
- package/src/ui/renderers/styleRenderers.tsx +813 -0
- package/src/ui/screens/AliasScreen.tsx +1558 -0
- package/src/ui/screens/CliToolsScreen.tsx +367 -0
- package/src/ui/screens/EnvVarsScreen.tsx +221 -0
- package/src/ui/screens/GitignoreScreen.tsx +562 -0
- package/src/ui/screens/McpRegistryScreen.tsx +366 -0
- package/src/ui/screens/McpScreen.tsx +256 -0
- package/src/ui/screens/ModelsScreen.tsx +542 -0
- package/src/ui/screens/PluginsScreen.tsx +1423 -0
- package/src/ui/screens/ProfilesScreen.tsx +438 -0
- package/src/ui/screens/SkillsScreen.tsx +853 -0
- package/src/ui/screens/StylesScreen.tsx +1064 -0
- package/src/ui/screens/index.ts +11 -0
- package/src/ui/state/AppContext.tsx +233 -0
- package/src/ui/state/DimensionsContext.tsx +154 -0
- package/src/ui/state/reducer.ts +810 -0
- package/src/ui/state/types.ts +503 -0
- package/src/ui/theme-mode.ts +440 -0
- package/src/ui/theme-resolve.ts +147 -0
- package/src/ui/theme.ts +175 -0
- package/src/utils/clipboard.ts +58 -0
- package/src/utils/command-utils.ts +149 -0
- package/src/utils/config-dir.ts +45 -0
- package/src/utils/file-locking.ts +146 -0
- package/src/utils/fuzzy-search.ts +138 -0
- package/src/utils/go-module.ts +36 -0
- package/src/utils/open-file.ts +84 -0
- package/src/utils/run.ts +67 -0
- package/src/utils/string-utils.ts +88 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import { theme } from "../theme.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Markdown → OpenTUI elements, for the detail pane.
|
|
6
|
+
*
|
|
7
|
+
* Why not a library. `glow` is a Go binary: it cannot ship inside
|
|
8
|
+
* `bun build --compile`, and shelling out to something the user may not have
|
|
9
|
+
* installed turns a display detail into a runtime dependency. The JS options
|
|
10
|
+
* (`marked-terminal` and friends) emit ANSI escape sequences, which is the
|
|
11
|
+
* wrong output format — OpenTUI composes `<text>`/`<span>` elements with theme
|
|
12
|
+
* colours, and feeding it raw escapes bypasses the theme and breaks on a
|
|
13
|
+
* re-render. This module is ~200 lines and emits elements directly.
|
|
14
|
+
*
|
|
15
|
+
* Deliberately partial. It covers what SKILL.md files actually contain:
|
|
16
|
+
* headings, fenced code, tables, rules, lists, blockquotes, and inline
|
|
17
|
+
* bold/code. Anything unrecognised falls through as plain text, which is what
|
|
18
|
+
* the pane showed for everything before.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export interface MarkdownRenderResult {
|
|
22
|
+
nodes: React.ReactNode[];
|
|
23
|
+
/** Lines beyond `maxLines` that were not rendered. */
|
|
24
|
+
withheld: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface RenderOptions {
|
|
28
|
+
/** Columns available to the pane, used to size table columns. */
|
|
29
|
+
width: number;
|
|
30
|
+
/** Render-cost guard; the pane itself scrolls. */
|
|
31
|
+
maxLines: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ─── Inline spans ────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Split a line into `code`, **bold** and plain runs.
|
|
38
|
+
*
|
|
39
|
+
* One pass over both markers rather than nested passes: a nested pass would
|
|
40
|
+
* re-scan text already claimed by a code span and turn `` `a**b` `` into a bold
|
|
41
|
+
* run inside code.
|
|
42
|
+
*/
|
|
43
|
+
function inlineSpans(text: string, keyPrefix: string): React.ReactNode[] {
|
|
44
|
+
const nodes: React.ReactNode[] = [];
|
|
45
|
+
const pattern = /(`[^`]+`)|(\*\*[^*]+\*\*)/g;
|
|
46
|
+
let last = 0;
|
|
47
|
+
let match: RegExpExecArray | null = pattern.exec(text);
|
|
48
|
+
let i = 0;
|
|
49
|
+
|
|
50
|
+
while (match) {
|
|
51
|
+
if (match.index > last) {
|
|
52
|
+
nodes.push(
|
|
53
|
+
<span key={`${keyPrefix}-t${i}`}>{text.slice(last, match.index)}</span>,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (match[1]) {
|
|
57
|
+
nodes.push(
|
|
58
|
+
<span key={`${keyPrefix}-c${i}`} fg={theme.colors.link}>
|
|
59
|
+
{match[1].slice(1, -1)}
|
|
60
|
+
</span>,
|
|
61
|
+
);
|
|
62
|
+
} else {
|
|
63
|
+
nodes.push(
|
|
64
|
+
<strong key={`${keyPrefix}-b${i}`}>{match[2].slice(2, -2)}</strong>,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
last = match.index + match[0].length;
|
|
68
|
+
i += 1;
|
|
69
|
+
match = pattern.exec(text);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (last < text.length) {
|
|
73
|
+
nodes.push(<span key={`${keyPrefix}-t${i}`}>{text.slice(last)}</span>);
|
|
74
|
+
}
|
|
75
|
+
return nodes;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ─── Tables ──────────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Strip inline markers from a table cell.
|
|
82
|
+
*
|
|
83
|
+
* Cells are padded to a column width, so they cannot carry `<span>` children
|
|
84
|
+
* the way body text does without the padding and the spans disagreeing about
|
|
85
|
+
* length. Removing the markers is the honest trade: a cell reading
|
|
86
|
+
* `` `context`, `callers` `` printed its backticks verbatim, which is the raw
|
|
87
|
+
* source leaking into a rendered view.
|
|
88
|
+
*/
|
|
89
|
+
function plainCell(cell: string): string {
|
|
90
|
+
return cell
|
|
91
|
+
.trim()
|
|
92
|
+
.replace(/`([^`]+)`/g, "$1")
|
|
93
|
+
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
94
|
+
.replace(/<br\s*\/?>/gi, " ");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function splitRow(line: string): string[] {
|
|
98
|
+
return line
|
|
99
|
+
.replace(/^\s*\|/, "")
|
|
100
|
+
.replace(/\|\s*$/, "")
|
|
101
|
+
.split("|")
|
|
102
|
+
.map(plainCell);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const isSeparatorRow = (line: string): boolean =>
|
|
106
|
+
/^\s*\|?[\s:|-]+\|[\s:|-]*$/.test(line) && line.includes("-");
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Render a table as aligned columns with a rule under the header.
|
|
110
|
+
*
|
|
111
|
+
* Pipes are dropped rather than drawn. In a pane this narrow the vertical bars
|
|
112
|
+
* cost a column each and, once a cell wraps, line up with nothing — which is
|
|
113
|
+
* exactly how the raw source read before.
|
|
114
|
+
*/
|
|
115
|
+
function renderTable(
|
|
116
|
+
rows: string[][],
|
|
117
|
+
width: number,
|
|
118
|
+
key: string,
|
|
119
|
+
): React.ReactNode[] {
|
|
120
|
+
const columns = Math.max(...rows.map((r) => r.length));
|
|
121
|
+
const natural: number[] = [];
|
|
122
|
+
for (let c = 0; c < columns; c++) {
|
|
123
|
+
natural.push(Math.max(1, ...rows.map((r) => (r[c] ?? "").length)));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Shrink the widest column until the row fits. Shrinking the widest keeps a
|
|
127
|
+
// narrow key column (Mode, Level) intact and takes the space out of the prose
|
|
128
|
+
// column, which is the one that can afford it.
|
|
129
|
+
const gap = 2;
|
|
130
|
+
const budget = Math.max(20, width - 1);
|
|
131
|
+
const widths = [...natural];
|
|
132
|
+
const total = () => widths.reduce((a, b) => a + b, 0) + gap * (columns - 1);
|
|
133
|
+
while (total() > budget) {
|
|
134
|
+
const widest = widths.indexOf(Math.max(...widths));
|
|
135
|
+
if (widths[widest] <= 6) break;
|
|
136
|
+
widths[widest] -= 1;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const fit = (cell: string, w: number) =>
|
|
140
|
+
cell.length <= w ? cell.padEnd(w) : `${cell.slice(0, Math.max(1, w - 1))}…`;
|
|
141
|
+
|
|
142
|
+
const nodes: React.ReactNode[] = [];
|
|
143
|
+
rows.forEach((row, r) => {
|
|
144
|
+
const line = widths
|
|
145
|
+
.map((w, c) => fit(row[c] ?? "", w))
|
|
146
|
+
.join(" ".repeat(gap))
|
|
147
|
+
.trimEnd();
|
|
148
|
+
|
|
149
|
+
nodes.push(
|
|
150
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: table rows never reorder
|
|
151
|
+
<text key={`${key}-r${r}`} fg={theme.colors.muted}>
|
|
152
|
+
{" "}
|
|
153
|
+
{r === 0 ? <strong>{line}</strong> : line}
|
|
154
|
+
</text>,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
if (r === 0) {
|
|
158
|
+
nodes.push(
|
|
159
|
+
<text key={`${key}-rule`} fg={theme.colors.dim}>
|
|
160
|
+
{" "}
|
|
161
|
+
{widths.map((w) => "─".repeat(w)).join(" ".repeat(gap))}
|
|
162
|
+
</text>,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
return nodes;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ─── Document ────────────────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
const HEADING_COLORS = [
|
|
172
|
+
theme.colors.accent,
|
|
173
|
+
theme.colors.accent,
|
|
174
|
+
theme.colors.info,
|
|
175
|
+
theme.colors.info,
|
|
176
|
+
theme.colors.muted,
|
|
177
|
+
theme.colors.muted,
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
export function renderMarkdown(
|
|
181
|
+
source: string,
|
|
182
|
+
{ width, maxLines }: RenderOptions,
|
|
183
|
+
): MarkdownRenderResult {
|
|
184
|
+
const lines = source.split("\n");
|
|
185
|
+
const nodes: React.ReactNode[] = [];
|
|
186
|
+
let i = 0;
|
|
187
|
+
let emitted = 0;
|
|
188
|
+
|
|
189
|
+
const push = (node: React.ReactNode) => {
|
|
190
|
+
nodes.push(node);
|
|
191
|
+
emitted += 1;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
while (i < lines.length && emitted < maxLines) {
|
|
195
|
+
const line = lines[i];
|
|
196
|
+
|
|
197
|
+
// Fenced code. The fence markers are dropped and the block is tinted, so
|
|
198
|
+
// the reader sees a code block rather than three backticks they have to
|
|
199
|
+
// mentally strip.
|
|
200
|
+
const fence = line.match(/^\s*```\s*(\S*)/);
|
|
201
|
+
if (fence) {
|
|
202
|
+
const lang = fence[1];
|
|
203
|
+
i += 1;
|
|
204
|
+
if (lang) {
|
|
205
|
+
push(
|
|
206
|
+
<text key={`f${i}`} fg={theme.colors.dim}>
|
|
207
|
+
{" "}
|
|
208
|
+
{lang}
|
|
209
|
+
</text>,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
while (i < lines.length && !/^\s*```/.test(lines[i])) {
|
|
213
|
+
const code = lines[i];
|
|
214
|
+
push(
|
|
215
|
+
<text key={`fc${i}`} fg={theme.colors.link}>
|
|
216
|
+
{` ${code}` || " "}
|
|
217
|
+
</text>,
|
|
218
|
+
);
|
|
219
|
+
i += 1;
|
|
220
|
+
if (emitted >= maxLines) break;
|
|
221
|
+
}
|
|
222
|
+
i += 1; // closing fence
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Table: a pipe row whose successor is a separator row.
|
|
227
|
+
if (
|
|
228
|
+
line.trim().startsWith("|") &&
|
|
229
|
+
i + 1 < lines.length &&
|
|
230
|
+
isSeparatorRow(lines[i + 1])
|
|
231
|
+
) {
|
|
232
|
+
const rows: string[][] = [splitRow(line)];
|
|
233
|
+
i += 2;
|
|
234
|
+
while (i < lines.length && lines[i].trim().startsWith("|")) {
|
|
235
|
+
rows.push(splitRow(lines[i]));
|
|
236
|
+
i += 1;
|
|
237
|
+
}
|
|
238
|
+
for (const node of renderTable(rows, width, `tb${i}`)) push(node);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Horizontal rule. Drawn, rather than left as three hyphens that read as
|
|
243
|
+
// a stray line of text.
|
|
244
|
+
if (/^\s*(---+|\*\*\*+|___+)\s*$/.test(line)) {
|
|
245
|
+
push(
|
|
246
|
+
<text key={`hr${i}`} fg={theme.colors.dim}>
|
|
247
|
+
{"─".repeat(Math.max(4, Math.min(width - 1, 60)))}
|
|
248
|
+
</text>,
|
|
249
|
+
);
|
|
250
|
+
i += 1;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const heading = line.match(/^(#{1,6})\s+(.*)$/);
|
|
255
|
+
if (heading) {
|
|
256
|
+
const level = heading[1].length;
|
|
257
|
+
push(
|
|
258
|
+
<text key={`h${i}`} fg={HEADING_COLORS[level - 1]}>
|
|
259
|
+
<strong>{heading[2]}</strong>
|
|
260
|
+
</text>,
|
|
261
|
+
);
|
|
262
|
+
i += 1;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const quote = line.match(/^\s*>\s?(.*)$/);
|
|
267
|
+
if (quote) {
|
|
268
|
+
push(
|
|
269
|
+
<text key={`q${i}`} fg={theme.colors.muted}>
|
|
270
|
+
<span fg={theme.colors.dim}>{" │ "}</span>
|
|
271
|
+
{inlineSpans(quote[1], `q${i}`)}
|
|
272
|
+
</text>,
|
|
273
|
+
);
|
|
274
|
+
i += 1;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const list = line.match(/^(\s*)([-*+]|\d+\.)\s+(.*)$/);
|
|
279
|
+
if (list) {
|
|
280
|
+
const indent = " ".repeat(Math.min(list[1].length, 8));
|
|
281
|
+
const bullet = /\d/.test(list[2]) ? list[2] : "•";
|
|
282
|
+
push(
|
|
283
|
+
<text key={`li${i}`} fg={theme.colors.muted}>
|
|
284
|
+
{` ${indent}`}
|
|
285
|
+
<span fg={theme.colors.dim}>{bullet} </span>
|
|
286
|
+
{inlineSpans(list[3], `li${i}`)}
|
|
287
|
+
</text>,
|
|
288
|
+
);
|
|
289
|
+
i += 1;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
push(
|
|
294
|
+
<text key={`p${i}`} fg={theme.colors.muted}>
|
|
295
|
+
{line ? inlineSpans(line, `p${i}`) : " "}
|
|
296
|
+
</text>,
|
|
297
|
+
);
|
|
298
|
+
i += 1;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return { nodes, withheld: Math.max(0, lines.length - i) };
|
|
302
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import type { McpServer } from "../../types/index.js";
|
|
3
|
+
import { theme } from "../theme.js";
|
|
4
|
+
|
|
5
|
+
// ─── List item types ───────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
export type McpListItem =
|
|
8
|
+
| { kind: "category"; label: string }
|
|
9
|
+
| { kind: "server"; server: McpServer; isInstalled: boolean };
|
|
10
|
+
|
|
11
|
+
// ─── Row renderers ─────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export function renderMcpRow(
|
|
14
|
+
item: McpListItem,
|
|
15
|
+
_index: number,
|
|
16
|
+
isSelected: boolean,
|
|
17
|
+
): React.ReactNode {
|
|
18
|
+
if (item.kind === "category") {
|
|
19
|
+
return (
|
|
20
|
+
<text fg={theme.selection.bg}>
|
|
21
|
+
<strong>
|
|
22
|
+
{"▸ "}
|
|
23
|
+
{item.label}
|
|
24
|
+
</strong>
|
|
25
|
+
</text>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { server, isInstalled } = item;
|
|
30
|
+
const icon = isInstalled ? "●" : "○";
|
|
31
|
+
const iconColor = isInstalled ? theme.colors.success : theme.colors.muted;
|
|
32
|
+
|
|
33
|
+
if (isSelected) {
|
|
34
|
+
return (
|
|
35
|
+
<text bg={theme.selection.bg} fg={theme.selection.fg}>
|
|
36
|
+
{" "}
|
|
37
|
+
{icon} {server.name}{" "}
|
|
38
|
+
</text>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<text fg={theme.colors.text}>
|
|
44
|
+
<span fg={iconColor}>{icon}</span>
|
|
45
|
+
<span fg={theme.colors.text}> {server.name}</span>
|
|
46
|
+
{server.requiresConfig ? <span fg={theme.colors.warning}> ⚙</span> : null}
|
|
47
|
+
</text>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ─── Detail renderer ───────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
export function renderMcpDetail(
|
|
54
|
+
item: McpListItem | undefined,
|
|
55
|
+
loading: boolean,
|
|
56
|
+
): React.ReactNode {
|
|
57
|
+
if (loading) {
|
|
58
|
+
return <text fg={theme.colors.muted}>Loading MCP servers...</text>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!item || item.kind === "category") {
|
|
62
|
+
return (
|
|
63
|
+
<box
|
|
64
|
+
flexDirection="column"
|
|
65
|
+
alignItems="center"
|
|
66
|
+
justifyContent="center"
|
|
67
|
+
flexGrow={1}
|
|
68
|
+
>
|
|
69
|
+
<text fg={theme.colors.muted}>Select a server to see details</text>
|
|
70
|
+
</box>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const { server, isInstalled } = item;
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<box flexDirection="column">
|
|
78
|
+
<box marginBottom={1}>
|
|
79
|
+
<text fg={theme.colors.info}>
|
|
80
|
+
<strong>
|
|
81
|
+
{"⚡ "}
|
|
82
|
+
{server.name}
|
|
83
|
+
</strong>
|
|
84
|
+
</text>
|
|
85
|
+
{server.requiresConfig ? (
|
|
86
|
+
<text fg={theme.colors.warning}> ⚙</text>
|
|
87
|
+
) : null}
|
|
88
|
+
</box>
|
|
89
|
+
|
|
90
|
+
<text fg={theme.colors.muted}>{server.description}</text>
|
|
91
|
+
|
|
92
|
+
<box marginTop={1} flexDirection="column">
|
|
93
|
+
<box>
|
|
94
|
+
<text fg={theme.colors.muted}>{"Status "}</text>
|
|
95
|
+
{isInstalled ? (
|
|
96
|
+
<text fg={theme.colors.success}>{"● Installed"}</text>
|
|
97
|
+
) : (
|
|
98
|
+
<text fg={theme.colors.muted}>{"○ Not installed"}</text>
|
|
99
|
+
)}
|
|
100
|
+
</box>
|
|
101
|
+
<box>
|
|
102
|
+
<text fg={theme.colors.muted}>{"Type "}</text>
|
|
103
|
+
<text fg={theme.colors.text}>
|
|
104
|
+
{server.type === "http" ? "HTTP" : "Command"}
|
|
105
|
+
</text>
|
|
106
|
+
</box>
|
|
107
|
+
{server.type === "http" ? (
|
|
108
|
+
<box>
|
|
109
|
+
<text fg={theme.colors.muted}>{"URL "}</text>
|
|
110
|
+
<text fg={theme.colors.link}>{server.url}</text>
|
|
111
|
+
</box>
|
|
112
|
+
) : (
|
|
113
|
+
<box>
|
|
114
|
+
<text fg={theme.colors.muted}>{"Command "}</text>
|
|
115
|
+
<text fg={theme.colors.info}>{server.command}</text>
|
|
116
|
+
</box>
|
|
117
|
+
)}
|
|
118
|
+
{server.requiresConfig ? (
|
|
119
|
+
<box>
|
|
120
|
+
<text fg={theme.colors.muted}>{"Config "}</text>
|
|
121
|
+
<text fg={theme.colors.warning}>
|
|
122
|
+
{server.configFields?.length ?? 0} fields required
|
|
123
|
+
</text>
|
|
124
|
+
</box>
|
|
125
|
+
) : null}
|
|
126
|
+
</box>
|
|
127
|
+
|
|
128
|
+
<box marginTop={2}>
|
|
129
|
+
{isInstalled ? (
|
|
130
|
+
<box>
|
|
131
|
+
<text bg={theme.colors.danger} fg={theme.hints.fg}>
|
|
132
|
+
{" "}
|
|
133
|
+
Enter{" "}
|
|
134
|
+
</text>
|
|
135
|
+
<text fg={theme.colors.muted}> Remove server</text>
|
|
136
|
+
</box>
|
|
137
|
+
) : (
|
|
138
|
+
<box>
|
|
139
|
+
<text bg={theme.colors.success} fg={theme.hints.fg}>
|
|
140
|
+
{" "}
|
|
141
|
+
Enter{" "}
|
|
142
|
+
</text>
|
|
143
|
+
<text fg={theme.colors.muted}> Install server</text>
|
|
144
|
+
</box>
|
|
145
|
+
)}
|
|
146
|
+
</box>
|
|
147
|
+
</box>
|
|
148
|
+
);
|
|
149
|
+
}
|