magus-cli 7.13.0 → 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 +7 -4
- package/package.json +4 -4
- 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-router.test.ts +4 -4
- package/src/cli/market-view.ts +59 -18
- package/src/cli/market.ts +128 -44
- package/src/cli/router.ts +42 -10
- package/src/data/marketplaces.ts +1 -0
- package/src/main.tsx +1 -1
- package/src/market-tui.tsx +1 -1
- package/src/services/local-marketplace.ts +8 -0
- package/src/services/market-change.ts +6 -6
- package/src/services/market-local.ts +93 -0
- package/src/services/market-ops-plugin.ts +366 -47
- package/src/services/market-ops-publish.ts +363 -15
- package/src/services/market-ops-repo.ts +94 -13
- package/src/services/market-ops-skill.ts +93 -11
- package/src/services/market-ops.ts +72 -8
- package/src/services/market-publish.ts +10 -1
- package/src/services/market-types.ts +23 -0
- package/src/tui-runtime.tsx +2 -2
- package/src/types/index.ts +2 -0
- package/src/ui/adapters/marketAdapter.ts +123 -4
- package/src/ui/adapters/pluginsAdapter.ts +25 -1
- package/src/ui/components/primitives/ListCategoryRow.tsx +22 -2
- package/src/ui/market/MarketplaceScreen.tsx +55 -24
- package/src/ui/market/MarketplacesDetail.snowflake.tsx +60 -4
- package/src/ui/market/MarketplacesScreen.tsx +120 -29
- package/src/ui/market/PluginDetail.snowflake.tsx +12 -1
- package/src/ui/market/marketKeymap.ts +29 -2
- package/src/ui/market/useMarketState.ts +146 -5
- package/src/utils/display-path.ts +33 -0
package/src/cli/market-view.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type {
|
|
11
|
+
LocalCopy,
|
|
11
12
|
MarketPlan,
|
|
12
13
|
MarketResult,
|
|
13
14
|
SkillStatusRow,
|
|
@@ -89,23 +90,24 @@ export function stepLine(step: StepRecord, label: string): string {
|
|
|
89
90
|
return fail(`${label}: ${step.detail ?? "failed"}`);
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
function
|
|
93
|
-
const copy = result.localCopy;
|
|
94
|
-
if (!copy) return [];
|
|
93
|
+
function copyLine(copy: LocalCopy): string {
|
|
95
94
|
const whose = copy.scope === "user" ? "your USER" : "this project's";
|
|
96
95
|
if (copy.action === "removed") {
|
|
97
|
-
return
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
),
|
|
101
|
-
];
|
|
96
|
+
return ok(
|
|
97
|
+
`removed ${whose} copy of ${bold(copy.name)}${copy.scope === "project" ? ` — commit the deletion (.claude/skills/${copy.name})` : ""}`,
|
|
98
|
+
);
|
|
102
99
|
}
|
|
103
100
|
if (copy.action === "created") {
|
|
104
|
-
return
|
|
101
|
+
return ok(`${whose} copy of ${bold(copy.name)} is ready to edit`);
|
|
105
102
|
}
|
|
103
|
+
return info(`kept ${whose} copy of ${bold(copy.name)}: ${copy.reason ?? ""}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function localCopyLine(result: MarketResult): string[] {
|
|
106
107
|
return [
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
...(result.localCopy ? [result.localCopy] : []),
|
|
109
|
+
...(result.localCopies ?? []),
|
|
110
|
+
].map(copyLine);
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
function installLines(result: MarketResult): string[] {
|
|
@@ -213,6 +215,22 @@ export function statusLines(result: MarketResult): string[] {
|
|
|
213
215
|
export function listLines(result: MarketResult): string[] {
|
|
214
216
|
const lines = headerFor("market list");
|
|
215
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
|
+
];
|
|
216
234
|
if (rows.length === 0) {
|
|
217
235
|
lines.push(info(result.message));
|
|
218
236
|
lines.push(
|
|
@@ -220,10 +238,13 @@ export function listLines(result: MarketResult): string[] {
|
|
|
220
238
|
"create one with `magus market create`, or `magus market connect` an existing one",
|
|
221
239
|
),
|
|
222
240
|
);
|
|
223
|
-
return [...lines, ""];
|
|
241
|
+
return [...lines, "", ...localLines];
|
|
224
242
|
}
|
|
225
243
|
for (const m of rows) {
|
|
226
|
-
|
|
244
|
+
const by = m.company ? ` by ${m.company}` : "";
|
|
245
|
+
lines.push(
|
|
246
|
+
` ${bold(m.name)}${by} ${dim(m.repo)} ${scopeBadge(m.scope)}`,
|
|
247
|
+
);
|
|
227
248
|
if (m.plugins.length === 0) lines.push(listItem(dim("no plugins yet")));
|
|
228
249
|
for (const p of m.plugins) {
|
|
229
250
|
lines.push(
|
|
@@ -235,7 +256,7 @@ export function listLines(result: MarketResult): string[] {
|
|
|
235
256
|
}
|
|
236
257
|
lines.push("");
|
|
237
258
|
}
|
|
238
|
-
return lines;
|
|
259
|
+
return [...lines, ...localLines];
|
|
239
260
|
}
|
|
240
261
|
|
|
241
262
|
/** `magus market --help`. */
|
|
@@ -244,23 +265,38 @@ export function marketHelp(): string {
|
|
|
244
265
|
|
|
245
266
|
Usage: magus market <command> [args]
|
|
246
267
|
|
|
268
|
+
The screen for all of this is \`magus edit\`; these commands run without one.
|
|
269
|
+
|
|
247
270
|
list Company marketplaces on this machine, with their
|
|
248
271
|
plugins and skills. Changes nothing.
|
|
249
272
|
fetch [<m>] Bring magus's copies of company marketplaces up to
|
|
250
273
|
date with GitHub. Changes nothing else.
|
|
251
|
-
create <owner>/<repo> --private|--public [--name <m>] [--
|
|
274
|
+
create <owner>/<repo> --private|--public [--name <m>] [--company <name>]
|
|
275
|
+
[--description <t>]
|
|
252
276
|
Create a GitHub repository holding a new company
|
|
253
277
|
marketplace and add it to this machine.
|
|
254
|
-
|
|
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>]
|
|
255
282
|
Use an existing repository as a company marketplace.
|
|
256
|
-
--name only when the repository holds
|
|
283
|
+
--name and --company only when the repository holds
|
|
284
|
+
no marketplace; otherwise its manifest names both.
|
|
257
285
|
remove <m> Remove a company marketplace from this machine,
|
|
258
286
|
uninstalling its plugins. The repository stays.
|
|
259
287
|
plugin create <p@m> [--description <t>]
|
|
260
288
|
plugin delete <p@m> Add or delete a plugin in the marketplace.
|
|
261
|
-
|
|
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]
|
|
262
296
|
Add a skill from this project (or your USER skills)
|
|
263
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.
|
|
264
300
|
skill edit <skill> [--plugin <p@m>] [--to project|global]
|
|
265
301
|
Copy a plugin's skill here to edit it.
|
|
266
302
|
skill remove <skill> --plugin <p@m>
|
|
@@ -274,6 +310,11 @@ Usage: magus market <command> [args]
|
|
|
274
310
|
then remove the local copy. --force replaces the
|
|
275
311
|
plugin's version when the copy's origin is unknown
|
|
276
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.
|
|
277
318
|
|
|
278
319
|
@m may be left out when this machine has one company marketplace.
|
|
279
320
|
|
package/src/cli/market.ts
CHANGED
|
@@ -7,17 +7,18 @@
|
|
|
7
7
|
* only that plan. Under `--json` one MarketResult is printed, on the last
|
|
8
8
|
* line of stdout, and nothing is asked.
|
|
9
9
|
*
|
|
10
|
-
* Bare `magus market`
|
|
11
|
-
*
|
|
10
|
+
* Bare `magus market` prints the help; the interactive screen is `magus
|
|
11
|
+
* edit`.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
|
+
planPluginAdd,
|
|
15
16
|
planPluginCreate,
|
|
16
17
|
planPluginDelete,
|
|
17
18
|
} from "../services/market-ops-plugin.js";
|
|
18
19
|
import {
|
|
19
|
-
type PublishArgs,
|
|
20
20
|
marketStatus,
|
|
21
|
+
planPluginPublish,
|
|
21
22
|
planPublish,
|
|
22
23
|
provisionalPublish,
|
|
23
24
|
} from "../services/market-ops-publish.js";
|
|
@@ -61,6 +62,7 @@ import { askYesNo, promptValue } from "./prompt.js";
|
|
|
61
62
|
/** Flags that take a value, and flags that stand alone. */
|
|
62
63
|
const VALUE_FLAGS = new Set([
|
|
63
64
|
"--name",
|
|
65
|
+
"--company",
|
|
64
66
|
"--description",
|
|
65
67
|
"--plugin",
|
|
66
68
|
"--from",
|
|
@@ -70,6 +72,7 @@ const BOOL_FLAGS = new Set([
|
|
|
70
72
|
"--private",
|
|
71
73
|
"--public",
|
|
72
74
|
"--keep-local",
|
|
75
|
+
"--create-plugin",
|
|
73
76
|
"--force",
|
|
74
77
|
"--dry-run",
|
|
75
78
|
"--json",
|
|
@@ -276,15 +279,15 @@ function warnBlock(text: string): string {
|
|
|
276
279
|
|
|
277
280
|
/**
|
|
278
281
|
* `publish --dry-run`: the plan, or the answer a publish would give, and
|
|
279
|
-
* nothing written. On state older than GitHub's, a provisional
|
|
280
|
-
* compare, still exit 0.
|
|
282
|
+
* nothing written. On state older than GitHub's, a provisional answer with
|
|
283
|
+
* no compare (`provisional`), still exit 0; without one, that error.
|
|
281
284
|
*/
|
|
282
285
|
async function dryRunPublish(
|
|
283
|
-
|
|
286
|
+
plan: () => Promise<PlanAnswer>,
|
|
284
287
|
io: Io,
|
|
285
|
-
|
|
288
|
+
provisional?: (error: MarketError) => MarketResult,
|
|
286
289
|
): Promise<number> {
|
|
287
|
-
const answer = await
|
|
290
|
+
const answer = await plan();
|
|
288
291
|
if (answer.kind === "plan") {
|
|
289
292
|
const p = answer.plan;
|
|
290
293
|
const result: MarketResult = {
|
|
@@ -307,8 +310,11 @@ async function dryRunPublish(
|
|
|
307
310
|
emit(answer.result, io);
|
|
308
311
|
return exitCodeFor(answer.result);
|
|
309
312
|
}
|
|
310
|
-
if (
|
|
311
|
-
|
|
313
|
+
if (
|
|
314
|
+
provisional &&
|
|
315
|
+
(answer.error.code === "stale" || answer.error.code === "no-network")
|
|
316
|
+
) {
|
|
317
|
+
emit(provisional(answer.error), io);
|
|
312
318
|
return 0;
|
|
313
319
|
}
|
|
314
320
|
const result = errorResult(answer.command, answer.error);
|
|
@@ -316,15 +322,12 @@ async function dryRunPublish(
|
|
|
316
322
|
return answer.error.exit ?? exitCodeFor(result);
|
|
317
323
|
}
|
|
318
324
|
|
|
319
|
-
/**
|
|
320
|
-
* Run `magus market <argv>`. Returns the exit code, or "tui" for a bare
|
|
321
|
-
* `magus market`.
|
|
322
|
-
*/
|
|
325
|
+
/** Run `magus market <argv>`. Returns the exit code. */
|
|
323
326
|
export async function runMarketCommand(
|
|
324
327
|
argv: string[],
|
|
325
328
|
version: string,
|
|
326
329
|
cwd: string = process.cwd(),
|
|
327
|
-
): Promise<number
|
|
330
|
+
): Promise<number> {
|
|
328
331
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
329
332
|
console.log(marketHelp());
|
|
330
333
|
return 0;
|
|
@@ -333,7 +336,11 @@ export async function runMarketCommand(
|
|
|
333
336
|
console.log(`magus v${version}`);
|
|
334
337
|
return 0;
|
|
335
338
|
}
|
|
336
|
-
if (argv.length === 0)
|
|
339
|
+
if (argv.length === 0) {
|
|
340
|
+
console.log(marketHelp());
|
|
341
|
+
console.log("Open the screen with `magus edit`.");
|
|
342
|
+
return 0;
|
|
343
|
+
}
|
|
337
344
|
|
|
338
345
|
const json = argv.includes("--json");
|
|
339
346
|
const io: Io = {
|
|
@@ -348,7 +355,7 @@ export async function runMarketCommand(
|
|
|
348
355
|
switch (verb) {
|
|
349
356
|
case "list": {
|
|
350
357
|
expect(p, [], { min: 0, max: 0, usage: "magus market list [--json]" });
|
|
351
|
-
const result = await marketList();
|
|
358
|
+
const result = await marketList(cwd);
|
|
352
359
|
emit(result, io);
|
|
353
360
|
return exitCodeFor(result);
|
|
354
361
|
}
|
|
@@ -367,12 +374,16 @@ export async function runMarketCommand(
|
|
|
367
374
|
}
|
|
368
375
|
case "create": {
|
|
369
376
|
const usage =
|
|
370
|
-
"magus market create <owner>/<repo> --private|--public [--name <m>] [--description <t>]";
|
|
371
|
-
expect(
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
377
|
+
"magus market create <owner>/<repo> --private|--public [--name <m>] [--company <name>] [--description <t>]";
|
|
378
|
+
expect(
|
|
379
|
+
p,
|
|
380
|
+
["--private", "--public", "--name", "--company", "--description"],
|
|
381
|
+
{
|
|
382
|
+
min: 1,
|
|
383
|
+
max: 1,
|
|
384
|
+
usage,
|
|
385
|
+
},
|
|
386
|
+
);
|
|
376
387
|
const priv = p.flags.has("--private");
|
|
377
388
|
const pub = p.flags.has("--public");
|
|
378
389
|
if (priv === pub) {
|
|
@@ -386,6 +397,7 @@ export async function runMarketCommand(
|
|
|
386
397
|
repo: p.positional[0] as string,
|
|
387
398
|
visibility: priv ? "private" : "public",
|
|
388
399
|
name: str(p, "--name"),
|
|
400
|
+
company: str(p, "--company"),
|
|
389
401
|
description: str(p, "--description"),
|
|
390
402
|
}),
|
|
391
403
|
io,
|
|
@@ -394,15 +406,20 @@ export async function runMarketCommand(
|
|
|
394
406
|
);
|
|
395
407
|
}
|
|
396
408
|
case "connect": {
|
|
397
|
-
expect(p, ["--name"], {
|
|
409
|
+
expect(p, ["--name", "--company"], {
|
|
398
410
|
min: 1,
|
|
399
411
|
max: 1,
|
|
400
|
-
usage:
|
|
412
|
+
usage:
|
|
413
|
+
"magus market connect <owner/repo|git-url|path> [--name <m>] [--company <name>]",
|
|
401
414
|
});
|
|
402
415
|
return await runWrite(
|
|
403
416
|
() =>
|
|
404
417
|
planConnect(
|
|
405
|
-
{
|
|
418
|
+
{
|
|
419
|
+
source: p.positional[0] as string,
|
|
420
|
+
name: str(p, "--name"),
|
|
421
|
+
company: str(p, "--company"),
|
|
422
|
+
},
|
|
406
423
|
cwd,
|
|
407
424
|
),
|
|
408
425
|
io,
|
|
@@ -435,6 +452,27 @@ export async function runMarketCommand(
|
|
|
435
452
|
cwd,
|
|
436
453
|
);
|
|
437
454
|
}
|
|
455
|
+
if (sub === "add") {
|
|
456
|
+
expect(sp, ["--to", "--name"], {
|
|
457
|
+
min: 1,
|
|
458
|
+
max: 1,
|
|
459
|
+
usage: "magus market plugin add <path> [--to <m>] [--name <p>]",
|
|
460
|
+
});
|
|
461
|
+
return await runWrite(
|
|
462
|
+
() =>
|
|
463
|
+
planPluginAdd(
|
|
464
|
+
{
|
|
465
|
+
path: ref as string,
|
|
466
|
+
to: str(p, "--to"),
|
|
467
|
+
name: str(p, "--name"),
|
|
468
|
+
},
|
|
469
|
+
cwd,
|
|
470
|
+
),
|
|
471
|
+
io,
|
|
472
|
+
yes,
|
|
473
|
+
cwd,
|
|
474
|
+
);
|
|
475
|
+
}
|
|
438
476
|
if (sub === "delete") {
|
|
439
477
|
expect(sp, [], {
|
|
440
478
|
min: 1,
|
|
@@ -448,22 +486,40 @@ export async function runMarketCommand(
|
|
|
448
486
|
cwd,
|
|
449
487
|
);
|
|
450
488
|
}
|
|
451
|
-
throw new UsageError(
|
|
489
|
+
throw new UsageError(
|
|
490
|
+
"usage: magus market plugin create|delete <p@m>, or magus market plugin add <path>",
|
|
491
|
+
);
|
|
452
492
|
}
|
|
453
493
|
case "skill": {
|
|
454
494
|
const [sub, skill, ...extra] = p.positional;
|
|
455
495
|
const sp = { ...p, positional: skill ? [skill, ...extra] : [] };
|
|
456
496
|
if (sub === "add") {
|
|
457
497
|
const usage =
|
|
458
|
-
"magus market skill add <skill> --plugin <p@m> [--from project|global] [--keep-local]";
|
|
459
|
-
expect(
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
498
|
+
"magus market skill add <skill> --plugin <p@m> [--create-plugin [--description <t>]] [--from project|global] [--keep-local]";
|
|
499
|
+
expect(
|
|
500
|
+
sp,
|
|
501
|
+
[
|
|
502
|
+
"--plugin",
|
|
503
|
+
"--from",
|
|
504
|
+
"--keep-local",
|
|
505
|
+
"--create-plugin",
|
|
506
|
+
"--description",
|
|
507
|
+
],
|
|
508
|
+
{
|
|
509
|
+
min: 1,
|
|
510
|
+
max: 1,
|
|
511
|
+
usage,
|
|
512
|
+
},
|
|
513
|
+
);
|
|
464
514
|
const plugin = str(p, "--plugin");
|
|
465
515
|
if (!plugin)
|
|
466
516
|
throw new UsageError(`--plugin is required — usage: ${usage}`);
|
|
517
|
+
const createPlugin = p.flags.has("--create-plugin");
|
|
518
|
+
if (p.flags.has("--description") && !createPlugin) {
|
|
519
|
+
throw new UsageError(
|
|
520
|
+
`--description describes a plugin --create-plugin creates — usage: ${usage}`,
|
|
521
|
+
);
|
|
522
|
+
}
|
|
467
523
|
const from = choice(
|
|
468
524
|
p,
|
|
469
525
|
"--from",
|
|
@@ -478,6 +534,12 @@ export async function runMarketCommand(
|
|
|
478
534
|
plugin,
|
|
479
535
|
from,
|
|
480
536
|
keepLocal: p.flags.has("--keep-local"),
|
|
537
|
+
...(createPlugin
|
|
538
|
+
? {
|
|
539
|
+
createPlugin: true,
|
|
540
|
+
description: str(p, "--description"),
|
|
541
|
+
}
|
|
542
|
+
: {}),
|
|
481
543
|
},
|
|
482
544
|
cwd,
|
|
483
545
|
),
|
|
@@ -538,25 +600,47 @@ export async function runMarketCommand(
|
|
|
538
600
|
return exitCodeFor(result);
|
|
539
601
|
}
|
|
540
602
|
case "publish": {
|
|
603
|
+
const usage =
|
|
604
|
+
"magus market publish <skill> [--plugin <p@m>] [--from project|global] [--keep-local] [--force] [--dry-run], or magus market publish --plugin <p@m> [--keep-local] [--dry-run]";
|
|
541
605
|
expect(
|
|
542
606
|
p,
|
|
543
607
|
["--plugin", "--from", "--keep-local", "--force", "--dry-run"],
|
|
544
|
-
{
|
|
545
|
-
min: 1,
|
|
546
|
-
max: 1,
|
|
547
|
-
usage:
|
|
548
|
-
"magus market publish <skill> [--plugin <p@m>] [--from project|global] [--keep-local] [--force] [--dry-run]",
|
|
549
|
-
},
|
|
608
|
+
{ min: 0, max: 1, usage },
|
|
550
609
|
);
|
|
551
|
-
const
|
|
552
|
-
|
|
610
|
+
const skill = p.positional[0];
|
|
611
|
+
const keepLocal = p.flags.has("--keep-local");
|
|
612
|
+
if (skill === undefined) {
|
|
613
|
+
const plugin = str(p, "--plugin");
|
|
614
|
+
if (!plugin) {
|
|
615
|
+
throw new UsageError(
|
|
616
|
+
`name a skill, or a plugin with --plugin — usage: ${usage}`,
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
for (const flag of ["--from", "--force"]) {
|
|
620
|
+
if (p.flags.has(flag)) {
|
|
621
|
+
throw new UsageError(
|
|
622
|
+
`${flag} applies to one named skill: \`magus market publish <skill> ${flag} …\``,
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
const plan = () => planPluginPublish({ plugin, keepLocal }, cwd);
|
|
627
|
+
if (p.flags.has("--dry-run")) return await dryRunPublish(plan, io);
|
|
628
|
+
return await runWrite(plan, io, yes, cwd);
|
|
629
|
+
}
|
|
630
|
+
const args = {
|
|
631
|
+
skill,
|
|
553
632
|
plugin: str(p, "--plugin"),
|
|
554
633
|
from: choice(p, "--from", ["project", "global"] as const, "project"),
|
|
555
|
-
keepLocal
|
|
634
|
+
keepLocal,
|
|
556
635
|
force: p.flags.has("--force"),
|
|
557
636
|
};
|
|
558
|
-
|
|
559
|
-
|
|
637
|
+
const plan = () => planPublish(args, cwd);
|
|
638
|
+
if (p.flags.has("--dry-run")) {
|
|
639
|
+
return await dryRunPublish(plan, io, (error) =>
|
|
640
|
+
provisionalPublish(args, cwd, error),
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
return await runWrite(plan, io, yes, cwd);
|
|
560
644
|
}
|
|
561
645
|
default:
|
|
562
646
|
throw new UsageError(
|
package/src/cli/router.ts
CHANGED
|
@@ -30,8 +30,8 @@ export interface RouteOutcome {
|
|
|
30
30
|
theme?: ThemeMode;
|
|
31
31
|
/**
|
|
32
32
|
* Which interactive app to launch when `launchTui` is true: the dependency
|
|
33
|
-
* TUI (bare `magus`, the default) or the company marketplace screens
|
|
34
|
-
* `magus
|
|
33
|
+
* TUI (bare `magus`, the default) or the company marketplace screens
|
|
34
|
+
* (`magus edit`).
|
|
35
35
|
*/
|
|
36
36
|
app?: "main" | "market";
|
|
37
37
|
}
|
|
@@ -93,6 +93,21 @@ const HANDLED = (exitCode: number): RouteOutcome => ({
|
|
|
93
93
|
});
|
|
94
94
|
const LAUNCH_TUI: RouteOutcome = { handled: false, launchTui: true };
|
|
95
95
|
|
|
96
|
+
/** `magus edit --help`. */
|
|
97
|
+
function editHelp(): string {
|
|
98
|
+
return `magus edit — open the company marketplace screens
|
|
99
|
+
|
|
100
|
+
Usage: magus edit
|
|
101
|
+
|
|
102
|
+
Lists the company marketplaces on this machine, their plugins and skills, and
|
|
103
|
+
creates, connects and changes them: publishes edited skills (one skill, or a
|
|
104
|
+
whole plugin as one new version), and adds this project's and your USER skills
|
|
105
|
+
and plugin folders to a marketplace. Needs a terminal.
|
|
106
|
+
|
|
107
|
+
\`magus market --help\` lists the commands that do the same without one.
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
|
|
96
111
|
/**
|
|
97
112
|
* Every name the switch below handles.
|
|
98
113
|
*
|
|
@@ -108,6 +123,7 @@ const SUBCOMMANDS = new Set([
|
|
|
108
123
|
"models",
|
|
109
124
|
"doctor",
|
|
110
125
|
"market",
|
|
126
|
+
"edit",
|
|
111
127
|
]);
|
|
112
128
|
|
|
113
129
|
/**
|
|
@@ -130,10 +146,10 @@ export async function route(
|
|
|
130
146
|
themeEnv?: ThemeEnv,
|
|
131
147
|
): Promise<RouteOutcome> {
|
|
132
148
|
// Top-level flags take precedence over any command token — for every
|
|
133
|
-
// command but `market
|
|
134
|
-
// write nothing doing so). `magus upgrade --help` must still
|
|
135
|
-
// rather than upgrade, so this exception is exactly argv[0]
|
|
136
|
-
const marketFirst = argv[0] === "market";
|
|
149
|
+
// command but `market` and `edit`, which answer --help and --version
|
|
150
|
+
// themselves (and write nothing doing so). `magus upgrade --help` must still
|
|
151
|
+
// print help rather than upgrade, so this exception is exactly argv[0].
|
|
152
|
+
const marketFirst = argv[0] === "market" || argv[0] === "edit";
|
|
137
153
|
if (!marketFirst && (argv.includes("--version") || argv.includes("-v"))) {
|
|
138
154
|
console.log(`magus v${version}`);
|
|
139
155
|
const result = await checkForUpdates();
|
|
@@ -229,8 +245,23 @@ export async function route(
|
|
|
229
245
|
return HANDLED(await runDoctorCommand(rest));
|
|
230
246
|
case "market": {
|
|
231
247
|
const { runMarketCommand } = await import("./market.js");
|
|
232
|
-
|
|
233
|
-
|
|
248
|
+
return HANDLED(await runMarketCommand(rest, version));
|
|
249
|
+
}
|
|
250
|
+
case "edit": {
|
|
251
|
+
if (rest.includes("--help") || rest.includes("-h")) {
|
|
252
|
+
console.log(editHelp());
|
|
253
|
+
return HANDLED(0);
|
|
254
|
+
}
|
|
255
|
+
if (rest.includes("--version") || rest.includes("-v")) {
|
|
256
|
+
console.log(`magus v${version}`);
|
|
257
|
+
return HANDLED(0);
|
|
258
|
+
}
|
|
259
|
+
if (rest.length > 0) {
|
|
260
|
+
console.error(
|
|
261
|
+
"magus edit: takes no arguments — the commands are `magus market <verb>` (`magus market --help` lists them)",
|
|
262
|
+
);
|
|
263
|
+
return HANDLED(2);
|
|
264
|
+
}
|
|
234
265
|
return {
|
|
235
266
|
handled: false,
|
|
236
267
|
launchTui: true,
|
|
@@ -327,14 +358,15 @@ applied by a PreToolUse hook magus registers at user scope.
|
|
|
327
358
|
|
|
328
359
|
Company marketplaces — your team's own Claude Code plugin marketplace on
|
|
329
360
|
GitHub, listed first in magus. Every change prints its plan and asks first.
|
|
330
|
-
|
|
361
|
+
edit Open the company marketplace screens
|
|
331
362
|
market list Company marketplaces on this machine, with their plugins
|
|
332
363
|
market create Create one on GitHub; market connect use an existing one
|
|
333
364
|
market remove <m> Remove one from this machine (the repository stays)
|
|
334
|
-
market plugin create|delete <p@m>
|
|
365
|
+
market plugin create|delete <p@m>; market plugin add <path>
|
|
335
366
|
market skill add|edit|remove <skill>
|
|
336
367
|
market status How your local copies of company skills stand
|
|
337
368
|
market publish <skill> Send a local skill's edits back to its plugin
|
|
369
|
+
market publish --plugin <p@m> Every edited skill of a plugin, one version
|
|
338
370
|
market --help Every market command, its flags and exit codes
|
|
339
371
|
|
|
340
372
|
Other commands:
|
package/src/data/marketplaces.ts
CHANGED
package/src/main.tsx
CHANGED
|
@@ -97,7 +97,7 @@ async function main(): Promise<void> {
|
|
|
97
97
|
if (outcome.app === "market") {
|
|
98
98
|
if (!isInteractive) {
|
|
99
99
|
console.error(
|
|
100
|
-
"magus
|
|
100
|
+
"magus edit opens an interactive screen, which needs a terminal. `magus market --help` lists the commands that run without one.",
|
|
101
101
|
);
|
|
102
102
|
process.exit(2);
|
|
103
103
|
}
|
package/src/market-tui.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The interactive screens behind
|
|
2
|
+
* The interactive screens behind `magus edit`: the company
|
|
3
3
|
* marketplaces on this machine, and the plugins and skills of one of them.
|
|
4
4
|
*
|
|
5
5
|
* Imported dynamically from `main.tsx`, like `tui.tsx`, so no other command
|
|
@@ -55,6 +55,8 @@ export interface LocalMarketplace {
|
|
|
55
55
|
* and the name and source are on this machine's trust list.
|
|
56
56
|
*/
|
|
57
57
|
company?: boolean;
|
|
58
|
+
/** `owner.name` from the manifest, trimmed; absent when empty or missing. */
|
|
59
|
+
ownerName?: string;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
/** Claude Code's marketplace clones, resolved per call. */
|
|
@@ -322,6 +324,11 @@ async function scanSingleMarketplace(
|
|
|
322
324
|
? manifest.metadata.pluginRoot
|
|
323
325
|
: undefined;
|
|
324
326
|
|
|
327
|
+
const ownerName: string | undefined =
|
|
328
|
+
typeof manifest.owner?.name === "string" && manifest.owner.name.trim()
|
|
329
|
+
? manifest.owner.name.trim()
|
|
330
|
+
: undefined;
|
|
331
|
+
|
|
325
332
|
const plugins: LocalMarketplacePlugin[] = [];
|
|
326
333
|
|
|
327
334
|
if (manifest.plugins && Array.isArray(manifest.plugins)) {
|
|
@@ -398,6 +405,7 @@ async function scanSingleMarketplace(
|
|
|
398
405
|
company:
|
|
399
406
|
readMarker(manifest) &&
|
|
400
407
|
isTrustedIn(trusted, marketplaceName, remoteSource),
|
|
408
|
+
...(ownerName ? { ownerName } : {}),
|
|
401
409
|
};
|
|
402
410
|
} catch {
|
|
403
411
|
return null;
|
|
@@ -140,8 +140,8 @@ export interface ChangeSpec {
|
|
|
140
140
|
/** Names a pull-request branch. */
|
|
141
141
|
slug: string;
|
|
142
142
|
prBody: string;
|
|
143
|
-
/**
|
|
144
|
-
verify?: { dir: string; files: readonly SkillFile[] }
|
|
143
|
+
/** Directories whose committed files must equal these, each. */
|
|
144
|
+
verify?: ReadonlyArray<{ dir: string; files: readonly SkillFile[] }>;
|
|
145
145
|
/** Set when the commit is made, for later steps. */
|
|
146
146
|
state: { commit?: string; pushed?: boolean };
|
|
147
147
|
}
|
|
@@ -186,15 +186,15 @@ export function changeSteps(spec: ChangeSpec): MarketStep[] {
|
|
|
186
186
|
}
|
|
187
187
|
const made = await gitCommit(dir, spec.message);
|
|
188
188
|
if (!made.ok) throw gitError(made, spec.label);
|
|
189
|
-
|
|
190
|
-
const tree = await lsTree(dir, made.value,
|
|
189
|
+
for (const check of spec.verify ?? []) {
|
|
190
|
+
const tree = await lsTree(dir, made.value, check.dir);
|
|
191
191
|
if (!tree.ok) throw gitError(tree, spec.label);
|
|
192
|
-
const prefix = `${
|
|
192
|
+
const prefix = `${check.dir}/`;
|
|
193
193
|
const committed = tree.value.map((e) => ({
|
|
194
194
|
path: e.path.slice(prefix.length),
|
|
195
195
|
oid: e.oid,
|
|
196
196
|
}));
|
|
197
|
-
if (!sameFiles(committed,
|
|
197
|
+
if (!sameFiles(committed, check.files)) {
|
|
198
198
|
throw new MarketError(
|
|
199
199
|
"other",
|
|
200
200
|
"the commit did not hold exactly the planned files — nothing was pushed",
|