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,281 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import fs from "fs-extra";
|
|
6
|
+
import {
|
|
7
|
+
emptyManifest,
|
|
8
|
+
mergeManifestSettings,
|
|
9
|
+
migrateToV2,
|
|
10
|
+
readManifest,
|
|
11
|
+
validateManifest,
|
|
12
|
+
writeManifest,
|
|
13
|
+
} from "../services/manifest.js";
|
|
14
|
+
import type { ProfileManifest } from "../types/index.js";
|
|
15
|
+
|
|
16
|
+
describe("migrateToV2", () => {
|
|
17
|
+
test("a v1 file migrates by version bump, profiles preserved", () => {
|
|
18
|
+
const v1 = {
|
|
19
|
+
version: 1,
|
|
20
|
+
profiles: {
|
|
21
|
+
mine: {
|
|
22
|
+
name: "Mine",
|
|
23
|
+
plugins: { "dev@magus": true },
|
|
24
|
+
createdAt: "t",
|
|
25
|
+
updatedAt: "t",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
const v2 = migrateToV2(v1);
|
|
30
|
+
expect(v2.version).toBe(2);
|
|
31
|
+
expect(v2.profiles.mine!.name).toBe("Mine");
|
|
32
|
+
expect(v2.profiles.mine!.plugins).toEqual({ "dev@magus": true });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("null/garbage input degrades to an empty manifest", () => {
|
|
36
|
+
expect(migrateToV2(null)).toEqual(emptyManifest());
|
|
37
|
+
expect(migrateToV2(42)).toEqual(emptyManifest());
|
|
38
|
+
expect(migrateToV2({})).toEqual(emptyManifest());
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("non-object profile entries are dropped", () => {
|
|
42
|
+
const v2 = migrateToV2({
|
|
43
|
+
version: 2,
|
|
44
|
+
profiles: { good: { name: "G" }, bad: 5 },
|
|
45
|
+
});
|
|
46
|
+
expect(Object.keys(v2.profiles)).toEqual(["good"]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("strictVersions is carried through as a boolean", () => {
|
|
50
|
+
expect(
|
|
51
|
+
migrateToV2({ version: 2, strictVersions: true, profiles: {} })
|
|
52
|
+
.strictVersions,
|
|
53
|
+
).toBe(true);
|
|
54
|
+
// Omitted (not written as false) to keep a committed manifest minimal.
|
|
55
|
+
expect(
|
|
56
|
+
migrateToV2({ version: 2, profiles: {} }).strictVersions,
|
|
57
|
+
).toBeUndefined();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("validateManifest", () => {
|
|
62
|
+
const valid: ProfileManifest = {
|
|
63
|
+
version: 2,
|
|
64
|
+
profiles: {
|
|
65
|
+
frontend: {
|
|
66
|
+
name: "Frontend",
|
|
67
|
+
plugins: { "dev@magus": "2.9.0" },
|
|
68
|
+
marketplaces: { magus: { source: "github", repo: "MadAppGang/magus" } },
|
|
69
|
+
skills: [{ name: "A", repo: "o/r", path: "skills/a" }],
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
test("a well-formed manifest yields no errors", () => {
|
|
75
|
+
expect(validateManifest(valid)).toEqual([]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("flags version != 2 and empty profiles", () => {
|
|
79
|
+
const errors = validateManifest({ version: 1, profiles: {} } as never);
|
|
80
|
+
expect(errors.some((e) => e.path === "version")).toBe(true);
|
|
81
|
+
expect(errors.some((e) => e.path === "profiles")).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("flags a plugin id without a marketplace", () => {
|
|
85
|
+
const errors = validateManifest({
|
|
86
|
+
version: 2,
|
|
87
|
+
profiles: { p: { name: "P", plugins: { dev: "1.0.0" } } },
|
|
88
|
+
});
|
|
89
|
+
expect(errors.some((e) => e.path === "profiles.p.plugins.dev")).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("flags a github marketplace missing repo and a skill missing path", () => {
|
|
93
|
+
const errors = validateManifest({
|
|
94
|
+
version: 2,
|
|
95
|
+
profiles: {
|
|
96
|
+
p: {
|
|
97
|
+
name: "P",
|
|
98
|
+
marketplaces: { m: { source: "github" } },
|
|
99
|
+
skills: [{ name: "A", repo: "o/r" } as never],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
expect(errors.some((e) => e.path === "profiles.p.marketplaces.m")).toBe(
|
|
104
|
+
true,
|
|
105
|
+
);
|
|
106
|
+
expect(errors.some((e) => e.path.startsWith("profiles.p.skills"))).toBe(
|
|
107
|
+
true,
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Model routing is the one field whose bad value does not degrade: MEASURED
|
|
112
|
+
// (benches/agent-model-routing `bogus-model`, 5/5), a model the Agent tool
|
|
113
|
+
// does not accept refuses the spawn outright, so every subagent dispatch
|
|
114
|
+
// fails for everyone who pulled the manifest. This is the gate.
|
|
115
|
+
test("a hand-edited models block is validated, path-prefixed by profile", () => {
|
|
116
|
+
const errors = validateManifest({
|
|
117
|
+
version: 2,
|
|
118
|
+
profiles: {
|
|
119
|
+
p: {
|
|
120
|
+
name: "P",
|
|
121
|
+
models: {
|
|
122
|
+
version: 1,
|
|
123
|
+
preset: "hand",
|
|
124
|
+
main: { model: "opus" },
|
|
125
|
+
grades: {
|
|
126
|
+
smart: { model: "claude-opus-4-6" },
|
|
127
|
+
normal: { model: "opus" },
|
|
128
|
+
cheap: { model: "sonnet" },
|
|
129
|
+
},
|
|
130
|
+
agents: {},
|
|
131
|
+
fallback: "normal",
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
} as unknown as ProfileManifest);
|
|
136
|
+
|
|
137
|
+
expect(
|
|
138
|
+
errors.some((e) => e.path === "profiles.p.models.grades.smart.model"),
|
|
139
|
+
).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("a valid models block reports nothing", () => {
|
|
143
|
+
const errors = validateManifest({
|
|
144
|
+
version: 2,
|
|
145
|
+
profiles: {
|
|
146
|
+
p: {
|
|
147
|
+
name: "P",
|
|
148
|
+
models: {
|
|
149
|
+
version: 1,
|
|
150
|
+
preset: "fable-advisor",
|
|
151
|
+
main: { model: "opus", effort: "medium" },
|
|
152
|
+
grades: {
|
|
153
|
+
smart: { model: "fable", effort: "xhigh" },
|
|
154
|
+
normal: { model: "opus", effort: "medium" },
|
|
155
|
+
cheap: { model: "sonnet", effort: "low" },
|
|
156
|
+
},
|
|
157
|
+
agents: { "dev:architect": "smart" },
|
|
158
|
+
fallback: "normal",
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
} as unknown as ProfileManifest);
|
|
163
|
+
|
|
164
|
+
expect(errors).toEqual([]);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe("mergeManifestSettings", () => {
|
|
169
|
+
let dir: string;
|
|
170
|
+
beforeEach(async () => {
|
|
171
|
+
dir = await mkdtemp(join(tmpdir(), "manifest-merge-"));
|
|
172
|
+
});
|
|
173
|
+
afterEach(async () => {
|
|
174
|
+
await rm(dir, { recursive: true, force: true });
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("merges into the entry's settings, keeping what was there", async () => {
|
|
178
|
+
await writeManifest(
|
|
179
|
+
{
|
|
180
|
+
version: 2,
|
|
181
|
+
profiles: { p: { name: "P", settings: { outputStyle: "direct" } } },
|
|
182
|
+
},
|
|
183
|
+
dir,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
expect(await mergeManifestSettings(dir, "p", { model: "opus" })).toBe(true);
|
|
187
|
+
|
|
188
|
+
const m = await readManifest(dir);
|
|
189
|
+
expect(m.profiles.p!.settings).toEqual({
|
|
190
|
+
outputStyle: "direct",
|
|
191
|
+
model: "opus",
|
|
192
|
+
});
|
|
193
|
+
expect(m.profiles.p!.updatedAt).toBeDefined();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("unset removes keys outright, not as undefined", async () => {
|
|
197
|
+
await writeManifest(
|
|
198
|
+
{
|
|
199
|
+
version: 2,
|
|
200
|
+
profiles: { p: { name: "P", settings: { model: "opus", keep: 1 } } },
|
|
201
|
+
},
|
|
202
|
+
dir,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
await mergeManifestSettings(dir, "p", {}, { unset: ["model"] });
|
|
206
|
+
|
|
207
|
+
const raw = await fs.readJson(join(dir, ".claude", "profiles.json"));
|
|
208
|
+
expect("model" in raw.profiles.p.settings).toBe(false);
|
|
209
|
+
expect(raw.profiles.p.settings.keep).toBe(1);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("false, not an error, when there is nothing to record against", async () => {
|
|
213
|
+
expect(await mergeManifestSettings(dir, "p", { model: "opus" })).toBe(
|
|
214
|
+
false,
|
|
215
|
+
);
|
|
216
|
+
expect(await mergeManifestSettings(dir, null, { model: "opus" })).toBe(
|
|
217
|
+
false,
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
await writeManifest(
|
|
221
|
+
{ version: 2, profiles: { other: { name: "O" } } },
|
|
222
|
+
dir,
|
|
223
|
+
);
|
|
224
|
+
expect(await mergeManifestSettings(dir, "p", { model: "opus" })).toBe(
|
|
225
|
+
false,
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// readManifest degrades a corrupt file to an empty manifest. Reusing it here
|
|
230
|
+
// would record nothing and report success, leaving the caller's live setting
|
|
231
|
+
// to be reverted by the next install with no way to learn why.
|
|
232
|
+
test("throws on an unreadable manifest instead of silently doing nothing", async () => {
|
|
233
|
+
await fs.outputFile(join(dir, ".claude", "profiles.json"), "{ not json");
|
|
234
|
+
|
|
235
|
+
await expect(
|
|
236
|
+
mergeManifestSettings(dir, "p", { model: "opus" }),
|
|
237
|
+
).rejects.toThrow(/not valid JSON/);
|
|
238
|
+
|
|
239
|
+
expect(
|
|
240
|
+
await fs.readFile(join(dir, ".claude", "profiles.json"), "utf8"),
|
|
241
|
+
).toBe("{ not json");
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe("readManifest / writeManifest", () => {
|
|
246
|
+
let dir: string;
|
|
247
|
+
beforeEach(async () => {
|
|
248
|
+
dir = await mkdtemp(join(tmpdir(), "manifest-"));
|
|
249
|
+
});
|
|
250
|
+
afterEach(async () => {
|
|
251
|
+
await rm(dir, { recursive: true, force: true });
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("absent file → empty manifest", async () => {
|
|
255
|
+
expect(await readManifest(dir)).toEqual(emptyManifest());
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("roundtrip write→read preserves content", async () => {
|
|
259
|
+
const manifest: ProfileManifest = {
|
|
260
|
+
version: 2,
|
|
261
|
+
strictVersions: true,
|
|
262
|
+
profiles: { p: { name: "P", plugins: { "dev@magus": "1.0.0" } } },
|
|
263
|
+
};
|
|
264
|
+
await writeManifest(manifest, dir);
|
|
265
|
+
expect(await fs.pathExists(join(dir, ".claude", "profiles.json"))).toBe(
|
|
266
|
+
true,
|
|
267
|
+
);
|
|
268
|
+
expect(await readManifest(dir)).toEqual(manifest);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("a committed v1 file is read as migrated v2", async () => {
|
|
272
|
+
await fs.ensureDir(join(dir, ".claude"));
|
|
273
|
+
await fs.writeJson(join(dir, ".claude", "profiles.json"), {
|
|
274
|
+
version: 1,
|
|
275
|
+
profiles: { legacy: { name: "L", plugins: { "dev@magus": true } } },
|
|
276
|
+
});
|
|
277
|
+
const m = await readManifest(dir);
|
|
278
|
+
expect(m.version).toBe(2);
|
|
279
|
+
expect(m.profiles.legacy!.name).toBe("L");
|
|
280
|
+
});
|
|
281
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { renderMarkdown } from "../ui/renderers/markdownRenderer.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The renderer emits OpenTUI elements, so these assert structure and counts
|
|
6
|
+
* rather than pixels: how many lines a construct produces, and that the raw
|
|
7
|
+
* markers (fences, pipes, hashes) do not survive into the output.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Flatten a rendered node tree to the text a reader would see. */
|
|
11
|
+
function textOf(node: any): string {
|
|
12
|
+
if (node === null || node === undefined || typeof node === "boolean") {
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
if (typeof node === "string" || typeof node === "number") return String(node);
|
|
16
|
+
if (Array.isArray(node)) return node.map(textOf).join("");
|
|
17
|
+
if (node.props) return textOf(node.props.children);
|
|
18
|
+
return "";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const render = (src: string, width = 80) =>
|
|
22
|
+
renderMarkdown(src, { width, maxLines: 500 });
|
|
23
|
+
|
|
24
|
+
describe("renderMarkdown", () => {
|
|
25
|
+
it("drops the # markers from a heading", () => {
|
|
26
|
+
const { nodes } = render("## Routing");
|
|
27
|
+
|
|
28
|
+
expect(nodes).toHaveLength(1);
|
|
29
|
+
expect(textOf(nodes[0])).toBe("Routing");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("drops the fence markers and keeps the code", () => {
|
|
33
|
+
const { nodes } = render(
|
|
34
|
+
["```bash", "which mnemex && mnemex --version", "```"].join("\n"),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const text = nodes.map(textOf).join("\n");
|
|
38
|
+
expect(text).not.toContain("```");
|
|
39
|
+
expect(text).toContain("which mnemex && mnemex --version");
|
|
40
|
+
expect(text).toContain("bash");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("turns a pipe table into aligned columns with a rule, no pipes", () => {
|
|
44
|
+
const { nodes } = render(
|
|
45
|
+
[
|
|
46
|
+
"| Mode | Keywords |",
|
|
47
|
+
"|------|----------|",
|
|
48
|
+
"| Bug | debug, error |",
|
|
49
|
+
"| Test | coverage |",
|
|
50
|
+
].join("\n"),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// header + rule + 2 body rows
|
|
54
|
+
expect(nodes).toHaveLength(4);
|
|
55
|
+
const text = nodes.map(textOf).join("\n");
|
|
56
|
+
expect(text).not.toContain("|");
|
|
57
|
+
expect(text).toContain("Mode");
|
|
58
|
+
expect(text).toContain("debug, error");
|
|
59
|
+
expect(textOf(nodes[1])).toMatch(/^\s*─+\s+─+$/);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("narrows the widest table column instead of overflowing the pane", () => {
|
|
63
|
+
const { nodes } = render(
|
|
64
|
+
["| Mode | Notes |", "|---|---|", `| Bug | ${"x".repeat(120)} |`].join(
|
|
65
|
+
"\n",
|
|
66
|
+
),
|
|
67
|
+
40,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
for (const node of nodes) {
|
|
71
|
+
expect(textOf(node).length).toBeLessThanOrEqual(41);
|
|
72
|
+
}
|
|
73
|
+
expect(nodes.map(textOf).join("")).toContain("…");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("strips inline markers inside table cells", () => {
|
|
77
|
+
const { nodes } = render(
|
|
78
|
+
[
|
|
79
|
+
"| Mode | Primary Commands |",
|
|
80
|
+
"|---|---|",
|
|
81
|
+
"| Bug | `context`, `callers` |",
|
|
82
|
+
"| Arch | **map** |",
|
|
83
|
+
].join("\n"),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const text = nodes.map(textOf).join("\n");
|
|
87
|
+
expect(text).toContain("context, callers");
|
|
88
|
+
expect(text).not.toContain("`");
|
|
89
|
+
expect(text).not.toContain("**");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("draws a rule for --- rather than printing hyphens as text", () => {
|
|
93
|
+
const { nodes } = render("---", 30);
|
|
94
|
+
|
|
95
|
+
expect(textOf(nodes[0])).toMatch(/^─+$/);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("renders a list bullet and keeps ordered markers", () => {
|
|
99
|
+
const { nodes } = render(["- first", "2. second"].join("\n"));
|
|
100
|
+
|
|
101
|
+
expect(textOf(nodes[0])).toContain("•");
|
|
102
|
+
expect(textOf(nodes[0])).toContain("first");
|
|
103
|
+
expect(textOf(nodes[1])).toContain("2.");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("strips inline code backticks and bold asterisks", () => {
|
|
107
|
+
const { nodes } = render("Run `bun test` and **stop**.");
|
|
108
|
+
|
|
109
|
+
const text = textOf(nodes[0]);
|
|
110
|
+
expect(text).toBe("Run bun test and stop.");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("does not read bold markers that sit inside a code span", () => {
|
|
114
|
+
const { nodes } = render("Use `a**b` here.");
|
|
115
|
+
|
|
116
|
+
expect(textOf(nodes[0])).toBe("Use a**b here.");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("marks a blockquote with a bar instead of a > character", () => {
|
|
120
|
+
const { nodes } = render("> With a sufficient number of users");
|
|
121
|
+
|
|
122
|
+
const text = textOf(nodes[0]);
|
|
123
|
+
expect(text).toContain("│");
|
|
124
|
+
expect(text).not.toContain(">");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("reports the lines it stopped short of", () => {
|
|
128
|
+
const long = Array.from({ length: 30 }, (_, i) => `line ${i}`).join("\n");
|
|
129
|
+
|
|
130
|
+
const { nodes, withheld } = renderMarkdown(long, {
|
|
131
|
+
width: 80,
|
|
132
|
+
maxLines: 10,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(nodes).toHaveLength(10);
|
|
136
|
+
expect(withheld).toBe(20);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("withholds nothing when the whole document fits", () => {
|
|
140
|
+
expect(render("one\ntwo").withheld).toBe(0);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { Marketplace } from "../types/index.js";
|
|
3
|
+
import { buildPluginBrowserItems } from "../ui/adapters/pluginsAdapter.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The marketplace category badge is the line users read on every magus launch,
|
|
7
|
+
* and it had no coverage. The MadAppGang byline must not leak onto anyone else's
|
|
8
|
+
* marketplace, and the Official/Deprecated markers must still win.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const mp = (over: Partial<Marketplace> & { name: string }): Marketplace => ({
|
|
12
|
+
displayName: over.displayName ?? over.name,
|
|
13
|
+
source: { source: "github", repo: `someone/${over.name}` },
|
|
14
|
+
description: "",
|
|
15
|
+
...over,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/** Badge for a marketplace when it has plugins and is expanded. */
|
|
19
|
+
function badgeFor(marketplace: Marketplace): string | undefined {
|
|
20
|
+
const items = buildPluginBrowserItems({
|
|
21
|
+
marketplaces: [marketplace],
|
|
22
|
+
plugins: [
|
|
23
|
+
{
|
|
24
|
+
id: `demo@${marketplace.name}`,
|
|
25
|
+
name: "demo",
|
|
26
|
+
marketplace: marketplace.name,
|
|
27
|
+
// Only the fields the adapter groups on matter here.
|
|
28
|
+
} as never,
|
|
29
|
+
],
|
|
30
|
+
collapsedMarketplaces: new Set(),
|
|
31
|
+
});
|
|
32
|
+
const category = items.find((i) => i.kind === "category");
|
|
33
|
+
return category && "badge" in category ? category.badge : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Tone for a marketplace when it has plugins and is expanded. */
|
|
37
|
+
function toneFor(marketplace: Marketplace): string | undefined {
|
|
38
|
+
const items = buildPluginBrowserItems({
|
|
39
|
+
marketplaces: [marketplace],
|
|
40
|
+
plugins: [
|
|
41
|
+
{
|
|
42
|
+
id: `demo@${marketplace.name}`,
|
|
43
|
+
name: "demo",
|
|
44
|
+
marketplace: marketplace.name,
|
|
45
|
+
} as never,
|
|
46
|
+
],
|
|
47
|
+
collapsedMarketplaces: new Set(),
|
|
48
|
+
});
|
|
49
|
+
const category = items.find((i) => i.kind === "category");
|
|
50
|
+
return category && "tone" in category ? category.tone : undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe("marketplace category badge", () => {
|
|
54
|
+
test("the byline uses a low-key tone, not an attention-grabbing one", () => {
|
|
55
|
+
// It is a signature, not a status. Green reads as "action succeeded" and
|
|
56
|
+
// competes with the plugin rows for attention.
|
|
57
|
+
expect(
|
|
58
|
+
toneFor(
|
|
59
|
+
mp({
|
|
60
|
+
name: "magus",
|
|
61
|
+
source: { source: "github", repo: "MadAppGang/magus" },
|
|
62
|
+
}),
|
|
63
|
+
),
|
|
64
|
+
).toBe("byline");
|
|
65
|
+
|
|
66
|
+
// A third-party marketplace still uses the green "Added" status tone.
|
|
67
|
+
expect(
|
|
68
|
+
toneFor(
|
|
69
|
+
mp({
|
|
70
|
+
name: "sp",
|
|
71
|
+
source: { source: "github", repo: "obra/superpowers-marketplace" },
|
|
72
|
+
}),
|
|
73
|
+
),
|
|
74
|
+
).toBe("green");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("MadAppGang marketplaces carry the byline instead of a generic label", () => {
|
|
78
|
+
for (const name of ["magus", "magus-marketing", "magus-alpha"]) {
|
|
79
|
+
const badge = badgeFor(
|
|
80
|
+
mp({ name, source: { source: "github", repo: `MadAppGang/${name}` } }),
|
|
81
|
+
);
|
|
82
|
+
expect(badge).toBe("by MadAppGang");
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("owner match is case-insensitive", () => {
|
|
87
|
+
// The npm/GitHub casing of the org has varied in config; don't depend on it.
|
|
88
|
+
const badge = badgeFor(
|
|
89
|
+
mp({
|
|
90
|
+
name: "magus",
|
|
91
|
+
source: { source: "github", repo: "madappgang/magus" },
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
expect(badge).toBe("by MadAppGang");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("someone else's marketplace does NOT claim the byline", () => {
|
|
98
|
+
const badge = badgeFor(
|
|
99
|
+
mp({
|
|
100
|
+
name: "superpowers-marketplace",
|
|
101
|
+
source: { source: "github", repo: "obra/superpowers-marketplace" },
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
expect(badge).toBe("✓ Added");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("a repo merely containing 'madappgang' downstream does not match", () => {
|
|
108
|
+
// Guards against a substring check — only the owner segment counts.
|
|
109
|
+
const badge = badgeFor(
|
|
110
|
+
mp({
|
|
111
|
+
name: "impostor",
|
|
112
|
+
source: { source: "github", repo: "evil/madappgang-magus" },
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
expect(badge).toBe("✓ Added");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("Official and Deprecated still take precedence", () => {
|
|
119
|
+
expect(
|
|
120
|
+
badgeFor(
|
|
121
|
+
mp({
|
|
122
|
+
name: "claude-plugins-official",
|
|
123
|
+
source: {
|
|
124
|
+
source: "github",
|
|
125
|
+
repo: "anthropics/claude-plugins-official",
|
|
126
|
+
},
|
|
127
|
+
}),
|
|
128
|
+
),
|
|
129
|
+
).toBe("★ Official");
|
|
130
|
+
|
|
131
|
+
expect(
|
|
132
|
+
badgeFor(
|
|
133
|
+
mp({
|
|
134
|
+
name: "claude-code-plugins",
|
|
135
|
+
source: { source: "github", repo: "anthropics/claude-code" },
|
|
136
|
+
}),
|
|
137
|
+
),
|
|
138
|
+
).toBe("⚠ Deprecated");
|
|
139
|
+
});
|
|
140
|
+
});
|