@hachej/boring-ui-plugin-cli 0.1.33 → 0.1.34

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/dist/bin.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runBoringUiPluginCli
4
- } from "./chunk-DH4PSVGY.js";
4
+ } from "./chunk-HY4ZELTX.js";
5
+ import "./chunk-6FD653KO.js";
5
6
 
6
7
  // src/bin.ts
7
8
  runBoringUiPluginCli().catch((error) => {
@@ -0,0 +1,605 @@
1
+ // src/server/pluginSources.ts
2
+ import { spawnSync } from "child_process";
3
+ import {
4
+ existsSync,
5
+ mkdtempSync,
6
+ mkdirSync,
7
+ readFileSync,
8
+ realpathSync,
9
+ renameSync,
10
+ rmSync,
11
+ writeFileSync
12
+ } from "fs";
13
+ import { homedir } from "os";
14
+ import { basename, dirname, isAbsolute, join, relative, resolve } from "path";
15
+
16
+ // src/manifest.ts
17
+ var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
18
+ var PLUGIN_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
19
+ function isValidBoringPluginId(id) {
20
+ return typeof id === "string" && id.length > 0 && PLUGIN_ID_RE.test(id);
21
+ }
22
+ function isSafePluginRelativePath(value) {
23
+ return typeof value === "string" && value.length > 0 && value !== "." && !value.includes("\0") && !value.includes("\\") && !value.startsWith("/") && !value.startsWith("//") && !/^[A-Za-z]:[\\/]/.test(value) && !value.split("/").includes("..");
24
+ }
25
+ function issue(code, field, message) {
26
+ return { code, field, message };
27
+ }
28
+ function isRecord(value) {
29
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
30
+ }
31
+ function validateStringArray(issues, value, field, pathLike) {
32
+ if (value === void 0) return;
33
+ if (!Array.isArray(value)) {
34
+ issues.push(issue("INVALID_FIELD", field, `${field} must be an array`));
35
+ return;
36
+ }
37
+ value.forEach((entry, index) => {
38
+ const itemField = `${field}[${index}]`;
39
+ if (typeof entry !== "string" || entry.length === 0) {
40
+ issues.push(issue("INVALID_FIELD", itemField, `${itemField} must be a non-empty string`));
41
+ return;
42
+ }
43
+ if (pathLike && !isSafePluginRelativePath(entry)) {
44
+ issues.push(issue("INVALID_PATH", itemField, `${itemField} must be a safe relative path`));
45
+ }
46
+ });
47
+ }
48
+ var REMOVED_BORING_UI_FIELDS = ["outputs", "panels", "commands", "leftTabs", "surfaceResolvers", "providers", "bindings", "catalogs"];
49
+ function validateBoringField(issues, boring) {
50
+ if (boring === void 0) return void 0;
51
+ if (!isRecord(boring)) {
52
+ issues.push(issue("INVALID_FIELD", "boring", "boring must be an object when provided"));
53
+ return void 0;
54
+ }
55
+ for (const field of REMOVED_BORING_UI_FIELDS) {
56
+ if (boring[field] !== void 0) {
57
+ issues.push(issue(
58
+ "INVALID_FIELD",
59
+ `boring.${field}`,
60
+ `boring.${field} is not supported; declare front contributions in boring.front via definePlugin({ ... })`
61
+ ));
62
+ }
63
+ }
64
+ if (boring.id !== void 0 && (typeof boring.id !== "string" || !isValidBoringPluginId(boring.id))) {
65
+ issues.push(issue("INVALID_ID", "boring.id", "boring.id must start with a letter or number and use only letters, numbers, dot, underscore, colon, or dash"));
66
+ }
67
+ const front = boring.front;
68
+ if (front !== void 0 && (typeof front !== "string" || !isSafePluginRelativePath(front))) {
69
+ issues.push(issue("INVALID_PATH", "boring.front", "boring.front must be a safe relative path"));
70
+ }
71
+ const server = boring.server;
72
+ if (server !== void 0 && server !== false && (typeof server !== "string" || !isSafePluginRelativePath(server))) {
73
+ issues.push(issue("INVALID_PATH", "boring.server", "boring.server must be a safe relative path or false"));
74
+ }
75
+ if (boring.label !== void 0 && typeof boring.label !== "string") {
76
+ issues.push(issue("INVALID_FIELD", "boring.label", "boring.label must be a string when provided"));
77
+ }
78
+ return {
79
+ ...typeof boring.id === "string" ? { id: boring.id } : {},
80
+ ...typeof boring.front === "string" ? { front: boring.front } : {},
81
+ ...typeof boring.server === "string" || boring.server === false ? { server: boring.server } : {},
82
+ ...typeof boring.label === "string" ? { label: boring.label } : {}
83
+ };
84
+ }
85
+ var REMOTE_PI_PACKAGE_PREFIXES = ["npm:", "git:", "github:", "http:", "https:", "ssh:"];
86
+ function isRemotePiPackageSource(value) {
87
+ return REMOTE_PI_PACKAGE_PREFIXES.some((prefix) => value.startsWith(prefix));
88
+ }
89
+ function isSafePiPackageSource(value) {
90
+ if (value.length === 0) return false;
91
+ if (isRemotePiPackageSource(value)) return true;
92
+ const path = value.startsWith("file:") ? value.slice("file:".length) : value;
93
+ if (path === "." || path === "./") return true;
94
+ const normalized = path.startsWith("./") ? path.slice(2) : path;
95
+ return isSafePluginRelativePath(normalized);
96
+ }
97
+ function validatePiPackages(issues, value) {
98
+ if (value === void 0) return;
99
+ if (!Array.isArray(value)) {
100
+ issues.push(issue("INVALID_FIELD", "pi.packages", "pi.packages must be an array when provided"));
101
+ return;
102
+ }
103
+ value.forEach((entry, index) => {
104
+ const field = `pi.packages[${index}]`;
105
+ if (typeof entry === "string") {
106
+ if (!isSafePiPackageSource(entry)) {
107
+ issues.push(issue("INVALID_PATH", field, `${field} must be a safe package source`));
108
+ }
109
+ return;
110
+ }
111
+ if (!isRecord(entry)) {
112
+ issues.push(issue("INVALID_FIELD", field, `${field} must be a string or package source object`));
113
+ return;
114
+ }
115
+ if (typeof entry.source !== "string" || entry.source.length === 0) {
116
+ issues.push(issue("INVALID_FIELD", `${field}.source`, `${field}.source must be a non-empty string`));
117
+ } else if (!isSafePiPackageSource(entry.source)) {
118
+ issues.push(issue("INVALID_PATH", `${field}.source`, `${field}.source must be a safe package source`));
119
+ }
120
+ });
121
+ }
122
+ function validatePiField(issues, pi) {
123
+ if (pi === void 0) return void 0;
124
+ if (!isRecord(pi)) {
125
+ issues.push(issue("INVALID_FIELD", "pi", "pi must be an object when provided"));
126
+ return void 0;
127
+ }
128
+ validateStringArray(issues, pi.extensions, "pi.extensions", true);
129
+ validateStringArray(issues, pi.skills, "pi.skills", true);
130
+ validatePiPackages(issues, pi.packages);
131
+ if (pi.systemPrompt !== void 0 && typeof pi.systemPrompt !== "string") {
132
+ issues.push(issue("INVALID_FIELD", "pi.systemPrompt", "pi.systemPrompt must be a string when provided"));
133
+ }
134
+ return pi;
135
+ }
136
+ function validateBoringPluginManifest(raw) {
137
+ const issues = [];
138
+ if (!isRecord(raw)) {
139
+ return {
140
+ valid: false,
141
+ issues: [issue("INVALID_FIELD", "<root>", "package.json manifest must be an object")]
142
+ };
143
+ }
144
+ if (raw.name !== void 0 && typeof raw.name !== "string") {
145
+ issues.push(issue("INVALID_FIELD", "name", "name must be a string when provided"));
146
+ }
147
+ if (raw.version !== void 0 && typeof raw.version !== "string") {
148
+ issues.push(issue("INVALID_VERSION", "version", "version must be a string when provided"));
149
+ } else if (typeof raw.version === "string" && raw.version.length > 0 && !SEMVER_RE.test(raw.version)) {
150
+ issues.push(issue("INVALID_VERSION", "version", "version must be a valid semver string"));
151
+ }
152
+ const boring = validateBoringField(issues, raw.boring);
153
+ const pi = validatePiField(issues, raw.pi);
154
+ if (!boring && !pi) {
155
+ issues.push(issue("MISSING_REQUIRED_FIELD", "boring|pi", "package.json must include boring and/or pi plugin metadata"));
156
+ }
157
+ if (issues.length > 0) return { valid: false, issues };
158
+ return {
159
+ valid: true,
160
+ packageJson: {
161
+ ...typeof raw.name === "string" ? { name: raw.name } : {},
162
+ ...typeof raw.version === "string" ? { version: raw.version } : {},
163
+ ...boring ? { boring } : {},
164
+ ...pi ? { pi } : {}
165
+ }
166
+ };
167
+ }
168
+
169
+ // src/server/pluginSources.ts
170
+ function defaultWorkspaceRoot() {
171
+ return process.env.BORING_AGENT_WORKSPACE_ROOT ?? process.cwd();
172
+ }
173
+ function defaultGlobalRoot() {
174
+ return process.env.BORING_UI_PLUGIN_GLOBAL_ROOT ?? join(homedir(), ".pi", "agent");
175
+ }
176
+ function resolvePluginSourceScopePaths(scope, opts = {}) {
177
+ if (scope === "global") {
178
+ const baseDir2 = resolve(opts.globalRoot ?? defaultGlobalRoot());
179
+ return {
180
+ scope,
181
+ baseDir: baseDir2,
182
+ extensionsDir: join(baseDir2, "extensions"),
183
+ gitDir: join(baseDir2, "git"),
184
+ npmDir: join(baseDir2, "npm"),
185
+ settingsPath: join(baseDir2, "settings.json")
186
+ };
187
+ }
188
+ const workspaceRoot = resolve(opts.workspaceRoot ?? defaultWorkspaceRoot());
189
+ const baseDir = join(workspaceRoot, ".pi");
190
+ return {
191
+ scope,
192
+ workspaceRoot,
193
+ baseDir,
194
+ extensionsDir: join(baseDir, "extensions"),
195
+ gitDir: join(baseDir, "git"),
196
+ npmDir: join(baseDir, "npm"),
197
+ settingsPath: join(baseDir, "settings.json")
198
+ };
199
+ }
200
+ function ensureScopeDirs(paths) {
201
+ mkdirSync(paths.baseDir, { recursive: true });
202
+ mkdirSync(paths.extensionsDir, { recursive: true });
203
+ mkdirSync(paths.gitDir, { recursive: true });
204
+ mkdirSync(paths.npmDir, { recursive: true });
205
+ }
206
+ function readPiSettings(settingsPath) {
207
+ if (!existsSync(settingsPath)) return {};
208
+ let parsed;
209
+ try {
210
+ parsed = JSON.parse(readFileSync(settingsPath, "utf8"));
211
+ } catch (error) {
212
+ const message = error instanceof Error ? error.message : String(error);
213
+ throw new Error(`invalid Pi settings file ${settingsPath}: ${message}`);
214
+ }
215
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
216
+ throw new Error(`invalid Pi settings file ${settingsPath}: expected object`);
217
+ }
218
+ return parsed;
219
+ }
220
+ function writePiSettings(settingsPath, settings) {
221
+ mkdirSync(dirname(settingsPath), { recursive: true });
222
+ writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
223
+ `, "utf8");
224
+ }
225
+ function packageEntries(settings) {
226
+ if (!Array.isArray(settings.packages)) return [];
227
+ return settings.packages.flatMap((entry) => {
228
+ if (typeof entry === "string") return [entry];
229
+ if (entry && typeof entry === "object" && !Array.isArray(entry)) return [entry];
230
+ return [];
231
+ });
232
+ }
233
+ function packageEntrySource(entry) {
234
+ return typeof entry === "string" ? entry : typeof entry.source === "string" ? entry.source : void 0;
235
+ }
236
+ function resolveMaybePath(value) {
237
+ if (value === "~") return homedir();
238
+ if (value.startsWith("~/")) return resolve(join(homedir(), value.slice(2)));
239
+ if (isAbsolute(value) || value.startsWith(".")) return resolve(value);
240
+ return value;
241
+ }
242
+ function resolvePackageSourcePath(settingsDir, source) {
243
+ const path = source.startsWith("file:") ? source.slice("file:".length) : source;
244
+ if (path.startsWith("npm:") || path.startsWith("git:") || path.startsWith("github:") || /^(https?|ssh):\/\//.test(path)) return void 0;
245
+ if (path === "~" || path.startsWith("~/")) return resolveMaybePath(path);
246
+ return isAbsolute(path) ? resolve(path) : resolve(settingsDir, path);
247
+ }
248
+ function pathInside(parent, child) {
249
+ const rel = relative(parent, child);
250
+ return rel === "" || !!rel && !rel.startsWith("..") && !isAbsolute(rel);
251
+ }
252
+ function sourceForLocalPackage(paths, rootDir) {
253
+ if (paths.workspaceRoot && pathInside(paths.workspaceRoot, rootDir)) {
254
+ const rel2 = relative(paths.baseDir, rootDir).split("\\").join("/");
255
+ if (!rel2 || rel2 === ".") return ".";
256
+ return rel2.startsWith(".") ? rel2 : rel2.startsWith("..") ? rel2 : `./${rel2}`;
257
+ }
258
+ const rel = relative(paths.baseDir, rootDir).split("\\").join("/");
259
+ if (!rel || rel === ".") return ".";
260
+ if (!rel.startsWith("..") && !isAbsolute(rel)) return rel.startsWith(".") ? rel : `./${rel}`;
261
+ return rootDir;
262
+ }
263
+ function inferKind(paths, rootDir, source) {
264
+ if (source.startsWith("npm:") || pathInside(paths.npmDir, rootDir)) return "npm";
265
+ if (source.startsWith("git:") || source.startsWith("github:") || /^(https?|ssh):\/\//.test(source) || pathInside(paths.gitDir, rootDir)) return "git";
266
+ return "local";
267
+ }
268
+ function run(command, args, opts = {}) {
269
+ const result = spawnSync(command, args, {
270
+ cwd: opts.cwd,
271
+ encoding: "utf8",
272
+ stdio: ["ignore", "pipe", "pipe"]
273
+ });
274
+ if (result.status === 0) return;
275
+ const stderr = result.stderr?.trim();
276
+ const stdout = result.stdout?.trim();
277
+ const details = stderr || stdout ? `: ${stderr || stdout}` : "";
278
+ throw new Error(`${command} ${args.join(" ")} failed${details}`);
279
+ }
280
+ function runWithStdout(command, args, opts = {}) {
281
+ const result = spawnSync(command, args, {
282
+ cwd: opts.cwd,
283
+ encoding: "utf8",
284
+ stdio: ["ignore", "pipe", "pipe"]
285
+ });
286
+ if (result.status !== 0) {
287
+ const stderr = result.stderr?.trim();
288
+ const stdout = result.stdout?.trim();
289
+ const details = stderr || stdout ? `: ${stderr || stdout}` : "";
290
+ throw new Error(`${command} ${args.join(" ")} failed${details}`);
291
+ }
292
+ return result.stdout;
293
+ }
294
+ function readPackageJson(pluginRoot) {
295
+ const pkgPath = join(pluginRoot, "package.json");
296
+ if (!existsSync(pkgPath)) throw new Error(`package.json missing in plugin source: ${pluginRoot}`);
297
+ let parsed;
298
+ try {
299
+ parsed = JSON.parse(readFileSync(pkgPath, "utf8"));
300
+ } catch (error) {
301
+ const message = error instanceof Error ? error.message : String(error);
302
+ throw new Error(`package.json is not valid JSON in ${pluginRoot}: ${message}`);
303
+ }
304
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
305
+ throw new Error(`package.json must be an object in ${pluginRoot}`);
306
+ }
307
+ return parsed;
308
+ }
309
+ function pluginIdFromPackageJson(pkg, rootDir) {
310
+ const boring = pkg.boring && typeof pkg.boring === "object" && !Array.isArray(pkg.boring) ? pkg.boring : void 0;
311
+ const pi = pkg.pi && typeof pkg.pi === "object" && !Array.isArray(pkg.pi) ? pkg.pi : void 0;
312
+ const explicitId = typeof boring?.id === "string" && boring.id.trim() ? boring.id.trim() : typeof pi?.id === "string" && pi.id.trim() ? pi.id.trim() : void 0;
313
+ if (explicitId) return explicitId;
314
+ const name = typeof pkg.name === "string" && pkg.name.trim() ? pkg.name.trim() : void 0;
315
+ return ((name ?? basename(rootDir)) || "plugin").replace(/^@/, "").replaceAll("/", "-");
316
+ }
317
+ function validateInstallablePluginRoot(pluginRoot) {
318
+ const resolvedRoot = resolve(pluginRoot);
319
+ const pkg = readPackageJson(resolvedRoot);
320
+ const validation = validateBoringPluginManifest(pkg);
321
+ if (!validation.valid) {
322
+ const issues = validation.issues.map((issue2) => `${issue2.field}: ${issue2.message}`).join("; ");
323
+ throw new Error(`invalid Boring plugin manifest in ${resolvedRoot}: ${issues}`);
324
+ }
325
+ const id = pluginIdFromPackageJson(validation.packageJson, resolvedRoot);
326
+ return {
327
+ id,
328
+ ...typeof validation.packageJson.name === "string" ? { packageName: validation.packageJson.name } : {},
329
+ ...typeof validation.packageJson.version === "string" ? { version: validation.packageJson.version } : {},
330
+ dependencyHints: dependencyHints(resolvedRoot, pkg)
331
+ };
332
+ }
333
+ var HOST_PROVIDED_DEPENDENCIES = /* @__PURE__ */ new Set(["react", "react-dom", "@hachej/boring-workspace", "@hachej/boring-ui-kit"]);
334
+ function dependencyHints(pluginRoot, pkg) {
335
+ const dependencies = pkg.dependencies;
336
+ if (!dependencies || typeof dependencies !== "object" || Array.isArray(dependencies)) return [];
337
+ const hints = [];
338
+ for (const dep of Object.keys(dependencies)) {
339
+ if (HOST_PROVIDED_DEPENDENCIES.has(dep)) continue;
340
+ const depDir = dep.startsWith("@") ? join(pluginRoot, "node_modules", ...dep.split("/")) : join(pluginRoot, "node_modules", dep);
341
+ if (!existsSync(depDir)) hints.push(`Missing dependency: ${dep}
342
+ Run: cd ${pluginRoot} && npm install`);
343
+ }
344
+ return hints;
345
+ }
346
+ function classifySource(source) {
347
+ if (source.startsWith("npm:")) return { kind: "npm", spec: source.slice("npm:".length), original: source };
348
+ if (source.startsWith("git:")) return { ...normalizeGitSource(source.slice("git:".length)), original: source };
349
+ if (source.startsWith("github:")) return { ...normalizeGitSource(source), original: source };
350
+ if (/^(https?|ssh):\/\//.test(source)) return { kind: "git", spec: source, original: source };
351
+ const maybePath = resolveMaybePath(source);
352
+ if (existsSync(maybePath)) return { kind: "local", spec: maybePath, original: source };
353
+ throw new Error(`unsupported plugin source ${JSON.stringify(source)}. Use a local path, npm:<package>, git:<repo>, github:<owner>/<repo>, or an http(s) git URL.`);
354
+ }
355
+ function normalizeGitSource(raw) {
356
+ let spec = raw;
357
+ let ref;
358
+ const hashIndex = spec.lastIndexOf("#");
359
+ if (hashIndex > 0) {
360
+ ref = spec.slice(hashIndex + 1);
361
+ spec = spec.slice(0, hashIndex);
362
+ } else {
363
+ const slashIndex = spec.lastIndexOf("/");
364
+ const atIndex = spec.lastIndexOf("@");
365
+ if (atIndex > slashIndex && !spec.slice(0, atIndex).includes(":")) {
366
+ ref = spec.slice(atIndex + 1);
367
+ spec = spec.slice(0, atIndex);
368
+ }
369
+ }
370
+ if (spec.startsWith("github:")) spec = `https://github.com/${spec.slice("github:".length)}`;
371
+ if (spec.startsWith("github.com/")) spec = `https://${spec}`;
372
+ return { kind: "git", spec, ...ref ? { ref } : {} };
373
+ }
374
+ function safeInstallDir(parent, id) {
375
+ const cleaned = id.replace(/[^A-Za-z0-9._:-]+/g, "-");
376
+ if (!cleaned || cleaned === "." || cleaned === "..") throw new Error(`invalid plugin id ${JSON.stringify(id)}`);
377
+ return join(parent, cleaned);
378
+ }
379
+ function moveFreshDir(from, to) {
380
+ if (existsSync(to)) throw new Error(`plugin install target already exists: ${to}. Remove it first with boring-ui-plugin remove ${basename(to)}`);
381
+ mkdirSync(dirname(to), { recursive: true });
382
+ renameSync(from, to);
383
+ }
384
+ function installLocalSource(source, paths) {
385
+ const rootDir = realpathSync(resolveMaybePath(source));
386
+ return { rootDir, packageSource: sourceForLocalPackage(paths, rootDir) };
387
+ }
388
+ function installPluginDependencies(pluginRoot) {
389
+ const pkg = readPackageJson(pluginRoot);
390
+ const declared = pkg.dependencies;
391
+ if (!declared || typeof declared !== "object" || Array.isArray(declared)) return;
392
+ const installable = Object.fromEntries(
393
+ Object.entries(declared).filter(([name]) => !HOST_PROVIDED_DEPENDENCIES.has(name))
394
+ );
395
+ if (Object.keys(installable).length === 0) return;
396
+ if (Object.keys(installable).length !== Object.keys(declared).length) {
397
+ writeFileSync(
398
+ join(pluginRoot, "package.json"),
399
+ `${JSON.stringify({ ...pkg, dependencies: installable }, null, 2)}
400
+ `,
401
+ "utf8"
402
+ );
403
+ }
404
+ try {
405
+ run("npm", ["install", "--omit=dev", "--no-audit", "--no-fund", "--legacy-peer-deps"], { cwd: pluginRoot });
406
+ } catch (err) {
407
+ rmSync(pluginRoot, { recursive: true, force: true });
408
+ const detail = err instanceof Error ? err.message : String(err);
409
+ throw new Error(`failed to install dependencies for plugin at ${pluginRoot}; install rolled back. ${detail}`);
410
+ }
411
+ }
412
+ function withStagingDir(parent, fn) {
413
+ mkdirSync(parent, { recursive: true });
414
+ const staging = mkdtempSync(join(parent, ".staging-"));
415
+ try {
416
+ return fn(staging);
417
+ } finally {
418
+ rmSync(staging, { recursive: true, force: true });
419
+ }
420
+ }
421
+ function installGitSource(spec, paths, ref) {
422
+ return withStagingDir(paths.gitDir, (staging) => {
423
+ const cloneDir = join(staging, "repo");
424
+ run("git", ["clone", "--quiet", spec, cloneDir]);
425
+ if (ref) run("git", ["checkout", "--quiet", ref], { cwd: cloneDir });
426
+ const meta = validateInstallablePluginRoot(cloneDir);
427
+ const target = safeInstallDir(paths.gitDir, meta.id);
428
+ moveFreshDir(cloneDir, target);
429
+ installPluginDependencies(target);
430
+ return { rootDir: target, packageSource: sourceForLocalPackage(paths, target), ...ref ? { ref } : {} };
431
+ });
432
+ }
433
+ function installNpmSource(spec, paths) {
434
+ return withStagingDir(paths.npmDir, (staging) => {
435
+ const packDir = join(staging, "pack");
436
+ const extractDir = join(staging, "extract");
437
+ mkdirSync(packDir, { recursive: true });
438
+ mkdirSync(extractDir, { recursive: true });
439
+ const stdout = runWithStdout("npm", ["pack", "--silent", spec, "--pack-destination", packDir], { cwd: staging });
440
+ const tarballName = stdout.trim().split(/\r?\n/).filter(Boolean).at(-1);
441
+ if (!tarballName) throw new Error(`npm pack did not produce a tarball for ${spec}`);
442
+ const tarball = isAbsolute(tarballName) ? tarballName : join(packDir, tarballName);
443
+ run("tar", ["-xzf", tarball, "-C", extractDir, "--strip-components", "1"]);
444
+ const meta = validateInstallablePluginRoot(extractDir);
445
+ const target = safeInstallDir(paths.npmDir, meta.id);
446
+ moveFreshDir(extractDir, target);
447
+ installPluginDependencies(target);
448
+ return { rootDir: target, packageSource: sourceForLocalPackage(paths, target) };
449
+ });
450
+ }
451
+ function recordFromPackageSource(paths, entry) {
452
+ const packageSource = packageEntrySource(entry);
453
+ if (!packageSource) return null;
454
+ const rootDir = resolvePackageSourcePath(paths.baseDir, packageSource);
455
+ if (!rootDir || !existsSync(join(rootDir, "package.json"))) return null;
456
+ const meta = validateInstallablePluginRoot(rootDir);
457
+ return {
458
+ id: meta.id,
459
+ kind: inferKind(paths, rootDir, packageSource),
460
+ scope: paths.scope,
461
+ packageSource,
462
+ source: rootDir,
463
+ rootDir,
464
+ ...meta.packageName ? { packageName: meta.packageName } : {},
465
+ ...meta.version ? { version: meta.version } : {}
466
+ };
467
+ }
468
+ function readPluginSourceRecords(paths) {
469
+ return packageEntries(readPiSettings(paths.settingsPath)).flatMap((entry) => {
470
+ try {
471
+ const record = recordFromPackageSource(paths, entry);
472
+ return record ? [record] : [];
473
+ } catch {
474
+ return [];
475
+ }
476
+ });
477
+ }
478
+ function readPluginSourceRecordsForRoots(opts) {
479
+ const local = resolvePluginSourceScopePaths("local", opts);
480
+ const global = resolvePluginSourceScopePaths("global", opts);
481
+ return [...readPluginSourceRecords(global), ...readPluginSourceRecords(local)];
482
+ }
483
+ function upsertPackageSource(paths, record) {
484
+ const settings = readPiSettings(paths.settingsPath);
485
+ const entries = packageEntries(settings);
486
+ let replaced = false;
487
+ const nextEntries = [];
488
+ for (const entry of entries) {
489
+ const existing = recordFromPackageSource(paths, entry);
490
+ const existingSource = packageEntrySource(entry);
491
+ if (existing?.id === record.id || existing?.rootDir === record.rootDir || existingSource === record.packageSource) {
492
+ replaced = true;
493
+ continue;
494
+ }
495
+ nextEntries.push(entry);
496
+ }
497
+ nextEntries.push(record.packageSource);
498
+ settings.packages = nextEntries;
499
+ writePiSettings(paths.settingsPath, settings);
500
+ return replaced;
501
+ }
502
+ function removePackageSource(paths, target) {
503
+ const settings = readPiSettings(paths.settingsPath);
504
+ const entries = packageEntries(settings);
505
+ const resolvedTarget = resolveMaybePath(target);
506
+ let removed = null;
507
+ const nextEntries = [];
508
+ for (const entry of entries) {
509
+ const packageSource = packageEntrySource(entry);
510
+ let record = null;
511
+ try {
512
+ record = recordFromPackageSource(paths, entry);
513
+ } catch {
514
+ record = null;
515
+ }
516
+ const rootDir = packageSource ? resolvePackageSourcePath(paths.baseDir, packageSource) : void 0;
517
+ const matches = record ? record.id === target || record.packageSource === target || record.source === target || record.rootDir === target || record.source === resolvedTarget || record.rootDir === resolvedTarget : packageSource === target || rootDir === target || rootDir === resolvedTarget;
518
+ if (matches && !removed) {
519
+ removed = record ?? (packageSource ? {
520
+ id: target,
521
+ kind: rootDir ? inferKind(paths, rootDir, packageSource) : "local",
522
+ scope: paths.scope,
523
+ packageSource,
524
+ source: rootDir ?? packageSource,
525
+ rootDir: rootDir ?? packageSource
526
+ } : null);
527
+ continue;
528
+ }
529
+ if (matches) {
530
+ continue;
531
+ }
532
+ nextEntries.push(entry);
533
+ }
534
+ if (!removed) return null;
535
+ settings.packages = nextEntries;
536
+ writePiSettings(paths.settingsPath, settings);
537
+ return removed;
538
+ }
539
+ function installPluginSource(opts) {
540
+ const scope = opts.scope ?? "local";
541
+ const paths = resolvePluginSourceScopePaths(scope, opts);
542
+ ensureScopeDirs(paths);
543
+ const classified = classifySource(opts.source);
544
+ const installed = classified.kind === "local" ? installLocalSource(classified.spec, paths) : classified.kind === "git" ? installGitSource(classified.spec, paths, classified.ref) : installNpmSource(classified.spec, paths);
545
+ const meta = validateInstallablePluginRoot(installed.rootDir);
546
+ const record = {
547
+ id: meta.id,
548
+ kind: classified.kind,
549
+ scope,
550
+ packageSource: installed.packageSource,
551
+ source: installed.rootDir,
552
+ rootDir: installed.rootDir,
553
+ ...meta.packageName ? { packageName: meta.packageName } : {},
554
+ ...meta.version ? { version: meta.version } : {},
555
+ ...installed.ref ? { ref: installed.ref } : {}
556
+ };
557
+ const replaced = upsertPackageSource(paths, record);
558
+ return { record, scopePaths: paths, dependencyHints: meta.dependencyHints, replaced };
559
+ }
560
+ function listPluginSources(opts = {}) {
561
+ const scopes = opts.scope === "all" ? [resolvePluginSourceScopePaths("global", opts), resolvePluginSourceScopePaths("local", opts)] : [resolvePluginSourceScopePaths(opts.scope ?? "local", opts)];
562
+ return {
563
+ scopes,
564
+ records: scopes.flatMap((paths) => readPluginSourceRecords(paths))
565
+ };
566
+ }
567
+ function removePluginSource(opts) {
568
+ const scope = opts.scope ?? "local";
569
+ const paths = resolvePluginSourceScopePaths(scope, opts);
570
+ const record = removePackageSource(paths, opts.target);
571
+ if (!record) throw new Error(`plugin source not found in ${scope} scope: ${opts.target}`);
572
+ let removedSourceDir = false;
573
+ if (record.kind === "git" || record.kind === "npm") {
574
+ const expectedRoot = record.kind === "git" ? paths.gitDir : paths.npmDir;
575
+ const resolvedRoot = resolve(record.rootDir);
576
+ if (pathInside(expectedRoot, resolvedRoot)) {
577
+ rmSync(resolvedRoot, { recursive: true, force: true });
578
+ removedSourceDir = true;
579
+ }
580
+ }
581
+ return { record, scopePaths: paths, removedSourceDir };
582
+ }
583
+ function formatPluginSourceList(result) {
584
+ if (result.records.length === 0) {
585
+ const scopes = result.scopes.map((scope) => scope.scope).join("+");
586
+ return `No plugins installed in ${scopes} scope.`;
587
+ }
588
+ return result.records.sort((a, b) => a.scope.localeCompare(b.scope) || a.id.localeCompare(b.id)).map((record) => `${record.id}
589
+ scope ${record.scope}
590
+ kind ${record.kind}
591
+ source ${record.packageSource}
592
+ dir ${record.rootDir}`).join("\n");
593
+ }
594
+
595
+ export {
596
+ isSafePluginRelativePath,
597
+ validateBoringPluginManifest,
598
+ resolvePluginSourceScopePaths,
599
+ readPluginSourceRecords,
600
+ readPluginSourceRecordsForRoots,
601
+ installPluginSource,
602
+ listPluginSources,
603
+ removePluginSource,
604
+ formatPluginSourceList
605
+ };
@@ -1,3 +1,12 @@
1
+ import {
2
+ formatPluginSourceList,
3
+ installPluginSource,
4
+ isSafePluginRelativePath,
5
+ listPluginSources,
6
+ removePluginSource,
7
+ validateBoringPluginManifest
8
+ } from "./chunk-6FD653KO.js";
9
+
1
10
  // src/server/index.ts
2
11
  import { join as join4, resolve as resolve4 } from "path";
3
12
 
@@ -198,161 +207,6 @@ function resolveBundledTemplatesDir() {
198
207
  import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3, realpathSync, statSync as statSync2 } from "fs";
199
208
  import { builtinModules, createRequire } from "module";
200
209
  import { extname, isAbsolute, join as join3, relative as relative2, resolve as resolve3 } from "path";
201
-
202
- // src/manifest.ts
203
- var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
204
- var PLUGIN_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
205
- function isValidBoringPluginId(id) {
206
- return typeof id === "string" && id.length > 0 && PLUGIN_ID_RE.test(id);
207
- }
208
- function isSafePluginRelativePath(value) {
209
- return typeof value === "string" && value.length > 0 && value !== "." && !value.includes("\0") && !value.includes("\\") && !value.startsWith("/") && !value.startsWith("//") && !/^[A-Za-z]:[\\/]/.test(value) && !value.split("/").includes("..");
210
- }
211
- function issue(code, field, message) {
212
- return { code, field, message };
213
- }
214
- function isRecord(value) {
215
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
216
- }
217
- function validateStringArray(issues, value, field, pathLike) {
218
- if (value === void 0) return;
219
- if (!Array.isArray(value)) {
220
- issues.push(issue("INVALID_FIELD", field, `${field} must be an array`));
221
- return;
222
- }
223
- value.forEach((entry, index) => {
224
- const itemField = `${field}[${index}]`;
225
- if (typeof entry !== "string" || entry.length === 0) {
226
- issues.push(issue("INVALID_FIELD", itemField, `${itemField} must be a non-empty string`));
227
- return;
228
- }
229
- if (pathLike && !isSafePluginRelativePath(entry)) {
230
- issues.push(issue("INVALID_PATH", itemField, `${itemField} must be a safe relative path`));
231
- }
232
- });
233
- }
234
- var REMOVED_BORING_UI_FIELDS = ["outputs", "panels", "commands", "leftTabs", "surfaceResolvers", "providers", "bindings", "catalogs"];
235
- function validateBoringField(issues, boring) {
236
- if (boring === void 0) return void 0;
237
- if (!isRecord(boring)) {
238
- issues.push(issue("INVALID_FIELD", "boring", "boring must be an object when provided"));
239
- return void 0;
240
- }
241
- for (const field of REMOVED_BORING_UI_FIELDS) {
242
- if (boring[field] !== void 0) {
243
- issues.push(issue(
244
- "INVALID_FIELD",
245
- `boring.${field}`,
246
- `boring.${field} is not supported; declare front contributions in boring.front via definePlugin({ ... })`
247
- ));
248
- }
249
- }
250
- if (boring.id !== void 0 && (typeof boring.id !== "string" || !isValidBoringPluginId(boring.id))) {
251
- issues.push(issue("INVALID_ID", "boring.id", "boring.id must start with a letter or number and use only letters, numbers, dot, underscore, colon, or dash"));
252
- }
253
- const front = boring.front;
254
- if (front !== void 0 && (typeof front !== "string" || !isSafePluginRelativePath(front))) {
255
- issues.push(issue("INVALID_PATH", "boring.front", "boring.front must be a safe relative path"));
256
- }
257
- const server = boring.server;
258
- if (server !== void 0 && server !== false && (typeof server !== "string" || !isSafePluginRelativePath(server))) {
259
- issues.push(issue("INVALID_PATH", "boring.server", "boring.server must be a safe relative path or false"));
260
- }
261
- if (boring.label !== void 0 && typeof boring.label !== "string") {
262
- issues.push(issue("INVALID_FIELD", "boring.label", "boring.label must be a string when provided"));
263
- }
264
- return {
265
- ...typeof boring.id === "string" ? { id: boring.id } : {},
266
- ...typeof boring.front === "string" ? { front: boring.front } : {},
267
- ...typeof boring.server === "string" || boring.server === false ? { server: boring.server } : {},
268
- ...typeof boring.label === "string" ? { label: boring.label } : {}
269
- };
270
- }
271
- var REMOTE_PI_PACKAGE_PREFIXES = ["npm:", "git:", "github:", "http:", "https:", "ssh:"];
272
- function isRemotePiPackageSource(value) {
273
- return REMOTE_PI_PACKAGE_PREFIXES.some((prefix) => value.startsWith(prefix));
274
- }
275
- function isSafePiPackageSource(value) {
276
- if (value.length === 0) return false;
277
- if (isRemotePiPackageSource(value)) return true;
278
- const path = value.startsWith("file:") ? value.slice("file:".length) : value;
279
- if (path === "." || path === "./") return true;
280
- const normalized = path.startsWith("./") ? path.slice(2) : path;
281
- return isSafePluginRelativePath(normalized);
282
- }
283
- function validatePiPackages(issues, value) {
284
- if (value === void 0) return;
285
- if (!Array.isArray(value)) {
286
- issues.push(issue("INVALID_FIELD", "pi.packages", "pi.packages must be an array when provided"));
287
- return;
288
- }
289
- value.forEach((entry, index) => {
290
- const field = `pi.packages[${index}]`;
291
- if (typeof entry === "string") {
292
- if (!isSafePiPackageSource(entry)) {
293
- issues.push(issue("INVALID_PATH", field, `${field} must be a safe package source`));
294
- }
295
- return;
296
- }
297
- if (!isRecord(entry)) {
298
- issues.push(issue("INVALID_FIELD", field, `${field} must be a string or package source object`));
299
- return;
300
- }
301
- if (typeof entry.source !== "string" || entry.source.length === 0) {
302
- issues.push(issue("INVALID_FIELD", `${field}.source`, `${field}.source must be a non-empty string`));
303
- } else if (!isSafePiPackageSource(entry.source)) {
304
- issues.push(issue("INVALID_PATH", `${field}.source`, `${field}.source must be a safe package source`));
305
- }
306
- });
307
- }
308
- function validatePiField(issues, pi) {
309
- if (pi === void 0) return void 0;
310
- if (!isRecord(pi)) {
311
- issues.push(issue("INVALID_FIELD", "pi", "pi must be an object when provided"));
312
- return void 0;
313
- }
314
- validateStringArray(issues, pi.extensions, "pi.extensions", true);
315
- validateStringArray(issues, pi.skills, "pi.skills", true);
316
- validatePiPackages(issues, pi.packages);
317
- if (pi.systemPrompt !== void 0 && typeof pi.systemPrompt !== "string") {
318
- issues.push(issue("INVALID_FIELD", "pi.systemPrompt", "pi.systemPrompt must be a string when provided"));
319
- }
320
- return pi;
321
- }
322
- function validateBoringPluginManifest(raw) {
323
- const issues = [];
324
- if (!isRecord(raw)) {
325
- return {
326
- valid: false,
327
- issues: [issue("INVALID_FIELD", "<root>", "package.json manifest must be an object")]
328
- };
329
- }
330
- if (raw.name !== void 0 && typeof raw.name !== "string") {
331
- issues.push(issue("INVALID_FIELD", "name", "name must be a string when provided"));
332
- }
333
- if (raw.version !== void 0 && typeof raw.version !== "string") {
334
- issues.push(issue("INVALID_VERSION", "version", "version must be a string when provided"));
335
- } else if (typeof raw.version === "string" && raw.version.length > 0 && !SEMVER_RE.test(raw.version)) {
336
- issues.push(issue("INVALID_VERSION", "version", "version must be a valid semver string"));
337
- }
338
- const boring = validateBoringField(issues, raw.boring);
339
- const pi = validatePiField(issues, raw.pi);
340
- if (!boring && !pi) {
341
- issues.push(issue("MISSING_REQUIRED_FIELD", "boring|pi", "package.json must include boring and/or pi plugin metadata"));
342
- }
343
- if (issues.length > 0) return { valid: false, issues };
344
- return {
345
- valid: true,
346
- packageJson: {
347
- ...typeof raw.name === "string" ? { name: raw.name } : {},
348
- ...typeof raw.version === "string" ? { version: raw.version } : {},
349
- ...boring ? { boring } : {},
350
- ...pi ? { pi } : {}
351
- }
352
- };
353
- }
354
-
355
- // src/server/verifyPlugin.ts
356
210
  function pluginFileSignature(path) {
357
211
  if (!path || !existsSync3(path)) return "missing";
358
212
  const stat = statSync2(path);
@@ -577,7 +431,7 @@ function verifySinglePlugin(pluginDir) {
577
431
  if (isObject(parsed)) validateDependencyManifest(pluginDir, parsed, errors);
578
432
  const result = validateBoringPluginManifest(parsed);
579
433
  if (!result.valid) {
580
- for (const issue2 of result.issues) errors.push(formatIssue(issue2));
434
+ for (const issue of result.issues) errors.push(formatIssue(issue));
581
435
  return { id, dir: pluginDir, ok: false, errors, warnings };
582
436
  }
583
437
  const manifest = result.packageJson;
@@ -627,8 +481,8 @@ function verifySinglePlugin(pluginDir) {
627
481
  }
628
482
  return { id, dir: pluginDir, ok: errors.length === 0, errors, warnings };
629
483
  }
630
- function formatIssue(issue2) {
631
- return `${issue2.code}: ${issue2.field}: ${issue2.message}`;
484
+ function formatIssue(issue) {
485
+ return `${issue.code}: ${issue.field}: ${issue.message}`;
632
486
  }
633
487
  function formatVerifyResult(result) {
634
488
  if (result.extensionsDirMissing) {
@@ -987,7 +841,10 @@ function pluginCommandUsage() {
987
841
  " boring-ui-plugin create <name> [--path <dir>]",
988
842
  " boring-ui-plugin scaffold <name> [workspace]",
989
843
  " boring-ui-plugin verify [name] [workspace]",
990
- " boring-ui-plugin test <name> [--url <url>] [--workspace <id>] [--panel-id <id>] [--timeout-ms <ms>] [--json]"
844
+ " boring-ui-plugin test <name> [--url <url>] [--workspace <id>] [--panel-id <id>] [--timeout-ms <ms>] [--json]",
845
+ " boring-ui-plugin install [-l|--local|--global] [--workspace <dir>] <source>",
846
+ " boring-ui-plugin list [--local|--global|--all] [--workspace <dir>] [--json]",
847
+ " boring-ui-plugin remove [-l|--local|--global] [--workspace <dir>] <id-or-source>"
991
848
  ].join("\n");
992
849
  }
993
850
  function handleStatus(json) {
@@ -1054,6 +911,77 @@ function readOption(argv, name) {
1054
911
  if (index === -1) return void 0;
1055
912
  return argv[index + 1];
1056
913
  }
914
+ function pluginSourceScope(argv) {
915
+ if (argv.includes("--global")) return "global";
916
+ return "local";
917
+ }
918
+ function pluginSourceWorkspaceRoot(argv) {
919
+ return readOption(argv, "--workspace");
920
+ }
921
+ function commandPositionals(argv) {
922
+ const out = [];
923
+ for (let i = 0; i < argv.length; i++) {
924
+ const arg = argv[i];
925
+ if (arg === "--workspace") {
926
+ i++;
927
+ continue;
928
+ }
929
+ if (arg.startsWith("-")) continue;
930
+ out.push(arg);
931
+ }
932
+ return out;
933
+ }
934
+ function handleInstall(argv, json) {
935
+ const source = commandPositionals(argv)[1];
936
+ if (!source) throw new Error("usage: boring-ui-plugin install [--local|--global] [--workspace <dir>] <source>");
937
+ const result = installPluginSource({
938
+ source,
939
+ scope: pluginSourceScope(argv),
940
+ ...pluginSourceWorkspaceRoot(argv) ? { workspaceRoot: pluginSourceWorkspaceRoot(argv) } : {}
941
+ });
942
+ if (json) {
943
+ console.log(JSON.stringify(result, null, 2));
944
+ return;
945
+ }
946
+ if (result.record.kind === "git" || result.record.kind === "npm") {
947
+ console.warn("Security: Boring plugins run as trusted local code in CLI mode. Review third-party source before installing.");
948
+ }
949
+ console.log(`${result.replaced ? "updated" : "installed"} ${result.record.id}`);
950
+ console.log(` scope ${result.record.scope}`);
951
+ console.log(` kind ${result.record.kind}`);
952
+ console.log(` dir ${result.record.rootDir}`);
953
+ if (result.dependencyHints.length > 0) {
954
+ console.log("");
955
+ console.log("Dependencies are not installed by boring-ui-plugin install. Run package-manager commands in the plugin folder:");
956
+ for (const hint of result.dependencyHints) console.log(hint);
957
+ }
958
+ console.log("");
959
+ console.log("Next step: ask the user to run /reload in the workspace UI.");
960
+ }
961
+ function handleList(argv, json) {
962
+ const scope = argv.includes("--all") ? "all" : pluginSourceScope(argv);
963
+ const result = listPluginSources({
964
+ scope,
965
+ ...pluginSourceWorkspaceRoot(argv) ? { workspaceRoot: pluginSourceWorkspaceRoot(argv) } : {}
966
+ });
967
+ console.log(json ? JSON.stringify(result, null, 2) : formatPluginSourceList(result));
968
+ }
969
+ function handleRemove(argv, json) {
970
+ const target = commandPositionals(argv)[1];
971
+ if (!target) throw new Error("usage: boring-ui-plugin remove [--local|--global] [--workspace <dir>] <id-or-source>");
972
+ const result = removePluginSource({
973
+ target,
974
+ scope: pluginSourceScope(argv),
975
+ ...pluginSourceWorkspaceRoot(argv) ? { workspaceRoot: pluginSourceWorkspaceRoot(argv) } : {}
976
+ });
977
+ if (json) {
978
+ console.log(JSON.stringify(result, null, 2));
979
+ return;
980
+ }
981
+ console.log(`removed ${result.record.id}`);
982
+ console.log(` scope ${result.record.scope}`);
983
+ if (result.removedSourceDir) console.log(` deleted ${result.record.rootDir}`);
984
+ }
1057
985
  async function handleTest(argv, positionals, json) {
1058
986
  const name = positionals[0];
1059
987
  if (!name) throw new Error("usage: boring-ui-plugin test <name> [--url <local-server-url>] [--workspace <id>] [--panel-id <id>] [--timeout-ms <ms>] [--json]");
@@ -1080,6 +1008,9 @@ async function runBoringUiPluginCli(argv = process.argv.slice(2)) {
1080
1008
  if (command === "scaffold") return handleScaffold(rest);
1081
1009
  if (command === "verify") return handleVerify(rest);
1082
1010
  if (command === "test") return await handleTest(argv, rest, json);
1011
+ if (command === "install") return handleInstall(argv, json);
1012
+ if (command === "list") return handleList(argv, json);
1013
+ if (command === "remove") return handleRemove(argv, json);
1083
1014
  console.log(pluginCommandUsage());
1084
1015
  }
1085
1016
 
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export { InstallPluginSourceOptions, ListPluginSourcesOptions, PluginInstallResult, PluginInstallScope, PluginListResult, PluginRemoveResult, PluginSourceKind, PluginSourceRecord, PluginSourceScopePaths, RemovePluginSourceOptions, formatPluginSourceList, installPluginSource, listPluginSources, readPluginSourceRecords, readPluginSourceRecordsForRoots, removePluginSource, resolvePluginSourceScopePaths } from './plugin-sources.js';
2
+
1
3
  interface CreatePluginOptions {
2
4
  name: string;
3
5
  path?: string;
package/dist/index.js CHANGED
@@ -8,13 +8,29 @@ import {
8
8
  runPluginSelfTest,
9
9
  scaffoldPlugin,
10
10
  verifyPlugin
11
- } from "./chunk-DH4PSVGY.js";
11
+ } from "./chunk-HY4ZELTX.js";
12
+ import {
13
+ formatPluginSourceList,
14
+ installPluginSource,
15
+ listPluginSources,
16
+ readPluginSourceRecords,
17
+ readPluginSourceRecordsForRoots,
18
+ removePluginSource,
19
+ resolvePluginSourceScopePaths
20
+ } from "./chunk-6FD653KO.js";
12
21
  export {
13
22
  createPlugin,
14
23
  findHintForError,
24
+ formatPluginSourceList,
15
25
  formatSelfTestResult,
16
26
  formatVerifyResult,
27
+ installPluginSource,
28
+ listPluginSources,
17
29
  pluginCommandUsage,
30
+ readPluginSourceRecords,
31
+ readPluginSourceRecordsForRoots,
32
+ removePluginSource,
33
+ resolvePluginSourceScopePaths,
18
34
  runBoringUiPluginCli,
19
35
  runPluginSelfTest,
20
36
  scaffoldPlugin,
@@ -0,0 +1,71 @@
1
+ type PluginInstallScope = "local" | "global";
2
+ type PluginSourceKind = "local" | "git" | "npm";
3
+ interface PluginSourceRecord {
4
+ id: string;
5
+ kind: PluginSourceKind;
6
+ scope: PluginInstallScope;
7
+ /** Pi package source entry as stored in settings.json. */
8
+ packageSource: string;
9
+ /** Resolved package root when the source is locally inspectable. */
10
+ source: string;
11
+ rootDir: string;
12
+ packageName?: string;
13
+ version?: string;
14
+ ref?: string;
15
+ }
16
+ interface PluginSourceScopePaths {
17
+ scope: PluginInstallScope;
18
+ workspaceRoot?: string;
19
+ baseDir: string;
20
+ extensionsDir: string;
21
+ gitDir: string;
22
+ npmDir: string;
23
+ settingsPath: string;
24
+ }
25
+ interface InstallPluginSourceOptions {
26
+ source: string;
27
+ scope?: PluginInstallScope;
28
+ workspaceRoot?: string;
29
+ globalRoot?: string;
30
+ }
31
+ interface RemovePluginSourceOptions {
32
+ target: string;
33
+ scope?: PluginInstallScope;
34
+ workspaceRoot?: string;
35
+ globalRoot?: string;
36
+ }
37
+ interface ListPluginSourcesOptions {
38
+ scope?: PluginInstallScope | "all";
39
+ workspaceRoot?: string;
40
+ globalRoot?: string;
41
+ }
42
+ interface PluginInstallResult {
43
+ record: PluginSourceRecord;
44
+ scopePaths: PluginSourceScopePaths;
45
+ dependencyHints: string[];
46
+ replaced: boolean;
47
+ }
48
+ interface PluginRemoveResult {
49
+ record: PluginSourceRecord;
50
+ scopePaths: PluginSourceScopePaths;
51
+ removedSourceDir: boolean;
52
+ }
53
+ interface PluginListResult {
54
+ records: PluginSourceRecord[];
55
+ scopes: PluginSourceScopePaths[];
56
+ }
57
+ declare function resolvePluginSourceScopePaths(scope: PluginInstallScope, opts?: {
58
+ workspaceRoot?: string;
59
+ globalRoot?: string;
60
+ }): PluginSourceScopePaths;
61
+ declare function readPluginSourceRecords(paths: PluginSourceScopePaths): PluginSourceRecord[];
62
+ declare function readPluginSourceRecordsForRoots(opts: {
63
+ workspaceRoot: string;
64
+ globalRoot?: string;
65
+ }): PluginSourceRecord[];
66
+ declare function installPluginSource(opts: InstallPluginSourceOptions): PluginInstallResult;
67
+ declare function listPluginSources(opts?: ListPluginSourcesOptions): PluginListResult;
68
+ declare function removePluginSource(opts: RemovePluginSourceOptions): PluginRemoveResult;
69
+ declare function formatPluginSourceList(result: PluginListResult): string;
70
+
71
+ export { type InstallPluginSourceOptions, type ListPluginSourcesOptions, type PluginInstallResult, type PluginInstallScope, type PluginListResult, type PluginRemoveResult, type PluginSourceKind, type PluginSourceRecord, type PluginSourceScopePaths, type RemovePluginSourceOptions, formatPluginSourceList, installPluginSource, listPluginSources, readPluginSourceRecords, readPluginSourceRecordsForRoots, removePluginSource, resolvePluginSourceScopePaths };
@@ -0,0 +1,18 @@
1
+ import {
2
+ formatPluginSourceList,
3
+ installPluginSource,
4
+ listPluginSources,
5
+ readPluginSourceRecords,
6
+ readPluginSourceRecordsForRoots,
7
+ removePluginSource,
8
+ resolvePluginSourceScopePaths
9
+ } from "./chunk-6FD653KO.js";
10
+ export {
11
+ formatPluginSourceList,
12
+ installPluginSource,
13
+ listPluginSources,
14
+ readPluginSourceRecords,
15
+ readPluginSourceRecordsForRoots,
16
+ removePluginSource,
17
+ resolvePluginSourceScopePaths
18
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-ui-plugin-cli",
3
- "version": "0.1.33",
3
+ "version": "0.1.34",
4
4
  "description": "Slim boring-ui plugin authoring CLI for workspace runtimes.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -16,6 +16,10 @@
16
16
  "import": "./dist/index.js",
17
17
  "types": "./dist/index.d.ts"
18
18
  },
19
+ "./plugin-sources": {
20
+ "import": "./dist/plugin-sources.js",
21
+ "types": "./dist/plugin-sources.d.ts"
22
+ },
19
23
  "./package.json": "./package.json"
20
24
  },
21
25
  "devDependencies": {