@f5xc-salesdemos/xcsh 19.5.1 → 19.7.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/package.json +7 -7
- package/src/discovery/claude-plugins.ts +9 -8
- package/src/discovery/helpers.ts +23 -2
- package/src/extensibility/plugins/marketplace/fetcher.ts +3 -2
- package/src/extensibility/plugins/marketplace/manager.ts +42 -14
- package/src/extensibility/plugins/marketplace/types.ts +2 -2
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/modes/components/plugin-selector.ts +1 -1
- package/src/modes/components/plugins/types.ts +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5xc-salesdemos/xcsh",
|
|
4
|
-
"version": "19.
|
|
4
|
+
"version": "19.7.0",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/f5xc-salesdemos/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
52
52
|
"@mozilla/readability": "^0.6",
|
|
53
|
-
"@f5xc-salesdemos/xcsh-stats": "19.
|
|
54
|
-
"@f5xc-salesdemos/pi-agent-core": "19.
|
|
55
|
-
"@f5xc-salesdemos/pi-ai": "19.
|
|
56
|
-
"@f5xc-salesdemos/pi-natives": "19.
|
|
57
|
-
"@f5xc-salesdemos/pi-tui": "19.
|
|
58
|
-
"@f5xc-salesdemos/pi-utils": "19.
|
|
53
|
+
"@f5xc-salesdemos/xcsh-stats": "19.7.0",
|
|
54
|
+
"@f5xc-salesdemos/pi-agent-core": "19.7.0",
|
|
55
|
+
"@f5xc-salesdemos/pi-ai": "19.7.0",
|
|
56
|
+
"@f5xc-salesdemos/pi-natives": "19.7.0",
|
|
57
|
+
"@f5xc-salesdemos/pi-tui": "19.7.0",
|
|
58
|
+
"@f5xc-salesdemos/pi-utils": "19.7.0",
|
|
59
59
|
"@sinclair/typebox": "^0.34",
|
|
60
60
|
"@xterm/headless": "^6.0",
|
|
61
61
|
"ajv": "^8.20",
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
listXcshPluginRoots,
|
|
20
20
|
loadFilesFromDir,
|
|
21
21
|
scanSkillsFromDir,
|
|
22
|
+
scopeToLevel,
|
|
22
23
|
type XcshPluginRoot,
|
|
23
24
|
} from "./helpers";
|
|
24
25
|
|
|
@@ -45,7 +46,7 @@ async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
|
|
|
45
46
|
const result = await scanSkillsFromDir(ctx, {
|
|
46
47
|
dir: skillsDir,
|
|
47
48
|
providerId: PROVIDER_ID,
|
|
48
|
-
level: root.scope,
|
|
49
|
+
level: scopeToLevel(root.scope),
|
|
49
50
|
});
|
|
50
51
|
return { root, result };
|
|
51
52
|
}),
|
|
@@ -76,7 +77,7 @@ async function loadSlashCommands(ctx: LoadContext): Promise<LoadResult<SlashComm
|
|
|
76
77
|
const results = await Promise.all(
|
|
77
78
|
roots.map(async root => {
|
|
78
79
|
const commandsDir = path.join(root.path, "commands");
|
|
79
|
-
return loadFilesFromDir<SlashCommand>(ctx, commandsDir, PROVIDER_ID, root.scope, {
|
|
80
|
+
return loadFilesFromDir<SlashCommand>(ctx, commandsDir, PROVIDER_ID, scopeToLevel(root.scope), {
|
|
80
81
|
extensions: ["md"],
|
|
81
82
|
transform: (name, content, filePath, source) => {
|
|
82
83
|
const cmdName = name.replace(/\.md$/, "");
|
|
@@ -84,7 +85,7 @@ async function loadSlashCommands(ctx: LoadContext): Promise<LoadResult<SlashComm
|
|
|
84
85
|
name: root.plugin ? `${root.plugin}:${cmdName}` : cmdName,
|
|
85
86
|
path: filePath,
|
|
86
87
|
content,
|
|
87
|
-
level: root.scope,
|
|
88
|
+
level: scopeToLevel(root.scope),
|
|
88
89
|
_source: source,
|
|
89
90
|
};
|
|
90
91
|
},
|
|
@@ -123,7 +124,7 @@ async function loadHooks(ctx: LoadContext): Promise<LoadResult<Hook>> {
|
|
|
123
124
|
const results = await Promise.all(
|
|
124
125
|
loadTasks.map(async ({ root, hookType }) => {
|
|
125
126
|
const hooksDir = path.join(root.path, "hooks", hookType);
|
|
126
|
-
return loadFilesFromDir<Hook>(ctx, hooksDir, PROVIDER_ID, root.scope, {
|
|
127
|
+
return loadFilesFromDir<Hook>(ctx, hooksDir, PROVIDER_ID, scopeToLevel(root.scope), {
|
|
127
128
|
transform: (name, _content, filePath, source) => {
|
|
128
129
|
const toolName = name.replace(/\.(sh|bash|zsh|fish)$/, "");
|
|
129
130
|
return {
|
|
@@ -131,7 +132,7 @@ async function loadHooks(ctx: LoadContext): Promise<LoadResult<Hook>> {
|
|
|
131
132
|
path: filePath,
|
|
132
133
|
type: hookType,
|
|
133
134
|
tool: toolName,
|
|
134
|
-
level: root.scope,
|
|
135
|
+
level: scopeToLevel(root.scope),
|
|
135
136
|
_source: source,
|
|
136
137
|
};
|
|
137
138
|
},
|
|
@@ -161,14 +162,14 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
|
|
|
161
162
|
const results = await Promise.all(
|
|
162
163
|
roots.map(async root => {
|
|
163
164
|
const toolsDir = path.join(root.path, "tools");
|
|
164
|
-
return loadFilesFromDir<CustomTool>(ctx, toolsDir, PROVIDER_ID, root.scope, {
|
|
165
|
+
return loadFilesFromDir<CustomTool>(ctx, toolsDir, PROVIDER_ID, scopeToLevel(root.scope), {
|
|
165
166
|
transform: (name, _content, filePath, source) => {
|
|
166
167
|
const toolName = name.replace(/\.(ts|js|sh|bash|py)$/, "");
|
|
167
168
|
return {
|
|
168
169
|
name: toolName,
|
|
169
170
|
path: filePath,
|
|
170
171
|
description: `${toolName} custom tool`,
|
|
171
|
-
level: root.scope,
|
|
172
|
+
level: scopeToLevel(root.scope),
|
|
172
173
|
_source: source,
|
|
173
174
|
};
|
|
174
175
|
},
|
|
@@ -242,7 +243,7 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
|
|
|
242
243
|
...(raw.auth !== undefined && { auth: raw.auth }),
|
|
243
244
|
...(raw.oauth !== undefined && { oauth: raw.oauth }),
|
|
244
245
|
...(raw.type !== undefined && { transport: raw.type as MCPServer["transport"] }),
|
|
245
|
-
_source: createSourceMeta(PROVIDER_ID, mcpPath, root.scope),
|
|
246
|
+
_source: createSourceMeta(PROVIDER_ID, mcpPath, scopeToLevel(root.scope)),
|
|
246
247
|
};
|
|
247
248
|
items.push(server);
|
|
248
249
|
}
|
package/src/discovery/helpers.ts
CHANGED
|
@@ -365,6 +365,22 @@ function expandEnvVars(value: string, extraEnv?: Record<string, string>): string
|
|
|
365
365
|
});
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
+
/**
|
|
369
|
+
* Build plugin-specific environment variables for variable substitution.
|
|
370
|
+
* These are injected as extraEnv when loading plugin configs (hooks, MCP, LSP).
|
|
371
|
+
*/
|
|
372
|
+
export function buildPluginEnvVars(pluginRoot: string, pluginId: string, projectDir?: string): Record<string, string> {
|
|
373
|
+
const dataDir = path.join(os.homedir(), getConfigDirName(), "plugins", "data", pluginId.replace("@", "__"));
|
|
374
|
+
const vars: Record<string, string> = {
|
|
375
|
+
XCSH_PLUGIN_ROOT: pluginRoot,
|
|
376
|
+
XCSH_PLUGIN_DATA: dataDir,
|
|
377
|
+
};
|
|
378
|
+
if (projectDir) {
|
|
379
|
+
vars.XCSH_PROJECT_DIR = projectDir;
|
|
380
|
+
}
|
|
381
|
+
return vars;
|
|
382
|
+
}
|
|
383
|
+
|
|
368
384
|
/**
|
|
369
385
|
* Recursively expand environment variables in an object.
|
|
370
386
|
*/
|
|
@@ -621,8 +637,13 @@ export interface XcshPluginRoot {
|
|
|
621
637
|
version: string;
|
|
622
638
|
/** Absolute path to plugin root */
|
|
623
639
|
path: string;
|
|
624
|
-
/** Whether this is a user or
|
|
625
|
-
scope: "user" | "project";
|
|
640
|
+
/** Whether this is a user, project, or local scope plugin */
|
|
641
|
+
scope: "user" | "project" | "local";
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/** Map plugin scope to the loading level — "local" behaves as "project" for capability loading. */
|
|
645
|
+
export function scopeToLevel(scope: "user" | "project" | "local"): "user" | "project" {
|
|
646
|
+
return scope === "local" ? "project" : scope;
|
|
626
647
|
}
|
|
627
648
|
|
|
628
649
|
/**
|
|
@@ -277,8 +277,9 @@ async function cloneAndReadCatalog(url: string, cacheDir: string): Promise<Fetch
|
|
|
277
277
|
const tmpDir = path.join(cacheDir, `.tmp-clone-${Date.now()}`);
|
|
278
278
|
await fs.mkdir(cacheDir, { recursive: true });
|
|
279
279
|
|
|
280
|
-
|
|
281
|
-
|
|
280
|
+
const timeoutMs = Number(Bun.env.XCSH_PLUGIN_GIT_TIMEOUT_MS) || 120_000;
|
|
281
|
+
logger.debug(`[marketplace] cloning ${url} → ${tmpDir} (timeout: ${timeoutMs}ms)`);
|
|
282
|
+
await git.clone(url, tmpDir, { signal: AbortSignal.timeout(timeoutMs) });
|
|
282
283
|
|
|
283
284
|
const catalogPath = path.join(tmpDir, CATALOG_RELATIVE_PATH);
|
|
284
285
|
let content: string;
|
|
@@ -49,6 +49,11 @@ export interface MarketplaceManagerOptions {
|
|
|
49
49
|
* Resolved by resolveActiveProjectRegistryPath(cwd) in callers.
|
|
50
50
|
*/
|
|
51
51
|
projectInstalledRegistryPath?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Path to the local-scoped installed_plugins.json (gitignored, project-specific).
|
|
54
|
+
* Same as project path but under a `.local` variant so it stays out of VCS.
|
|
55
|
+
*/
|
|
56
|
+
localInstalledRegistryPath?: string;
|
|
52
57
|
marketplacesCacheDir: string;
|
|
53
58
|
pluginsCacheDir: string;
|
|
54
59
|
/** Injected for testing; production callers pass clearXcshPluginRootsCache.
|
|
@@ -143,7 +148,17 @@ export class MarketplaceManager {
|
|
|
143
148
|
throw new Error(`Marketplace "${name}" not found`);
|
|
144
149
|
}
|
|
145
150
|
|
|
146
|
-
|
|
151
|
+
let fetchResult: { catalog: MarketplaceCatalog; clonePath?: string };
|
|
152
|
+
try {
|
|
153
|
+
fetchResult = await fetchMarketplace(existing.sourceUri, this.#opts.marketplacesCacheDir);
|
|
154
|
+
} catch (err) {
|
|
155
|
+
if (Bun.env.XCSH_PLUGIN_KEEP_MARKETPLACE_ON_FAILURE === "1") {
|
|
156
|
+
logger.debug("Marketplace fetch failed, preserving cached catalog", { name, error: String(err) });
|
|
157
|
+
return existing;
|
|
158
|
+
}
|
|
159
|
+
throw err;
|
|
160
|
+
}
|
|
161
|
+
const { catalog, clonePath } = fetchResult;
|
|
147
162
|
|
|
148
163
|
// Guard against upstream catalog silently renaming itself — the registry
|
|
149
164
|
// entry is keyed by name, so a drift would corrupt the entry on next read.
|
|
@@ -227,7 +242,7 @@ export class MarketplaceManager {
|
|
|
227
242
|
async installPlugin(
|
|
228
243
|
name: string,
|
|
229
244
|
marketplace: string,
|
|
230
|
-
options?: { force?: boolean; scope?: "user" | "project" },
|
|
245
|
+
options?: { force?: boolean; scope?: "user" | "project" | "local" },
|
|
231
246
|
): Promise<InstalledPluginEntry> {
|
|
232
247
|
const force = options?.force ?? false;
|
|
233
248
|
const scope = options?.scope ?? "user";
|
|
@@ -378,7 +393,7 @@ export class MarketplaceManager {
|
|
|
378
393
|
return "0.0.0";
|
|
379
394
|
}
|
|
380
395
|
|
|
381
|
-
async uninstallPlugin(pluginId: string, scope?: "user" | "project"): Promise<void> {
|
|
396
|
+
async uninstallPlugin(pluginId: string, scope?: "user" | "project" | "local"): Promise<void> {
|
|
382
397
|
const parsed = parsePluginId(pluginId);
|
|
383
398
|
if (!parsed) {
|
|
384
399
|
throw new Error(`Invalid plugin ID format: "${pluginId}". Expected "name@marketplace".`);
|
|
@@ -394,7 +409,7 @@ export class MarketplaceManager {
|
|
|
394
409
|
}
|
|
395
410
|
|
|
396
411
|
// Disambiguation: if installed in both scopes and no explicit scope, require one.
|
|
397
|
-
let targetScope: "user" | "project";
|
|
412
|
+
let targetScope: "user" | "project" | "local";
|
|
398
413
|
if (inUser && inProject) {
|
|
399
414
|
if (!scope) {
|
|
400
415
|
throw new Error(
|
|
@@ -478,7 +493,7 @@ export class MarketplaceManager {
|
|
|
478
493
|
return results;
|
|
479
494
|
}
|
|
480
495
|
|
|
481
|
-
async setPluginEnabled(pluginId: string, enabled: boolean, scope?: "user" | "project"): Promise<void> {
|
|
496
|
+
async setPluginEnabled(pluginId: string, enabled: boolean, scope?: "user" | "project" | "local"): Promise<void> {
|
|
482
497
|
const { userEntries, projectEntries, userReg, projectReg } = await this.#findInBothRegistries(pluginId);
|
|
483
498
|
|
|
484
499
|
const inUser = userEntries && userEntries.length > 0;
|
|
@@ -489,7 +504,7 @@ export class MarketplaceManager {
|
|
|
489
504
|
}
|
|
490
505
|
|
|
491
506
|
// Disambiguation: if installed in both scopes and no explicit scope, require one.
|
|
492
|
-
let targetScope: "user" | "project";
|
|
507
|
+
let targetScope: "user" | "project" | "local";
|
|
493
508
|
if (inUser && inProject) {
|
|
494
509
|
if (!scope) {
|
|
495
510
|
throw new Error(
|
|
@@ -548,16 +563,23 @@ export class MarketplaceManager {
|
|
|
548
563
|
// Compare installed plugin versions against their catalog entries.
|
|
549
564
|
// Returns one entry per (pluginId, scope) pair where the catalog declares a newer version.
|
|
550
565
|
// Catalog entries without a version field are skipped.
|
|
551
|
-
async checkForUpdates(): Promise<
|
|
566
|
+
async checkForUpdates(): Promise<
|
|
567
|
+
Array<{ pluginId: string; scope: "user" | "project" | "local"; from: string; to: string }>
|
|
568
|
+
> {
|
|
552
569
|
const mktReg = await readMarketplacesRegistry(this.#opts.marketplacesRegistryPath);
|
|
553
|
-
const updates: Array<{ pluginId: string; scope: "user" | "project"; from: string; to: string }> = [];
|
|
570
|
+
const updates: Array<{ pluginId: string; scope: "user" | "project" | "local"; from: string; to: string }> = [];
|
|
554
571
|
|
|
555
572
|
// Keyed by (path, scope) so each scope is checked independently.
|
|
556
573
|
// A plugin current in user scope but stale in project scope must still appear.
|
|
557
|
-
const registryEntries: Array<[string, "user" | "project"]> = [
|
|
574
|
+
const registryEntries: Array<[string, "user" | "project" | "local"]> = [
|
|
575
|
+
[this.#opts.installedRegistryPath, "user"],
|
|
576
|
+
];
|
|
558
577
|
if (this.#opts.projectInstalledRegistryPath) {
|
|
559
578
|
registryEntries.push([this.#opts.projectInstalledRegistryPath, "project"]);
|
|
560
579
|
}
|
|
580
|
+
if (this.#opts.localInstalledRegistryPath) {
|
|
581
|
+
registryEntries.push([this.#opts.localInstalledRegistryPath, "local"]);
|
|
582
|
+
}
|
|
561
583
|
|
|
562
584
|
for (const [regPath, scope] of registryEntries) {
|
|
563
585
|
const instReg = await readInstalledPluginsRegistry(regPath);
|
|
@@ -598,7 +620,7 @@ export class MarketplaceManager {
|
|
|
598
620
|
}
|
|
599
621
|
|
|
600
622
|
// Re-install a specific plugin at the latest catalog version (force-overwrites).
|
|
601
|
-
async upgradePlugin(pluginId: string, scope?: "user" | "project"): Promise<InstalledPluginEntry> {
|
|
623
|
+
async upgradePlugin(pluginId: string, scope?: "user" | "project" | "local"): Promise<InstalledPluginEntry> {
|
|
602
624
|
const parsed = parsePluginId(pluginId);
|
|
603
625
|
if (!parsed) {
|
|
604
626
|
throw new Error(`Invalid plugin ID: "${pluginId}". Expected "name@marketplace".`);
|
|
@@ -613,7 +635,7 @@ export class MarketplaceManager {
|
|
|
613
635
|
throw new Error(`Plugin "${pluginId}" is not installed`);
|
|
614
636
|
}
|
|
615
637
|
|
|
616
|
-
let resolvedScope: "user" | "project";
|
|
638
|
+
let resolvedScope: "user" | "project" | "local";
|
|
617
639
|
if (inUser && inProject) {
|
|
618
640
|
if (!scope) {
|
|
619
641
|
throw new Error(
|
|
@@ -667,10 +689,10 @@ export class MarketplaceManager {
|
|
|
667
689
|
// Only stale scopes are touched; a current user install is not re-installed when only
|
|
668
690
|
// the project scope is stale. Per-entry failures are skipped — partial success is returned.
|
|
669
691
|
async upgradeAllPlugins(): Promise<
|
|
670
|
-
Array<{ pluginId: string; scope: "user" | "project"; from: string; to: string }>
|
|
692
|
+
Array<{ pluginId: string; scope: "user" | "project" | "local"; from: string; to: string }>
|
|
671
693
|
> {
|
|
672
694
|
const updates = await this.checkForUpdates();
|
|
673
|
-
const results: Array<{ pluginId: string; scope: "user" | "project"; from: string; to: string }> = [];
|
|
695
|
+
const results: Array<{ pluginId: string; scope: "user" | "project" | "local"; from: string; to: string }> = [];
|
|
674
696
|
for (const update of updates) {
|
|
675
697
|
try {
|
|
676
698
|
const entry = await this.upgradePlugin(update.pluginId, update.scope);
|
|
@@ -684,13 +706,19 @@ export class MarketplaceManager {
|
|
|
684
706
|
|
|
685
707
|
// ── Private helpers ───────────────────────────────────────────────────────
|
|
686
708
|
|
|
687
|
-
#registryPath(scope: "user" | "project"): string {
|
|
709
|
+
#registryPath(scope: "user" | "project" | "local"): string {
|
|
688
710
|
if (scope === "project") {
|
|
689
711
|
if (!this.#opts.projectInstalledRegistryPath) {
|
|
690
712
|
throw new Error("project-scoped install requires running inside a project directory");
|
|
691
713
|
}
|
|
692
714
|
return this.#opts.projectInstalledRegistryPath;
|
|
693
715
|
}
|
|
716
|
+
if (scope === "local") {
|
|
717
|
+
if (!this.#opts.localInstalledRegistryPath) {
|
|
718
|
+
throw new Error("local-scoped install requires running inside a project directory");
|
|
719
|
+
}
|
|
720
|
+
return this.#opts.localInstalledRegistryPath;
|
|
721
|
+
}
|
|
694
722
|
return this.#opts.installedRegistryPath;
|
|
695
723
|
}
|
|
696
724
|
|
|
@@ -163,7 +163,7 @@ export interface InstalledPluginsRegistry {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
export interface InstalledPluginEntry {
|
|
166
|
-
scope: "user" | "project";
|
|
166
|
+
scope: "user" | "project" | "local";
|
|
167
167
|
/** Absolute path to cached plugin directory. */
|
|
168
168
|
installPath: string;
|
|
169
169
|
version: string;
|
|
@@ -186,7 +186,7 @@ export interface InstalledPluginEntry {
|
|
|
186
186
|
*/
|
|
187
187
|
export interface InstalledPluginSummary {
|
|
188
188
|
id: string;
|
|
189
|
-
scope: "user" | "project";
|
|
189
|
+
scope: "user" | "project" | "local";
|
|
190
190
|
entries: InstalledPluginEntry[];
|
|
191
191
|
/** Set when a user-scoped plugin is overridden by a project-scoped install. */
|
|
192
192
|
shadowedBy?: "project";
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "19.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "19.7.0",
|
|
21
|
+
"commit": "dbd20dc81eb4648b7941336222fccc8630c1200d",
|
|
22
|
+
"shortCommit": "dbd20dc",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v19.
|
|
25
|
-
"commitDate": "2026-06-
|
|
26
|
-
"buildDate": "2026-06-
|
|
24
|
+
"tag": "v19.7.0",
|
|
25
|
+
"commitDate": "2026-06-04T19:13:51Z",
|
|
26
|
+
"buildDate": "2026-06-04T19:45:06.219Z",
|
|
27
27
|
"dirty": true,
|
|
28
28
|
"prNumber": "",
|
|
29
29
|
"repoUrl": "https://github.com/f5xc-salesdemos/xcsh",
|
|
30
30
|
"repoSlug": "f5xc-salesdemos/xcsh",
|
|
31
|
-
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/
|
|
32
|
-
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.
|
|
31
|
+
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/dbd20dc81eb4648b7941336222fccc8630c1200d",
|
|
32
|
+
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.7.0"
|
|
33
33
|
};
|
|
@@ -17,7 +17,7 @@ export interface PluginItem {
|
|
|
17
17
|
plugin: { name: string; version?: string; description?: string };
|
|
18
18
|
marketplace: string;
|
|
19
19
|
/** Scope of this entry. When set, appended to the label and forwarded to onSelect. */
|
|
20
|
-
scope?: "user" | "project";
|
|
20
|
+
scope?: "user" | "project" | "local";
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export class PluginSelectorComponent extends Container {
|