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
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* market-ops-content.ts — what the plans for a marketplace's contents share:
|
|
3
|
+
* reading one plugin from fresh state, its files, the version bump, and the
|
|
4
|
+
* step that moves this machine's installs after a push.
|
|
5
|
+
*
|
|
6
|
+
* Every read here is of the marketplace as GitHub has it now (a stale read
|
|
7
|
+
* decides nothing); nothing here writes outside a step's `run`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { checkIgnore } from "./git-write.js";
|
|
13
|
+
import { forgetBase, forgetBasesFor, recordBase } from "./market-bases.js";
|
|
14
|
+
import {
|
|
15
|
+
type ChangeSpec,
|
|
16
|
+
changeSteps,
|
|
17
|
+
cloneOf,
|
|
18
|
+
deliveryTarget,
|
|
19
|
+
linkRefusal,
|
|
20
|
+
readJson,
|
|
21
|
+
targetLine,
|
|
22
|
+
writeJson,
|
|
23
|
+
} from "./market-change.js";
|
|
24
|
+
import { type Snapshot, cloneUrl } from "./market-clone.js";
|
|
25
|
+
import { refuseLinkUnder } from "./market-fs.js";
|
|
26
|
+
import {
|
|
27
|
+
SYMLINK_MODE,
|
|
28
|
+
type SkillFile,
|
|
29
|
+
addPluginEntry,
|
|
30
|
+
currentVersion,
|
|
31
|
+
findSkillOwners,
|
|
32
|
+
inDomain,
|
|
33
|
+
joinRel,
|
|
34
|
+
nextVersion,
|
|
35
|
+
pluginEntries,
|
|
36
|
+
pluginRelDir,
|
|
37
|
+
pluginRootOf,
|
|
38
|
+
removePluginEntry,
|
|
39
|
+
setEntryVersion,
|
|
40
|
+
treeHash,
|
|
41
|
+
validPackageName,
|
|
42
|
+
validSkillName,
|
|
43
|
+
} from "./market-layout.js";
|
|
44
|
+
import {
|
|
45
|
+
type MarketCommand,
|
|
46
|
+
computePlanId,
|
|
47
|
+
freshSnapshot,
|
|
48
|
+
manifestAt,
|
|
49
|
+
planError,
|
|
50
|
+
resolvePluginRef,
|
|
51
|
+
} from "./market-ops.js";
|
|
52
|
+
import {
|
|
53
|
+
type InstalledAt,
|
|
54
|
+
installReaches,
|
|
55
|
+
installedScopes,
|
|
56
|
+
moveInstalls,
|
|
57
|
+
profileManaged,
|
|
58
|
+
readSourceSets,
|
|
59
|
+
removeLocalCopy,
|
|
60
|
+
} from "./market-publish.js";
|
|
61
|
+
import {
|
|
62
|
+
type TrustedRegistration,
|
|
63
|
+
listCompanyMarketplaces,
|
|
64
|
+
} from "./market-registry.js";
|
|
65
|
+
import {
|
|
66
|
+
type FileChange,
|
|
67
|
+
type LocalCopy,
|
|
68
|
+
MarketError,
|
|
69
|
+
type MarketPlan,
|
|
70
|
+
type MarketStep,
|
|
71
|
+
type PlanAnswer,
|
|
72
|
+
} from "./market-types.js";
|
|
73
|
+
import { fastForwardCloneIfPresent } from "./marketplace-refresh.js";
|
|
74
|
+
import { getUserSkillsDir } from "./skills-manager.js";
|
|
75
|
+
|
|
76
|
+
export const MANIFEST = ".claude-plugin/marketplace.json";
|
|
77
|
+
export const SCOPE_WORD = {
|
|
78
|
+
user: "USER",
|
|
79
|
+
project: "PROJECT",
|
|
80
|
+
local: "LOCAL",
|
|
81
|
+
} as const;
|
|
82
|
+
|
|
83
|
+
/** What a content plan reads first: the marketplace and one plugin in it. */
|
|
84
|
+
export interface PluginView {
|
|
85
|
+
m: TrustedRegistration;
|
|
86
|
+
snap: Snapshot;
|
|
87
|
+
manifest: Record<string, unknown>;
|
|
88
|
+
plugin: string;
|
|
89
|
+
/** The plugin's directory in the repository. */
|
|
90
|
+
dir: string;
|
|
91
|
+
pluginJsonPath: string;
|
|
92
|
+
pluginJson: unknown;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function readPlugin(ref: string): Promise<PluginView> {
|
|
96
|
+
const { plugin, m } = await resolvePluginRef(ref);
|
|
97
|
+
const snap = await freshSnapshot(m);
|
|
98
|
+
return pluginViewAt(m, snap, await manifestAt(snap, m.name), plugin);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** One plugin of a marketplace snapshot already read. */
|
|
102
|
+
export async function pluginViewAt(
|
|
103
|
+
m: TrustedRegistration,
|
|
104
|
+
snap: Snapshot,
|
|
105
|
+
manifest: Record<string, unknown>,
|
|
106
|
+
plugin: string,
|
|
107
|
+
): Promise<PluginView> {
|
|
108
|
+
const entry = pluginEntries(manifest).find((p) => p.name === plugin);
|
|
109
|
+
if (!entry) {
|
|
110
|
+
throw new MarketError(
|
|
111
|
+
"not-found",
|
|
112
|
+
`${m.name} has no plugin named ${plugin}`,
|
|
113
|
+
"`magus market list` shows its plugins",
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
const dir = pluginRelDir(entry.source, pluginRootOf(manifest));
|
|
117
|
+
if (dir === null) {
|
|
118
|
+
throw new MarketError(
|
|
119
|
+
"external-plugin",
|
|
120
|
+
`${plugin} lives outside ${m.name}'s repository, so magus cannot change it`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
const pluginJsonPath = joinRel(dir, ".claude-plugin", "plugin.json");
|
|
124
|
+
return {
|
|
125
|
+
m,
|
|
126
|
+
snap,
|
|
127
|
+
manifest,
|
|
128
|
+
plugin,
|
|
129
|
+
dir,
|
|
130
|
+
pluginJsonPath,
|
|
131
|
+
pluginJson: await snap.readJson(pluginJsonPath),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Every blob under `dir/` at the snapshot, relative to `dir`. */
|
|
136
|
+
export function filesUnder(
|
|
137
|
+
snap: Snapshot,
|
|
138
|
+
dir: string,
|
|
139
|
+
): Array<SkillFile & { mode: string }> {
|
|
140
|
+
const prefix = dir ? `${dir}/` : "";
|
|
141
|
+
const out: Array<SkillFile & { mode: string }> = [];
|
|
142
|
+
for (const [p, e] of snap.entries) {
|
|
143
|
+
if (p.startsWith(prefix)) {
|
|
144
|
+
out.push({ path: p.slice(prefix.length), oid: e.oid, mode: e.mode });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return out.sort((a, b) => (a.path < b.path ? -1 : 1));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function versionLine(
|
|
151
|
+
plugin: string,
|
|
152
|
+
from: string | null,
|
|
153
|
+
to: string,
|
|
154
|
+
): string {
|
|
155
|
+
return from === null
|
|
156
|
+
? `${plugin} is unversioned → ${to}`
|
|
157
|
+
: `${plugin} ${from} → ${to}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** The plugin's next version, or an error for one magus will not bump. */
|
|
161
|
+
export function bumpFrom(view: PluginView): {
|
|
162
|
+
from: string | null;
|
|
163
|
+
to: string;
|
|
164
|
+
} {
|
|
165
|
+
const entry = pluginEntries(view.manifest).find(
|
|
166
|
+
(p) => p.name === view.plugin,
|
|
167
|
+
);
|
|
168
|
+
const from = currentVersion(view.pluginJson, entry);
|
|
169
|
+
const to = nextVersion(from);
|
|
170
|
+
if (!to) {
|
|
171
|
+
throw new MarketError(
|
|
172
|
+
"other",
|
|
173
|
+
`${view.plugin}'s version ${from} is not major.minor.patch, so magus cannot bump it`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return { from, to };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Write the new version to the entry, and to `plugin.json` when it exists. */
|
|
180
|
+
export function bumpStep(view: PluginView, to: string): MarketStep {
|
|
181
|
+
return {
|
|
182
|
+
id: "bump",
|
|
183
|
+
label: `${view.plugin} ${to}`,
|
|
184
|
+
outside: false,
|
|
185
|
+
async run(ctx) {
|
|
186
|
+
const clone = cloneOf(ctx);
|
|
187
|
+
const label = view.m.name;
|
|
188
|
+
const manifest = readJson(clone, MANIFEST, label);
|
|
189
|
+
writeJson(
|
|
190
|
+
clone,
|
|
191
|
+
MANIFEST,
|
|
192
|
+
setEntryVersion(manifest, view.plugin, to),
|
|
193
|
+
label,
|
|
194
|
+
);
|
|
195
|
+
refuseLinkUnder(clone, view.pluginJsonPath, linkRefusal(label));
|
|
196
|
+
if (fs.existsSync(path.join(clone, view.pluginJsonPath))) {
|
|
197
|
+
const current = readJson(clone, view.pluginJsonPath, label);
|
|
198
|
+
writeJson(
|
|
199
|
+
clone,
|
|
200
|
+
view.pluginJsonPath,
|
|
201
|
+
{ ...current, version: to },
|
|
202
|
+
label,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
return undefined;
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** The version files a bump changes, as staged paths and expected changes. */
|
|
211
|
+
export function versionFiles(
|
|
212
|
+
view: PluginView,
|
|
213
|
+
): Array<{ path: string; change: FileChange }> {
|
|
214
|
+
const out: Array<{ path: string; change: FileChange }> = [
|
|
215
|
+
{ path: MANIFEST, change: "modified" },
|
|
216
|
+
];
|
|
217
|
+
if (view.pluginJson !== null) {
|
|
218
|
+
out.push({ path: view.pluginJsonPath, change: "modified" });
|
|
219
|
+
}
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function installLines(
|
|
224
|
+
plugin: string,
|
|
225
|
+
installs: readonly InstalledAt[],
|
|
226
|
+
to: string,
|
|
227
|
+
): string[] {
|
|
228
|
+
if (installs.length === 0) {
|
|
229
|
+
return [`${plugin} is not installed here, so no install moves`];
|
|
230
|
+
}
|
|
231
|
+
return installs.map(
|
|
232
|
+
(i) => `Moves ${SCOPE_WORD[i.scope]} ${plugin} ${i.version} → ${to}`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Step 7: move this machine's installs to the version just pushed. */
|
|
237
|
+
export function installStep(
|
|
238
|
+
view: PluginView,
|
|
239
|
+
installs: readonly InstalledAt[],
|
|
240
|
+
to: string,
|
|
241
|
+
state: { moved?: boolean },
|
|
242
|
+
): MarketStep {
|
|
243
|
+
return {
|
|
244
|
+
id: "install",
|
|
245
|
+
label: `moved installs to ${to}`,
|
|
246
|
+
outside: true,
|
|
247
|
+
async run(ctx) {
|
|
248
|
+
if (installs.length === 0) {
|
|
249
|
+
state.moved = false;
|
|
250
|
+
return { skipped: true };
|
|
251
|
+
}
|
|
252
|
+
const r = await moveInstalls(
|
|
253
|
+
view.m.name,
|
|
254
|
+
`${view.plugin}@${view.m.name}`,
|
|
255
|
+
installs,
|
|
256
|
+
to,
|
|
257
|
+
ctx.cwd,
|
|
258
|
+
);
|
|
259
|
+
state.moved = r.ok;
|
|
260
|
+
if (r.ok) return { patch: { installs: r.installs } };
|
|
261
|
+
return {
|
|
262
|
+
patch: {
|
|
263
|
+
installs: r.installs,
|
|
264
|
+
outcome: "partial",
|
|
265
|
+
message:
|
|
266
|
+
r.message ??
|
|
267
|
+
`The push landed, but not every install moved to ${to}`,
|
|
268
|
+
error: {
|
|
269
|
+
code: "install-failed",
|
|
270
|
+
message: r.message ?? "not every install moved",
|
|
271
|
+
fix: r.fix ?? "magus update",
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|