magus-cli 7.12.2 → 7.14.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 +21 -0
- package/package.json +4 -4
- package/src/__tests__/market-bases.test.ts +112 -0
- package/src/__tests__/market-config.test.ts +84 -0
- package/src/__tests__/market-conflicts.test.ts +175 -0
- package/src/__tests__/market-consent.test.ts +185 -0
- package/src/__tests__/market-content.test.ts +183 -0
- package/src/__tests__/market-delivery.test.ts +308 -0
- package/src/__tests__/market-fixture.ts +1167 -0
- package/src/__tests__/market-git.test.ts +130 -0
- package/src/__tests__/market-identity.test.ts +127 -0
- package/src/__tests__/market-install.test.ts +143 -0
- package/src/__tests__/market-layout.test.ts +206 -0
- package/src/__tests__/market-publish.test.ts +263 -0
- package/src/__tests__/market-r2-adapters.test.ts +190 -0
- package/src/__tests__/market-r2-company-cli.test.ts +350 -0
- package/src/__tests__/market-r2-company-unit.test.ts +224 -0
- package/src/__tests__/market-r2-local-items.test.ts +197 -0
- package/src/__tests__/market-r2-plugin-add.test.ts +470 -0
- package/src/__tests__/market-r2-plugin-publish.test.ts +568 -0
- package/src/__tests__/market-r2-routing.test.ts +127 -0
- package/src/__tests__/market-r2-skill-create-plugin.test.ts +315 -0
- package/src/__tests__/market-r2b-display-path.test.ts +180 -0
- package/src/__tests__/market-r2b-fit-row.test.ts +287 -0
- package/src/__tests__/market-r2b-local-items.test.ts +253 -0
- package/src/__tests__/market-r2b-plugin-add.test.ts +130 -0
- package/src/__tests__/market-readonly.test.ts +162 -0
- package/src/__tests__/market-repositories.test.ts +263 -0
- package/src/__tests__/market-results.test.ts +198 -0
- package/src/__tests__/market-router.test.ts +65 -0
- package/src/__tests__/market-safety.test.ts +357 -0
- package/src/__tests__/market-teammates.test.ts +304 -0
- package/src/cli/install.ts +64 -5
- package/src/cli/market-view.ts +329 -0
- package/src/cli/market.ts +664 -0
- package/src/cli/router.ts +69 -3
- package/src/cli/update-view.ts +56 -0
- package/src/cli/update.ts +111 -9
- package/src/data/marketplaces.ts +8 -2
- package/src/main.tsx +20 -2
- package/src/market-tui.tsx +21 -0
- package/src/services/active-closure.ts +71 -9
- package/src/services/claude-cli.ts +140 -10
- package/src/services/claude-settings.ts +19 -25
- package/src/services/doctor.ts +5 -5
- package/src/services/git-write.ts +615 -0
- package/src/services/github-cli.ts +309 -0
- package/src/services/local-marketplace.ts +112 -50
- package/src/services/market-bases.ts +191 -0
- package/src/services/market-change.ts +242 -0
- package/src/services/market-clone.ts +572 -0
- package/src/services/market-fs.ts +138 -0
- package/src/services/market-install.ts +172 -0
- package/src/services/market-layout.ts +311 -0
- package/src/services/market-local.ts +93 -0
- package/src/services/market-marker.ts +49 -0
- package/src/services/market-ops-content.ts +277 -0
- package/src/services/market-ops-plugin.ts +655 -0
- package/src/services/market-ops-publish.ts +1227 -0
- package/src/services/market-ops-repo.ts +916 -0
- package/src/services/market-ops-skill.ts +788 -0
- package/src/services/market-ops.ts +686 -0
- package/src/services/market-publish.ts +468 -0
- package/src/services/market-registry.ts +382 -0
- package/src/services/market-types.ts +268 -0
- package/src/services/marketplace-conflicts.ts +127 -0
- package/src/services/marketplace-refresh.ts +33 -3
- package/src/services/marketplace-sync.ts +253 -22
- package/src/services/plugin-dependencies.ts +17 -0
- package/src/services/plugin-manager.ts +1 -1
- package/src/services/plugin-mcp-config.ts +6 -9
- package/src/services/plugin-requires.ts +20 -8
- package/src/services/profile-absorb.ts +88 -3
- package/src/services/profile-adopt.ts +10 -1
- package/src/services/profiles.ts +64 -1
- package/src/services/resolver.ts +17 -5
- package/src/services/settings-manager.ts +2 -2
- package/src/services/skills-manager.ts +2 -2
- package/src/services/update-plan.ts +67 -8
- package/src/tui-runtime.tsx +142 -0
- package/src/tui.tsx +7 -124
- package/src/types/index.ts +7 -0
- package/src/ui/adapters/marketAdapter.ts +366 -0
- package/src/ui/adapters/pluginsAdapter.ts +45 -3
- package/src/ui/components/layout/ScreenLayout.tsx +43 -17
- package/src/ui/components/modals/ConfirmModal.tsx +145 -8
- package/src/ui/components/modals/HelpOverlay.tsx +10 -3
- package/src/ui/components/modals/ModalContainer.tsx +47 -5
- package/src/ui/components/primitives/ListCategoryRow.tsx +22 -2
- package/src/ui/components/primitives/ScopeBadge.tsx +28 -0
- package/src/ui/components/primitives/ScopeVersionLine.tsx +27 -0
- package/src/ui/components/primitives/SelectableRow.tsx +14 -1
- package/src/ui/components/primitives/ToneLine.tsx +25 -0
- package/src/ui/components/primitives/index.ts +5 -0
- package/src/ui/keymap.ts +21 -5
- package/src/ui/market/MarketApp.tsx +59 -0
- package/src/ui/market/MarketplaceScreen.tsx +280 -0
- package/src/ui/market/MarketplacesDetail.snowflake.tsx +161 -0
- package/src/ui/market/MarketplacesScreen.tsx +264 -0
- package/src/ui/market/PluginDetail.snowflake.tsx +61 -0
- package/src/ui/market/SkillDetail.snowflake.tsx +71 -0
- package/src/ui/market/marketKeymap.ts +243 -0
- package/src/ui/market/useMarketState.ts +694 -0
- package/src/ui/renderers/pluginRenderers.tsx +31 -3
- package/src/ui/renderers/pluginStatusText.ts +6 -0
- package/src/ui/screens/PluginsScreen.tsx +49 -5
- package/src/ui/screens/SkillsScreen.tsx +2 -2
- package/src/ui/state/AppContext.tsx +14 -2
- package/src/ui/state/types.ts +9 -2
- package/src/utils/config-dir.ts +29 -9
- package/src/utils/display-path.ts +33 -0
package/src/cli/install.ts
CHANGED
|
@@ -31,6 +31,10 @@ import {
|
|
|
31
31
|
summarizeClosure,
|
|
32
32
|
} from "../services/install-plan.js";
|
|
33
33
|
import { readManifest, validateManifest } from "../services/manifest.js";
|
|
34
|
+
import {
|
|
35
|
+
conflictFreeOptions,
|
|
36
|
+
leaveOutConflicted,
|
|
37
|
+
} from "../services/marketplace-conflicts.js";
|
|
34
38
|
import { registerClosureMarketplaces } from "../services/marketplace-sync.js";
|
|
35
39
|
import {
|
|
36
40
|
clearMarketplaceCache,
|
|
@@ -56,12 +60,15 @@ import { askYesNo, promptValue } from "./prompt.js";
|
|
|
56
60
|
import { selectOption } from "./select.js";
|
|
57
61
|
import {
|
|
58
62
|
commandHeader,
|
|
63
|
+
conflictSkipLine,
|
|
64
|
+
conflictStopLine,
|
|
59
65
|
fail as failLine,
|
|
60
66
|
field,
|
|
61
67
|
info as infoLine,
|
|
62
68
|
note as noteLine,
|
|
63
69
|
ok as okLine,
|
|
64
70
|
question,
|
|
71
|
+
registrationReport,
|
|
65
72
|
warn as warnLine,
|
|
66
73
|
} from "./update-view.js";
|
|
67
74
|
|
|
@@ -122,6 +129,26 @@ async function runCheck(
|
|
|
122
129
|
* EVERY profile's plugins — so the caller runs this inside
|
|
123
130
|
* `withCliBookkeeping`, which renders the live files over it afterwards.
|
|
124
131
|
*/
|
|
132
|
+
/**
|
|
133
|
+
* The closure without the plugins of each marketplace whose registration on
|
|
134
|
+
* this machine is not the one the profile names: installing them would take
|
|
135
|
+
* them from the wrong repository. Prints one line per marketplace left out
|
|
136
|
+
* when `report`.
|
|
137
|
+
*/
|
|
138
|
+
function withoutConflicted(
|
|
139
|
+
closure: ResolvedClosure,
|
|
140
|
+
conflicted: ReadonlySet<string>,
|
|
141
|
+
report = true,
|
|
142
|
+
): ResolvedClosure {
|
|
143
|
+
const out = leaveOutConflicted(closure, conflicted);
|
|
144
|
+
if (report) {
|
|
145
|
+
for (const l of out.leftOut) {
|
|
146
|
+
console.log(conflictSkipLine(l.marketplace, l.plugins));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return out.closure;
|
|
150
|
+
}
|
|
151
|
+
|
|
125
152
|
async function installPlugins(closure: ResolvedClosure): Promise<string[]> {
|
|
126
153
|
const installed: string[] = [];
|
|
127
154
|
for (const pluginId of Object.keys(closure.plugins)) {
|
|
@@ -306,10 +333,14 @@ export async function runInstallCommand(
|
|
|
306
333
|
|
|
307
334
|
// Closure: a single profile, or the union of all — with every plugin the
|
|
308
335
|
// enabled ones depend on.
|
|
336
|
+
/** Marketplaces whose registration here is not the one the profile names. */
|
|
337
|
+
let conflicted: ReadonlySet<string> = new Set();
|
|
338
|
+
// Nothing is read for a conflicted marketplace's plugins: their
|
|
339
|
+
// dependencies and tool requirements would come from the other repository.
|
|
309
340
|
const resolveClosure = () =>
|
|
310
341
|
flags.profile
|
|
311
|
-
? resolveProfile(manifest, flags.profile)
|
|
312
|
-
: resolveAllProfiles(manifest);
|
|
342
|
+
? resolveProfile(manifest, flags.profile, conflictFreeOptions(conflicted))
|
|
343
|
+
: resolveAllProfiles(manifest, conflictFreeOptions(conflicted));
|
|
313
344
|
let closure = await resolveClosure();
|
|
314
345
|
|
|
315
346
|
if (flags.check) {
|
|
@@ -353,12 +384,17 @@ export async function runInstallCommand(
|
|
|
353
384
|
return 1;
|
|
354
385
|
}
|
|
355
386
|
|
|
356
|
-
await registerClosureMarketplaces(
|
|
387
|
+
const registration = await registerClosureMarketplaces(
|
|
388
|
+
closure.marketplaces,
|
|
389
|
+
projectPath,
|
|
390
|
+
);
|
|
391
|
+
for (const line of registrationReport(registration)) console.log(line);
|
|
357
392
|
// A marketplace added just now carries the plugin.json files that name
|
|
358
393
|
// each plugin's dependencies; resolve again so those are installed too.
|
|
359
394
|
clearMarketplaceCache();
|
|
360
395
|
const before = new Set(Object.keys(closure.plugins));
|
|
361
|
-
|
|
396
|
+
conflicted = new Set(registration.conflicts.map((c) => c.name));
|
|
397
|
+
closure = withoutConflicted(await resolveClosure(), conflicted);
|
|
362
398
|
const found = Object.keys(closure.plugins).filter((id) => !before.has(id));
|
|
363
399
|
if (found.length > 0) {
|
|
364
400
|
console.log(
|
|
@@ -383,6 +419,23 @@ export async function runInstallCommand(
|
|
|
383
419
|
console.error(failLine("No profile chosen — stopping."));
|
|
384
420
|
return 1;
|
|
385
421
|
}
|
|
422
|
+
// Activating turns on every plugin the profile names, whatever this run
|
|
423
|
+
// installs: one from a conflicted marketplace would load from the other
|
|
424
|
+
// repository. Stop before anything is activated or installed.
|
|
425
|
+
if (activeId && conflicted.size > 0 && manifest.profiles[activeId]) {
|
|
426
|
+
const active = await resolveProfile(
|
|
427
|
+
manifest,
|
|
428
|
+
activeId,
|
|
429
|
+
conflictFreeOptions(conflicted),
|
|
430
|
+
);
|
|
431
|
+
const blocked = leaveOutConflicted(active, conflicted).leftOut;
|
|
432
|
+
if (blocked.length > 0) {
|
|
433
|
+
for (const b of blocked) {
|
|
434
|
+
console.error(conflictStopLine(activeId, b.marketplace, b.plugins));
|
|
435
|
+
}
|
|
436
|
+
return 1;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
386
439
|
if (activeId) {
|
|
387
440
|
// The one switch path: what Claude Code wrote into the live config — or
|
|
388
441
|
// through an older layout's links — goes into the profile it was written
|
|
@@ -423,7 +476,13 @@ export async function runInstallCommand(
|
|
|
423
476
|
process.env.PATH ?? "",
|
|
424
477
|
process.env.SHELL ?? "",
|
|
425
478
|
);
|
|
426
|
-
if (env)
|
|
479
|
+
if (env) {
|
|
480
|
+
await installDeps(
|
|
481
|
+
withoutConflicted(await resolveClosure(), conflicted, false),
|
|
482
|
+
env,
|
|
483
|
+
flags.yes,
|
|
484
|
+
);
|
|
485
|
+
}
|
|
427
486
|
|
|
428
487
|
if (activeId) {
|
|
429
488
|
await collectEnv(
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* market-view.ts — every string `magus market` prints in text mode.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions: plan or result in, lines out, built from the
|
|
5
|
+
* `magus update` report pieces (`commandHeader`, `ok`/`warn`/`fail`/`info`/
|
|
6
|
+
* `note`, `field`, `listItem`, `scopeBadge`, `question`) so the market verbs
|
|
7
|
+
* read like the rest of magus. States first, in the user's words.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
LocalCopy,
|
|
12
|
+
MarketPlan,
|
|
13
|
+
MarketResult,
|
|
14
|
+
SkillStatusRow,
|
|
15
|
+
StepRecord,
|
|
16
|
+
} from "../services/market-types.js";
|
|
17
|
+
import { bold, dim } from "./ansi.js";
|
|
18
|
+
import {
|
|
19
|
+
commandHeader,
|
|
20
|
+
fail,
|
|
21
|
+
field,
|
|
22
|
+
info,
|
|
23
|
+
listItem,
|
|
24
|
+
note,
|
|
25
|
+
ok,
|
|
26
|
+
question,
|
|
27
|
+
scopeBadge,
|
|
28
|
+
warn,
|
|
29
|
+
} from "./update-view.js";
|
|
30
|
+
|
|
31
|
+
function headerFor(command: string, marketplace?: string): string[] {
|
|
32
|
+
return commandHeader(
|
|
33
|
+
command,
|
|
34
|
+
marketplace ? { label: "marketplace", value: marketplace } : undefined,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The plan, one line per thing it does, with its planId. */
|
|
39
|
+
export function planLines(plan: {
|
|
40
|
+
command: string;
|
|
41
|
+
lines?: string[];
|
|
42
|
+
planId?: string;
|
|
43
|
+
marketplace?: string;
|
|
44
|
+
}): string[] {
|
|
45
|
+
return [
|
|
46
|
+
...headerFor(plan.command, plan.marketplace),
|
|
47
|
+
...(plan.lines ?? []).map((l) => info(l)),
|
|
48
|
+
...(plan.planId ? [note(`plan ${plan.planId}`)] : []),
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function planOf(plan: MarketPlan): string[] {
|
|
53
|
+
return planLines({
|
|
54
|
+
command: plan.command,
|
|
55
|
+
lines: plan.lines,
|
|
56
|
+
planId: plan.planId,
|
|
57
|
+
marketplace: plan.summary.marketplace,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The report head for a plan applied on consent given up front (`--yes`):
|
|
63
|
+
* the same header and plan lines the question would have shown, without
|
|
64
|
+
* the planId, which nobody needs once it is applied.
|
|
65
|
+
*/
|
|
66
|
+
export function applyingLines(plan: MarketPlan): string[] {
|
|
67
|
+
return planLines({
|
|
68
|
+
command: plan.command,
|
|
69
|
+
lines: plan.lines,
|
|
70
|
+
marketplace: plan.summary.marketplace,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The consent question for a plan. */
|
|
75
|
+
export function applyQuestion(): string {
|
|
76
|
+
return question("Apply this plan?");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The question when the state read was older than GitHub's. */
|
|
80
|
+
export function fetchQuestion(): string {
|
|
81
|
+
return question("GitHub has newer commits — fetch them now?");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** One step as it settles. */
|
|
85
|
+
export function stepLine(step: StepRecord, label: string): string {
|
|
86
|
+
if (step.status === "done") {
|
|
87
|
+
return ok(step.detail ? `${label} ${dim(`(${step.detail})`)}` : label);
|
|
88
|
+
}
|
|
89
|
+
if (step.status === "skipped") return note(`${label} — not needed`);
|
|
90
|
+
return fail(`${label}: ${step.detail ?? "failed"}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function copyLine(copy: LocalCopy): string {
|
|
94
|
+
const whose = copy.scope === "user" ? "your USER" : "this project's";
|
|
95
|
+
if (copy.action === "removed") {
|
|
96
|
+
return ok(
|
|
97
|
+
`removed ${whose} copy of ${bold(copy.name)}${copy.scope === "project" ? ` — commit the deletion (.claude/skills/${copy.name})` : ""}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (copy.action === "created") {
|
|
101
|
+
return ok(`${whose} copy of ${bold(copy.name)} is ready to edit`);
|
|
102
|
+
}
|
|
103
|
+
return info(`kept ${whose} copy of ${bold(copy.name)}: ${copy.reason ?? ""}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function localCopyLine(result: MarketResult): string[] {
|
|
107
|
+
return [
|
|
108
|
+
...(result.localCopy ? [result.localCopy] : []),
|
|
109
|
+
...(result.localCopies ?? []),
|
|
110
|
+
].map(copyLine);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function installLines(result: MarketResult): string[] {
|
|
114
|
+
const out: string[] = [];
|
|
115
|
+
for (const i of result.installs ?? []) {
|
|
116
|
+
const row = `${scopeBadge(i.scope)} ${result.plugin ?? ""} ${i.to ?? "—"}`;
|
|
117
|
+
if (i.status === "failed") {
|
|
118
|
+
out.push(fail(`${row} ${i.message ?? ""}`));
|
|
119
|
+
if (i.fix) out.push(note(`fix: ${i.fix}`));
|
|
120
|
+
} else {
|
|
121
|
+
out.push(ok(row));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** What a finished command says after its steps. */
|
|
128
|
+
export function resultLines(result: MarketResult): string[] {
|
|
129
|
+
const lines: string[] = [];
|
|
130
|
+
if (result.outcome === "needs-consent" || result.outcome === "stale") {
|
|
131
|
+
if (result.plan) {
|
|
132
|
+
lines.push(
|
|
133
|
+
...planLines({
|
|
134
|
+
command: result.command,
|
|
135
|
+
lines: result.plan,
|
|
136
|
+
planId: result.planId,
|
|
137
|
+
marketplace: result.marketplace,
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
lines.push(warn(result.message));
|
|
142
|
+
if (result.error?.fix) lines.push(note(result.error.fix));
|
|
143
|
+
if (result.outcome === "needs-consent" && !result.error && result.planId) {
|
|
144
|
+
lines.push(note(`to apply it: add --yes=${result.planId}`));
|
|
145
|
+
}
|
|
146
|
+
return ["", ...lines, ""];
|
|
147
|
+
}
|
|
148
|
+
lines.push(...installLines(result), ...localCopyLine(result));
|
|
149
|
+
switch (result.outcome) {
|
|
150
|
+
case "ok":
|
|
151
|
+
case "plugin-newer":
|
|
152
|
+
lines.push(ok(result.message));
|
|
153
|
+
break;
|
|
154
|
+
case "unchanged":
|
|
155
|
+
case "pull-request":
|
|
156
|
+
lines.push(info(result.message));
|
|
157
|
+
break;
|
|
158
|
+
default:
|
|
159
|
+
// A refused publish (no base, or a conflict) shows what differs.
|
|
160
|
+
if (result.steps.length === 0 && result.plan) {
|
|
161
|
+
lines.push(...result.plan.map((l) => info(l)));
|
|
162
|
+
}
|
|
163
|
+
lines.push(fail(result.message));
|
|
164
|
+
if (result.error?.fix) lines.push(note(`fix: ${result.error.fix}`));
|
|
165
|
+
}
|
|
166
|
+
return ["", ...lines, ""];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** The second question before a publish replaces the plugin's version. */
|
|
170
|
+
export function secondQuestion(text: string): string {
|
|
171
|
+
return question(text, "[y/N]");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const STATE_WORDS: Record<SkillStatusRow["state"], string> = {
|
|
175
|
+
edited: "● edited here",
|
|
176
|
+
"no-base": "◇ edited here, origin unknown",
|
|
177
|
+
conflict: "⚠ changed here and on GitHub",
|
|
178
|
+
"plugin-newer": "↓ plugin has a newer version",
|
|
179
|
+
same: "same as the plugin",
|
|
180
|
+
"not-checked": "? not checked",
|
|
181
|
+
ambiguous: "held by more than one company plugin",
|
|
182
|
+
"not-owned": "no company plugin holds it",
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/** `market status`. */
|
|
186
|
+
export function statusLines(result: MarketResult): string[] {
|
|
187
|
+
const lines = headerFor("market status");
|
|
188
|
+
const rows = result.skills ?? [];
|
|
189
|
+
if (rows.length === 0) {
|
|
190
|
+
lines.push(info(result.message));
|
|
191
|
+
return [...lines, ""];
|
|
192
|
+
}
|
|
193
|
+
for (const r of rows) {
|
|
194
|
+
const owner = r.owner ?? r.owners?.join(", ") ?? "";
|
|
195
|
+
const words =
|
|
196
|
+
r.state === "not-checked" && r.reason
|
|
197
|
+
? `${STATE_WORDS[r.state]} — ${r.reason}`
|
|
198
|
+
: STATE_WORDS[r.state];
|
|
199
|
+
lines.push(
|
|
200
|
+
` ${scopeBadge(r.from)} ${bold(r.skill)} ${dim(owner)} ${words}`,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
lines.push("");
|
|
204
|
+
lines.push(info(result.message));
|
|
205
|
+
if (rows.some((r) => r.state === "not-checked")) {
|
|
206
|
+
lines.push(note("run `magus market fetch`, then look again"));
|
|
207
|
+
}
|
|
208
|
+
if (rows.some((r) => r.state === "edited")) {
|
|
209
|
+
lines.push(note("publish one with `magus market publish <skill>`"));
|
|
210
|
+
}
|
|
211
|
+
return [...lines, ""];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** `market list`. */
|
|
215
|
+
export function listLines(result: MarketResult): string[] {
|
|
216
|
+
const lines = headerFor("market list");
|
|
217
|
+
const rows = result.marketplaces ?? [];
|
|
218
|
+
const local = result.local ?? [];
|
|
219
|
+
const localLines =
|
|
220
|
+
local.length === 0
|
|
221
|
+
? []
|
|
222
|
+
: [
|
|
223
|
+
` ${bold("Here, in no marketplace")}`,
|
|
224
|
+
...local.map(
|
|
225
|
+
(l) => ` ${scopeBadge(l.scope)} ${bold(l.name)} ${dim(l.kind)}`,
|
|
226
|
+
),
|
|
227
|
+
note(
|
|
228
|
+
rows.length === 0
|
|
229
|
+
? "a company marketplace is needed before these can be added to one"
|
|
230
|
+
: "add one with `magus market skill add <skill> --plugin <p@m> --create-plugin` or `magus market plugin add <path>`",
|
|
231
|
+
),
|
|
232
|
+
"",
|
|
233
|
+
];
|
|
234
|
+
if (rows.length === 0) {
|
|
235
|
+
lines.push(info(result.message));
|
|
236
|
+
lines.push(
|
|
237
|
+
note(
|
|
238
|
+
"create one with `magus market create`, or `magus market connect` an existing one",
|
|
239
|
+
),
|
|
240
|
+
);
|
|
241
|
+
return [...lines, "", ...localLines];
|
|
242
|
+
}
|
|
243
|
+
for (const m of rows) {
|
|
244
|
+
const by = m.company ? ` by ${m.company}` : "";
|
|
245
|
+
lines.push(
|
|
246
|
+
` ${bold(m.name)}${by} ${dim(m.repo)} ${scopeBadge(m.scope)}`,
|
|
247
|
+
);
|
|
248
|
+
if (m.plugins.length === 0) lines.push(listItem(dim("no plugins yet")));
|
|
249
|
+
for (const p of m.plugins) {
|
|
250
|
+
lines.push(
|
|
251
|
+
field(
|
|
252
|
+
p.name,
|
|
253
|
+
`${p.version} ${dim(p.skills.length === 0 ? "no skills" : p.skills.join(", "))}`,
|
|
254
|
+
),
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
lines.push("");
|
|
258
|
+
}
|
|
259
|
+
return [...lines, ...localLines];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** `magus market --help`. */
|
|
263
|
+
export function marketHelp(): string {
|
|
264
|
+
return `magus market — a company plugin marketplace on GitHub
|
|
265
|
+
|
|
266
|
+
Usage: magus market <command> [args]
|
|
267
|
+
|
|
268
|
+
The screen for all of this is \`magus edit\`; these commands run without one.
|
|
269
|
+
|
|
270
|
+
list Company marketplaces on this machine, with their
|
|
271
|
+
plugins and skills. Changes nothing.
|
|
272
|
+
fetch [<m>] Bring magus's copies of company marketplaces up to
|
|
273
|
+
date with GitHub. Changes nothing else.
|
|
274
|
+
create <owner>/<repo> --private|--public [--name <m>] [--company <name>]
|
|
275
|
+
[--description <t>]
|
|
276
|
+
Create a GitHub repository holding a new company
|
|
277
|
+
marketplace and add it to this machine.
|
|
278
|
+
--company is the name magus shows it by ("by
|
|
279
|
+
<name>"); the GitHub owner when left out. One line,
|
|
280
|
+
at most 64 characters.
|
|
281
|
+
connect <owner/repo|git-url|path> [--name <m>] [--company <name>]
|
|
282
|
+
Use an existing repository as a company marketplace.
|
|
283
|
+
--name and --company only when the repository holds
|
|
284
|
+
no marketplace; otherwise its manifest names both.
|
|
285
|
+
remove <m> Remove a company marketplace from this machine,
|
|
286
|
+
uninstalling its plugins. The repository stays.
|
|
287
|
+
plugin create <p@m> [--description <t>]
|
|
288
|
+
plugin delete <p@m> Add or delete a plugin in the marketplace.
|
|
289
|
+
plugin add <path> [--to <m>] [--name <p>]
|
|
290
|
+
Copy a plugin folder of your own (one holding
|
|
291
|
+
.claude-plugin/plugin.json) into the marketplace
|
|
292
|
+
as a new plugin, at its own version (0.1.0 when it
|
|
293
|
+
has none). The folder stays where it is.
|
|
294
|
+
skill add <skill> --plugin <p@m> [--create-plugin [--description <t>]]
|
|
295
|
+
[--from project|global] [--keep-local]
|
|
296
|
+
Add a skill from this project (or your USER skills)
|
|
297
|
+
to a plugin, then use the plugin's version.
|
|
298
|
+
--create-plugin creates the plugin in the same
|
|
299
|
+
commit when the marketplace has none of that name.
|
|
300
|
+
skill edit <skill> [--plugin <p@m>] [--to project|global]
|
|
301
|
+
Copy a plugin's skill here to edit it.
|
|
302
|
+
skill remove <skill> --plugin <p@m>
|
|
303
|
+
Remove a skill from a plugin.
|
|
304
|
+
status [<skill>] How each local copy of a company skill stands
|
|
305
|
+
against its plugin. Changes nothing.
|
|
306
|
+
publish <skill> [--plugin <p@m>] [--from project|global] [--keep-local]
|
|
307
|
+
[--force] [--dry-run]
|
|
308
|
+
Send a local copy's edits to its plugin as the next
|
|
309
|
+
patch version, move this machine's installs to it,
|
|
310
|
+
then remove the local copy. --force replaces the
|
|
311
|
+
plugin's version when the copy's origin is unknown
|
|
312
|
+
or both sides changed. --dry-run shows the plan.
|
|
313
|
+
publish --plugin <p@m> [--keep-local] [--dry-run]
|
|
314
|
+
Send every skill of the plugin edited here as one
|
|
315
|
+
new patch version. A copy whose origin is unknown,
|
|
316
|
+
or that changed on both sides, is left out: publish
|
|
317
|
+
it on its own with --force.
|
|
318
|
+
|
|
319
|
+
@m may be left out when this machine has one company marketplace.
|
|
320
|
+
|
|
321
|
+
Every command that changes something prints its plan with a planId first.
|
|
322
|
+
--yes=<planId> Apply exactly that plan; a changed plan is refused
|
|
323
|
+
--yes Apply whatever the current plan is (scripts)
|
|
324
|
+
--json One JSON object on the last line of stdout
|
|
325
|
+
|
|
326
|
+
Exit codes: 0 done · 1 failed · 2 usage or unknown name · 3 needs consent,
|
|
327
|
+
the plan changed, or newer commits need fetching · 4 GitHub sign-in needed.
|
|
328
|
+
`;
|
|
329
|
+
}
|