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,664 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `magus market <verb>` — the non-interactive face of company marketplaces.
|
|
3
|
+
*
|
|
4
|
+
* Parses the verb and its flags, asks the planner (read-only), and handles
|
|
5
|
+
* consent: a TTY is asked `[Y/n]`; anything else writes nothing and exits 3
|
|
6
|
+
* with the plan and its planId, which `--yes=<planId>` then applies — and
|
|
7
|
+
* only that plan. Under `--json` one MarketResult is printed, on the last
|
|
8
|
+
* line of stdout, and nothing is asked.
|
|
9
|
+
*
|
|
10
|
+
* Bare `magus market` prints the help; the interactive screen is `magus
|
|
11
|
+
* edit`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
planPluginAdd,
|
|
16
|
+
planPluginCreate,
|
|
17
|
+
planPluginDelete,
|
|
18
|
+
} from "../services/market-ops-plugin.js";
|
|
19
|
+
import {
|
|
20
|
+
marketStatus,
|
|
21
|
+
planPluginPublish,
|
|
22
|
+
planPublish,
|
|
23
|
+
provisionalPublish,
|
|
24
|
+
} from "../services/market-ops-publish.js";
|
|
25
|
+
import {
|
|
26
|
+
planConnect,
|
|
27
|
+
planCreate,
|
|
28
|
+
planRemove,
|
|
29
|
+
} from "../services/market-ops-repo.js";
|
|
30
|
+
import {
|
|
31
|
+
planSkillAdd,
|
|
32
|
+
planSkillEdit,
|
|
33
|
+
planSkillRemove,
|
|
34
|
+
} from "../services/market-ops-skill.js";
|
|
35
|
+
import {
|
|
36
|
+
applyMarketPlan,
|
|
37
|
+
errorResult,
|
|
38
|
+
exitCodeFor,
|
|
39
|
+
marketFetch,
|
|
40
|
+
marketList,
|
|
41
|
+
needsConsent,
|
|
42
|
+
} from "../services/market-ops.js";
|
|
43
|
+
import {
|
|
44
|
+
MarketError,
|
|
45
|
+
type MarketResult,
|
|
46
|
+
type PlanAnswer,
|
|
47
|
+
} from "../services/market-types.js";
|
|
48
|
+
import {
|
|
49
|
+
applyQuestion,
|
|
50
|
+
applyingLines,
|
|
51
|
+
fetchQuestion,
|
|
52
|
+
listLines,
|
|
53
|
+
marketHelp,
|
|
54
|
+
planOf,
|
|
55
|
+
resultLines,
|
|
56
|
+
secondQuestion,
|
|
57
|
+
statusLines,
|
|
58
|
+
stepLine,
|
|
59
|
+
} from "./market-view.js";
|
|
60
|
+
import { askYesNo, promptValue } from "./prompt.js";
|
|
61
|
+
|
|
62
|
+
/** Flags that take a value, and flags that stand alone. */
|
|
63
|
+
const VALUE_FLAGS = new Set([
|
|
64
|
+
"--name",
|
|
65
|
+
"--company",
|
|
66
|
+
"--description",
|
|
67
|
+
"--plugin",
|
|
68
|
+
"--from",
|
|
69
|
+
"--to",
|
|
70
|
+
]);
|
|
71
|
+
const BOOL_FLAGS = new Set([
|
|
72
|
+
"--private",
|
|
73
|
+
"--public",
|
|
74
|
+
"--keep-local",
|
|
75
|
+
"--create-plugin",
|
|
76
|
+
"--force",
|
|
77
|
+
"--dry-run",
|
|
78
|
+
"--json",
|
|
79
|
+
"--yes",
|
|
80
|
+
"-y",
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
interface Parsed {
|
|
84
|
+
positional: string[];
|
|
85
|
+
flags: Map<string, string | true>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
class UsageError extends Error {}
|
|
89
|
+
|
|
90
|
+
function parseArgs(argv: string[]): Parsed {
|
|
91
|
+
const positional: string[] = [];
|
|
92
|
+
const flags = new Map<string, string | true>();
|
|
93
|
+
for (let i = 0; i < argv.length; i++) {
|
|
94
|
+
const token = argv[i] as string;
|
|
95
|
+
if (!token.startsWith("-")) {
|
|
96
|
+
positional.push(token);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const eq = token.indexOf("=");
|
|
100
|
+
const name = eq > 0 ? token.slice(0, eq) : token;
|
|
101
|
+
if (name === "--yes" && eq > 0) {
|
|
102
|
+
flags.set("--yes", token.slice(eq + 1));
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (VALUE_FLAGS.has(name)) {
|
|
106
|
+
const value = eq > 0 ? token.slice(eq + 1) : argv[++i];
|
|
107
|
+
if (value === undefined || value === "") {
|
|
108
|
+
throw new UsageError(`${name} needs a value`);
|
|
109
|
+
}
|
|
110
|
+
flags.set(name, value);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (BOOL_FLAGS.has(name) && eq < 0) {
|
|
114
|
+
flags.set(name === "-y" ? "--yes" : name, true);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
throw new UsageError(`unknown option ${token}`);
|
|
118
|
+
}
|
|
119
|
+
return { positional, flags };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Refuse flags a verb does not take, and a wrong number of arguments. */
|
|
123
|
+
function expect(
|
|
124
|
+
p: Parsed,
|
|
125
|
+
allowed: readonly string[],
|
|
126
|
+
args: { min: number; max: number; usage: string },
|
|
127
|
+
): void {
|
|
128
|
+
for (const flag of p.flags.keys()) {
|
|
129
|
+
if (flag !== "--json" && flag !== "--yes" && !allowed.includes(flag)) {
|
|
130
|
+
throw new UsageError(
|
|
131
|
+
`${flag} does not apply here — usage: ${args.usage}`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (p.positional.length < args.min || p.positional.length > args.max) {
|
|
136
|
+
throw new UsageError(`usage: ${args.usage}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function str(p: Parsed, flag: string): string | undefined {
|
|
141
|
+
const v = p.flags.get(flag);
|
|
142
|
+
return typeof v === "string" ? v : undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function choice<T extends string>(
|
|
146
|
+
p: Parsed,
|
|
147
|
+
flag: string,
|
|
148
|
+
values: readonly T[],
|
|
149
|
+
fallback: T,
|
|
150
|
+
): T {
|
|
151
|
+
const v = str(p, flag);
|
|
152
|
+
if (v === undefined) return fallback;
|
|
153
|
+
if (!(values as readonly string[]).includes(v)) {
|
|
154
|
+
throw new UsageError(`${flag} must be ${values.join(" or ")}`);
|
|
155
|
+
}
|
|
156
|
+
return v as T;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
interface Io {
|
|
160
|
+
json: boolean;
|
|
161
|
+
interactive: boolean;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function emit(result: MarketResult, io: Io): void {
|
|
165
|
+
if (io.json) {
|
|
166
|
+
console.log(JSON.stringify(result));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const lines =
|
|
170
|
+
result.outcome !== "ok"
|
|
171
|
+
? resultLines(result)
|
|
172
|
+
: result.command === "market list"
|
|
173
|
+
? listLines(result)
|
|
174
|
+
: result.command === "market status"
|
|
175
|
+
? statusLines(result)
|
|
176
|
+
: resultLines(result);
|
|
177
|
+
for (const line of lines) {
|
|
178
|
+
console.log(line);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Plan, ask (or not), apply, report. Returns the exit code.
|
|
184
|
+
* `yes` is true for bare `--yes`, the planId for `--yes=<planId>`.
|
|
185
|
+
*/
|
|
186
|
+
async function runWrite(
|
|
187
|
+
plan: () => Promise<PlanAnswer>,
|
|
188
|
+
io: Io,
|
|
189
|
+
yes: string | true | undefined,
|
|
190
|
+
cwd: string,
|
|
191
|
+
): Promise<number> {
|
|
192
|
+
let fetchedOnce = false;
|
|
193
|
+
for (;;) {
|
|
194
|
+
const answer = await plan();
|
|
195
|
+
if (answer.kind === "error") {
|
|
196
|
+
if (
|
|
197
|
+
answer.error.code === "stale" &&
|
|
198
|
+
io.interactive &&
|
|
199
|
+
yes === undefined &&
|
|
200
|
+
!fetchedOnce
|
|
201
|
+
) {
|
|
202
|
+
console.log(warnBlock(answer.error.message));
|
|
203
|
+
if (await askYesNo(fetchQuestion())) {
|
|
204
|
+
fetchedOnce = true;
|
|
205
|
+
const fetched = await marketFetch(
|
|
206
|
+
answer.error.marketplaces,
|
|
207
|
+
(s, label) => console.log(stepLine(s, label)),
|
|
208
|
+
);
|
|
209
|
+
if (fetched.outcome !== "ok") {
|
|
210
|
+
emit(fetched, io);
|
|
211
|
+
return exitCodeFor(fetched);
|
|
212
|
+
}
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Consent was given up front, and an apply syncs magus's working
|
|
217
|
+
// clones before it plans again anyway. Fetching first is that same
|
|
218
|
+
// write, so the approved plan is then compared with GitHub's state
|
|
219
|
+
// rather than refused for this machine's.
|
|
220
|
+
if (answer.error.code === "stale" && yes !== undefined && !fetchedOnce) {
|
|
221
|
+
fetchedOnce = true;
|
|
222
|
+
const fetched = await marketFetch(answer.error.marketplaces);
|
|
223
|
+
if (fetched.outcome === "ok") continue;
|
|
224
|
+
}
|
|
225
|
+
const result = errorResult(answer.command, answer.error);
|
|
226
|
+
emit(result, io);
|
|
227
|
+
return answer.error.exit ?? exitCodeFor(result);
|
|
228
|
+
}
|
|
229
|
+
if (answer.kind === "result") {
|
|
230
|
+
emit(answer.result, io);
|
|
231
|
+
return exitCodeFor(answer.result);
|
|
232
|
+
}
|
|
233
|
+
const p = answer.plan;
|
|
234
|
+
let approved: string | null;
|
|
235
|
+
if (yes !== undefined) {
|
|
236
|
+
approved = yes === true ? null : yes;
|
|
237
|
+
if (!io.json) for (const line of applyingLines(p)) console.log(line);
|
|
238
|
+
} else if (io.interactive) {
|
|
239
|
+
for (const line of planOf(p)) console.log(line);
|
|
240
|
+
if (
|
|
241
|
+
!(await askYesNo(applyQuestion())) ||
|
|
242
|
+
(p.secondConfirm !== undefined &&
|
|
243
|
+
!/^(y|yes)$/i.test(
|
|
244
|
+
await promptValue(secondQuestion(p.secondConfirm)),
|
|
245
|
+
))
|
|
246
|
+
) {
|
|
247
|
+
console.log("\n Nothing was changed.\n");
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
approved = p.planId;
|
|
251
|
+
} else {
|
|
252
|
+
const result = needsConsent(p);
|
|
253
|
+
emit(result, io);
|
|
254
|
+
return exitCodeFor(result);
|
|
255
|
+
}
|
|
256
|
+
const result = await applyMarketPlan(p, {
|
|
257
|
+
approved,
|
|
258
|
+
cwd,
|
|
259
|
+
onStep: io.json
|
|
260
|
+
? undefined
|
|
261
|
+
: (s, label) => console.log(stepLine(s, label)),
|
|
262
|
+
});
|
|
263
|
+
if (
|
|
264
|
+
result.error?.code === "plan-changed" &&
|
|
265
|
+
io.interactive &&
|
|
266
|
+
yes === undefined
|
|
267
|
+
) {
|
|
268
|
+
console.log(warnBlock(result.message));
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
emit(result, io);
|
|
272
|
+
return exitCodeFor(result);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function warnBlock(text: string): string {
|
|
277
|
+
return `\n ⚠ ${text}`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* `publish --dry-run`: the plan, or the answer a publish would give, and
|
|
282
|
+
* nothing written. On state older than GitHub's, a provisional answer with
|
|
283
|
+
* no compare (`provisional`), still exit 0; without one, that error.
|
|
284
|
+
*/
|
|
285
|
+
async function dryRunPublish(
|
|
286
|
+
plan: () => Promise<PlanAnswer>,
|
|
287
|
+
io: Io,
|
|
288
|
+
provisional?: (error: MarketError) => MarketResult,
|
|
289
|
+
): Promise<number> {
|
|
290
|
+
const answer = await plan();
|
|
291
|
+
if (answer.kind === "plan") {
|
|
292
|
+
const p = answer.plan;
|
|
293
|
+
const result: MarketResult = {
|
|
294
|
+
command: p.command,
|
|
295
|
+
outcome: "ok",
|
|
296
|
+
message: "Dry run — nothing was changed.",
|
|
297
|
+
planId: p.planId,
|
|
298
|
+
...p.summary,
|
|
299
|
+
plan: p.lines,
|
|
300
|
+
steps: [],
|
|
301
|
+
};
|
|
302
|
+
if (io.json) console.log(JSON.stringify(result));
|
|
303
|
+
else {
|
|
304
|
+
for (const line of planOf(p)) console.log(line);
|
|
305
|
+
console.log(`\n ${result.message}\n`);
|
|
306
|
+
}
|
|
307
|
+
return 0;
|
|
308
|
+
}
|
|
309
|
+
if (answer.kind === "result") {
|
|
310
|
+
emit(answer.result, io);
|
|
311
|
+
return exitCodeFor(answer.result);
|
|
312
|
+
}
|
|
313
|
+
if (
|
|
314
|
+
provisional &&
|
|
315
|
+
(answer.error.code === "stale" || answer.error.code === "no-network")
|
|
316
|
+
) {
|
|
317
|
+
emit(provisional(answer.error), io);
|
|
318
|
+
return 0;
|
|
319
|
+
}
|
|
320
|
+
const result = errorResult(answer.command, answer.error);
|
|
321
|
+
emit(result, io);
|
|
322
|
+
return answer.error.exit ?? exitCodeFor(result);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Run `magus market <argv>`. Returns the exit code. */
|
|
326
|
+
export async function runMarketCommand(
|
|
327
|
+
argv: string[],
|
|
328
|
+
version: string,
|
|
329
|
+
cwd: string = process.cwd(),
|
|
330
|
+
): Promise<number> {
|
|
331
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
332
|
+
console.log(marketHelp());
|
|
333
|
+
return 0;
|
|
334
|
+
}
|
|
335
|
+
if (argv.includes("--version") || argv.includes("-v")) {
|
|
336
|
+
console.log(`magus v${version}`);
|
|
337
|
+
return 0;
|
|
338
|
+
}
|
|
339
|
+
if (argv.length === 0) {
|
|
340
|
+
console.log(marketHelp());
|
|
341
|
+
console.log("Open the screen with `magus edit`.");
|
|
342
|
+
return 0;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const json = argv.includes("--json");
|
|
346
|
+
const io: Io = {
|
|
347
|
+
json,
|
|
348
|
+
interactive:
|
|
349
|
+
!json && process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
350
|
+
};
|
|
351
|
+
const [verb = "", ...rest] = argv;
|
|
352
|
+
try {
|
|
353
|
+
const p = parseArgs(rest);
|
|
354
|
+
const yes = p.flags.get("--yes");
|
|
355
|
+
switch (verb) {
|
|
356
|
+
case "list": {
|
|
357
|
+
expect(p, [], { min: 0, max: 0, usage: "magus market list [--json]" });
|
|
358
|
+
const result = await marketList(cwd);
|
|
359
|
+
emit(result, io);
|
|
360
|
+
return exitCodeFor(result);
|
|
361
|
+
}
|
|
362
|
+
case "fetch": {
|
|
363
|
+
expect(p, [], {
|
|
364
|
+
min: 0,
|
|
365
|
+
max: 1,
|
|
366
|
+
usage: "magus market fetch [<m>] [--json]",
|
|
367
|
+
});
|
|
368
|
+
const result = await marketFetch(
|
|
369
|
+
p.positional[0],
|
|
370
|
+
io.json ? undefined : (s, label) => console.log(stepLine(s, label)),
|
|
371
|
+
);
|
|
372
|
+
emit(result, io);
|
|
373
|
+
return exitCodeFor(result);
|
|
374
|
+
}
|
|
375
|
+
case "create": {
|
|
376
|
+
const usage =
|
|
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
|
+
);
|
|
387
|
+
const priv = p.flags.has("--private");
|
|
388
|
+
const pub = p.flags.has("--public");
|
|
389
|
+
if (priv === pub) {
|
|
390
|
+
throw new UsageError(
|
|
391
|
+
`choose one of --private or --public — usage: ${usage}`,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
return await runWrite(
|
|
395
|
+
() =>
|
|
396
|
+
planCreate({
|
|
397
|
+
repo: p.positional[0] as string,
|
|
398
|
+
visibility: priv ? "private" : "public",
|
|
399
|
+
name: str(p, "--name"),
|
|
400
|
+
company: str(p, "--company"),
|
|
401
|
+
description: str(p, "--description"),
|
|
402
|
+
}),
|
|
403
|
+
io,
|
|
404
|
+
yes,
|
|
405
|
+
cwd,
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
case "connect": {
|
|
409
|
+
expect(p, ["--name", "--company"], {
|
|
410
|
+
min: 1,
|
|
411
|
+
max: 1,
|
|
412
|
+
usage:
|
|
413
|
+
"magus market connect <owner/repo|git-url|path> [--name <m>] [--company <name>]",
|
|
414
|
+
});
|
|
415
|
+
return await runWrite(
|
|
416
|
+
() =>
|
|
417
|
+
planConnect(
|
|
418
|
+
{
|
|
419
|
+
source: p.positional[0] as string,
|
|
420
|
+
name: str(p, "--name"),
|
|
421
|
+
company: str(p, "--company"),
|
|
422
|
+
},
|
|
423
|
+
cwd,
|
|
424
|
+
),
|
|
425
|
+
io,
|
|
426
|
+
yes,
|
|
427
|
+
cwd,
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
case "remove": {
|
|
431
|
+
expect(p, [], { min: 1, max: 1, usage: "magus market remove <m>" });
|
|
432
|
+
return await runWrite(
|
|
433
|
+
() => planRemove(p.positional[0] as string, cwd),
|
|
434
|
+
io,
|
|
435
|
+
yes,
|
|
436
|
+
cwd,
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
case "plugin": {
|
|
440
|
+
const [sub, ref, ...extra] = p.positional;
|
|
441
|
+
const sp = { ...p, positional: ref ? [ref, ...extra] : [] };
|
|
442
|
+
if (sub === "create") {
|
|
443
|
+
expect(sp, ["--description"], {
|
|
444
|
+
min: 1,
|
|
445
|
+
max: 1,
|
|
446
|
+
usage: "magus market plugin create <p@m> [--description <t>]",
|
|
447
|
+
});
|
|
448
|
+
return await runWrite(
|
|
449
|
+
() => planPluginCreate(ref as string, str(p, "--description")),
|
|
450
|
+
io,
|
|
451
|
+
yes,
|
|
452
|
+
cwd,
|
|
453
|
+
);
|
|
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
|
+
}
|
|
476
|
+
if (sub === "delete") {
|
|
477
|
+
expect(sp, [], {
|
|
478
|
+
min: 1,
|
|
479
|
+
max: 1,
|
|
480
|
+
usage: "magus market plugin delete <p@m>",
|
|
481
|
+
});
|
|
482
|
+
return await runWrite(
|
|
483
|
+
() => planPluginDelete(ref as string, cwd),
|
|
484
|
+
io,
|
|
485
|
+
yes,
|
|
486
|
+
cwd,
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
throw new UsageError(
|
|
490
|
+
"usage: magus market plugin create|delete <p@m>, or magus market plugin add <path>",
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
case "skill": {
|
|
494
|
+
const [sub, skill, ...extra] = p.positional;
|
|
495
|
+
const sp = { ...p, positional: skill ? [skill, ...extra] : [] };
|
|
496
|
+
if (sub === "add") {
|
|
497
|
+
const usage =
|
|
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
|
+
);
|
|
514
|
+
const plugin = str(p, "--plugin");
|
|
515
|
+
if (!plugin)
|
|
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
|
+
}
|
|
523
|
+
const from = choice(
|
|
524
|
+
p,
|
|
525
|
+
"--from",
|
|
526
|
+
["project", "global"] as const,
|
|
527
|
+
"project",
|
|
528
|
+
);
|
|
529
|
+
return await runWrite(
|
|
530
|
+
() =>
|
|
531
|
+
planSkillAdd(
|
|
532
|
+
{
|
|
533
|
+
skill: skill as string,
|
|
534
|
+
plugin,
|
|
535
|
+
from,
|
|
536
|
+
keepLocal: p.flags.has("--keep-local"),
|
|
537
|
+
...(createPlugin
|
|
538
|
+
? {
|
|
539
|
+
createPlugin: true,
|
|
540
|
+
description: str(p, "--description"),
|
|
541
|
+
}
|
|
542
|
+
: {}),
|
|
543
|
+
},
|
|
544
|
+
cwd,
|
|
545
|
+
),
|
|
546
|
+
io,
|
|
547
|
+
yes,
|
|
548
|
+
cwd,
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
if (sub === "edit") {
|
|
552
|
+
expect(sp, ["--plugin", "--to"], {
|
|
553
|
+
min: 1,
|
|
554
|
+
max: 1,
|
|
555
|
+
usage:
|
|
556
|
+
"magus market skill edit <skill> [--plugin <p@m>] [--to project|global]",
|
|
557
|
+
});
|
|
558
|
+
const to = choice(
|
|
559
|
+
p,
|
|
560
|
+
"--to",
|
|
561
|
+
["project", "global"] as const,
|
|
562
|
+
"project",
|
|
563
|
+
);
|
|
564
|
+
return await runWrite(
|
|
565
|
+
() =>
|
|
566
|
+
planSkillEdit(
|
|
567
|
+
{ skill: skill as string, plugin: str(p, "--plugin"), to },
|
|
568
|
+
cwd,
|
|
569
|
+
),
|
|
570
|
+
io,
|
|
571
|
+
yes,
|
|
572
|
+
cwd,
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
if (sub === "remove") {
|
|
576
|
+
const usage = "magus market skill remove <skill> --plugin <p@m>";
|
|
577
|
+
expect(sp, ["--plugin"], { min: 1, max: 1, usage });
|
|
578
|
+
const plugin = str(p, "--plugin");
|
|
579
|
+
if (!plugin)
|
|
580
|
+
throw new UsageError(`--plugin is required — usage: ${usage}`);
|
|
581
|
+
return await runWrite(
|
|
582
|
+
() => planSkillRemove({ skill: skill as string, plugin }, cwd),
|
|
583
|
+
io,
|
|
584
|
+
yes,
|
|
585
|
+
cwd,
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
throw new UsageError(
|
|
589
|
+
"usage: magus market skill add|edit|remove <skill>",
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
case "status": {
|
|
593
|
+
expect(p, [], {
|
|
594
|
+
min: 0,
|
|
595
|
+
max: 1,
|
|
596
|
+
usage: "magus market status [<skill>] [--json]",
|
|
597
|
+
});
|
|
598
|
+
const result = await marketStatus(p.positional[0], cwd);
|
|
599
|
+
emit(result, io);
|
|
600
|
+
return exitCodeFor(result);
|
|
601
|
+
}
|
|
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]";
|
|
605
|
+
expect(
|
|
606
|
+
p,
|
|
607
|
+
["--plugin", "--from", "--keep-local", "--force", "--dry-run"],
|
|
608
|
+
{ min: 0, max: 1, usage },
|
|
609
|
+
);
|
|
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,
|
|
632
|
+
plugin: str(p, "--plugin"),
|
|
633
|
+
from: choice(p, "--from", ["project", "global"] as const, "project"),
|
|
634
|
+
keepLocal,
|
|
635
|
+
force: p.flags.has("--force"),
|
|
636
|
+
};
|
|
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);
|
|
644
|
+
}
|
|
645
|
+
default:
|
|
646
|
+
throw new UsageError(
|
|
647
|
+
`unknown command "${verb}" — \`magus market --help\` lists the commands`,
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
} catch (error) {
|
|
651
|
+
if (error instanceof UsageError) {
|
|
652
|
+
const result = errorResult(
|
|
653
|
+
`market ${verb || "?"}`,
|
|
654
|
+
new MarketError("other", error.message, "`magus market --help`", 2),
|
|
655
|
+
);
|
|
656
|
+
if (io.json) console.log(JSON.stringify(result));
|
|
657
|
+
else console.error(`magus market: ${error.message}`);
|
|
658
|
+
return 2;
|
|
659
|
+
}
|
|
660
|
+
const result = errorResult(`market ${verb}`, error);
|
|
661
|
+
emit(result, io);
|
|
662
|
+
return exitCodeFor(result);
|
|
663
|
+
}
|
|
664
|
+
}
|