@franzzemen/brokenstock-shell 0.2.3 → 0.3.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.
|
@@ -2227,7 +2227,13 @@ class CommandService {
|
|
|
2227
2227
|
const lines = [
|
|
2228
2228
|
' clear — Clear terminal history',
|
|
2229
2229
|
' help — List available commands',
|
|
2230
|
-
|
|
2230
|
+
// Unavailable commands stay listed, annotated. Removing them would make
|
|
2231
|
+
// the CLI look broken rather than gated.
|
|
2232
|
+
...registered.map(c => {
|
|
2233
|
+
const availability = c.isAvailable?.();
|
|
2234
|
+
const suffix = availability && !availability.ok ? ` [unavailable — ${availability.reason}]` : '';
|
|
2235
|
+
return ` ${c.name} — ${c.description}${suffix}`;
|
|
2236
|
+
}),
|
|
2231
2237
|
];
|
|
2232
2238
|
return {
|
|
2233
2239
|
success: true,
|
|
@@ -2257,6 +2263,18 @@ class CommandService {
|
|
|
2257
2263
|
if (hasHelp) {
|
|
2258
2264
|
return { success: true, message: def.usage, timestamp: new Date(), isHelp: true };
|
|
2259
2265
|
}
|
|
2266
|
+
// Readiness gate. Deliberately ahead of argument validation: "you can't use
|
|
2267
|
+
// this yet, here's why" is more useful than a complaint about the arguments
|
|
2268
|
+
// of a command that wouldn't have run anyway.
|
|
2269
|
+
const availability = def.isAvailable?.();
|
|
2270
|
+
if (availability && !availability.ok) {
|
|
2271
|
+
return {
|
|
2272
|
+
success: false,
|
|
2273
|
+
message: availability.reason || `'${command}' is not available yet.`,
|
|
2274
|
+
severity: 'error',
|
|
2275
|
+
timestamp: new Date(),
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2260
2278
|
const validation = def.argsSchema.safeParse(args);
|
|
2261
2279
|
if (!validation.success) {
|
|
2262
2280
|
return { success: false, message: `Invalid arguments for '${command}': ${validation.error.issues[0].message}`, timestamp: new Date() };
|
|
@@ -2271,7 +2289,11 @@ class CommandService {
|
|
|
2271
2289
|
const def = this.registry.get(name);
|
|
2272
2290
|
if (!def)
|
|
2273
2291
|
return { success: false, message: `Unknown command: ${name}`, timestamp: new Date() };
|
|
2274
|
-
|
|
2292
|
+
// Usage is still shown for a gated command — knowing how it works is how a
|
|
2293
|
+
// user decides whether it's worth unlocking. The gate is noted, not enforced.
|
|
2294
|
+
const availability = def.isAvailable?.();
|
|
2295
|
+
const note = availability && !availability.ok ? `\n\nCurrently unavailable — ${availability.reason}` : '';
|
|
2296
|
+
return { success: true, message: def.usage + note, timestamp: new Date(), isHelp: true };
|
|
2275
2297
|
}
|
|
2276
2298
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: CommandService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2277
2299
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: CommandService, providedIn: 'root' }); }
|
|
@@ -2331,6 +2353,18 @@ class SideMenu {
|
|
|
2331
2353
|
#shortcutService;
|
|
2332
2354
|
#terminalVisibility;
|
|
2333
2355
|
#allMenuItems;
|
|
2356
|
+
/**
|
|
2357
|
+
* Readiness, not entitlement — an unentitled item never reaches here, having
|
|
2358
|
+
* been filtered out by `visible` above. Read from the template so the
|
|
2359
|
+
* predicate's signal reads are tracked and the strip regreys live when the
|
|
2360
|
+
* user's state changes.
|
|
2361
|
+
*/
|
|
2362
|
+
isEnabled(item) {
|
|
2363
|
+
return !item.enabled || item.enabled();
|
|
2364
|
+
}
|
|
2365
|
+
disabledReasonFor(item) {
|
|
2366
|
+
return item.disabledReason?.() ?? item.label;
|
|
2367
|
+
}
|
|
2334
2368
|
onMenuClick(item) {
|
|
2335
2369
|
if (item.command) {
|
|
2336
2370
|
this.#commandService.echoCommand(item.command);
|
|
@@ -2344,11 +2378,11 @@ class SideMenu {
|
|
|
2344
2378
|
this.#terminalVisibility.toggle();
|
|
2345
2379
|
}
|
|
2346
2380
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: SideMenu, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2347
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: SideMenu, isStandalone: true, selector: "shell-side-menu", ngImport: i0, template: "<nav class=\"side-menu\">\n @for (item of menuItems(); track item.route) {\n <a\n
|
|
2381
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: SideMenu, isStandalone: true, selector: "shell-side-menu", ngImport: i0, template: "<nav class=\"side-menu\">\n @for (item of menuItems(); track item.route) {\n @if (isEnabled(item)) {\n <a\n class=\"menu-item\"\n [routerLink]=\"item.route\"\n routerLinkActive=\"active\"\n [matTooltip]=\"item.label\"\n matTooltipPosition=\"right\"\n (click)=\"onMenuClick(item)\"\n >\n <mat-icon>{{ item.icon }}</mat-icon>\n </a>\n } @else {\n <!-- No routerLink: the item is inert. Pointer events stay live (see the\n .disabled rule) so the tooltip still fires \u2014 it carries the only\n explanation the user gets. -->\n <span\n class=\"menu-item disabled\"\n [matTooltip]=\"disabledReasonFor(item)\"\n matTooltipPosition=\"right\"\n aria-disabled=\"true\"\n >\n <mat-icon>{{ item.icon }}</mat-icon>\n </span>\n }\n }\n <div class=\"menu-spacer\"></div>\n <button\n class=\"menu-item terminal-toggle\"\n type=\"button\"\n [class.active]=\"terminalVisible()\"\n [matTooltip]=\"terminalVisible() ? 'Hide command console' : 'Show command console'\"\n matTooltipPosition=\"right\"\n [attr.aria-pressed]=\"terminalVisible()\"\n (click)=\"toggleTerminal()\"\n >\n <mat-icon>terminal</mat-icon>\n </button>\n @if (hasShortcuts()) {\n <button\n class=\"help-button\"\n matTooltip=\"Keyboard shortcuts\"\n matTooltipPosition=\"right\"\n (click)=\"toggleHelp()\"\n >\n <span class=\"help-modifier\">Alt+</span><span class=\"help-key\">?</span>\n </button>\n }\n</nav>\n", styles: [".side-menu{display:flex;flex-direction:column;align-items:center;padding-top:8px;gap:4px;height:100%;background-color:var(--color-surface);border-right:1px solid var(--color-surface-border)}.menu-item{display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:8px;color:var(--color-secondary-info);text-decoration:none;transition:background-color .15s,color .15s}.menu-item:hover{background-color:var(--color-surface-light);color:var(--color-interactive)}.menu-item.active,.menu-item.active:hover{background-color:var(--color-interactive, #00e5ff);color:var(--color-surface-dark, #0d1117)}.menu-item.disabled{opacity:.25;cursor:not-allowed}.menu-item.disabled:hover{background-color:transparent;color:var(--color-secondary-info)}.menu-spacer{flex:1}.menu-item.terminal-toggle{border:none;background:none;padding:0;cursor:pointer;margin-bottom:4px}.help-button{margin-bottom:8px;padding:4px 6px;border:1px solid var(--color-surface-border);border-radius:6px;background:none;font-size:.7rem;font-family:Roboto Mono,monospace;cursor:pointer;color:var(--color-interactive);transition:background-color .15s}.help-button:hover{background-color:var(--color-surface-light)}.help-modifier{opacity:.6}.help-key{font-weight:700}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.Eager }); }
|
|
2348
2382
|
}
|
|
2349
2383
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: SideMenu, decorators: [{
|
|
2350
2384
|
type: Component,
|
|
2351
|
-
args: [{ selector: 'shell-side-menu', imports: [RouterLink, RouterLinkActive, MatIconModule, MatTooltipModule], changeDetection: ChangeDetectionStrategy.Eager, template: "<nav class=\"side-menu\">\n @for (item of menuItems(); track item.route) {\n <a\n
|
|
2385
|
+
args: [{ selector: 'shell-side-menu', imports: [RouterLink, RouterLinkActive, MatIconModule, MatTooltipModule], changeDetection: ChangeDetectionStrategy.Eager, template: "<nav class=\"side-menu\">\n @for (item of menuItems(); track item.route) {\n @if (isEnabled(item)) {\n <a\n class=\"menu-item\"\n [routerLink]=\"item.route\"\n routerLinkActive=\"active\"\n [matTooltip]=\"item.label\"\n matTooltipPosition=\"right\"\n (click)=\"onMenuClick(item)\"\n >\n <mat-icon>{{ item.icon }}</mat-icon>\n </a>\n } @else {\n <!-- No routerLink: the item is inert. Pointer events stay live (see the\n .disabled rule) so the tooltip still fires \u2014 it carries the only\n explanation the user gets. -->\n <span\n class=\"menu-item disabled\"\n [matTooltip]=\"disabledReasonFor(item)\"\n matTooltipPosition=\"right\"\n aria-disabled=\"true\"\n >\n <mat-icon>{{ item.icon }}</mat-icon>\n </span>\n }\n }\n <div class=\"menu-spacer\"></div>\n <button\n class=\"menu-item terminal-toggle\"\n type=\"button\"\n [class.active]=\"terminalVisible()\"\n [matTooltip]=\"terminalVisible() ? 'Hide command console' : 'Show command console'\"\n matTooltipPosition=\"right\"\n [attr.aria-pressed]=\"terminalVisible()\"\n (click)=\"toggleTerminal()\"\n >\n <mat-icon>terminal</mat-icon>\n </button>\n @if (hasShortcuts()) {\n <button\n class=\"help-button\"\n matTooltip=\"Keyboard shortcuts\"\n matTooltipPosition=\"right\"\n (click)=\"toggleHelp()\"\n >\n <span class=\"help-modifier\">Alt+</span><span class=\"help-key\">?</span>\n </button>\n }\n</nav>\n", styles: [".side-menu{display:flex;flex-direction:column;align-items:center;padding-top:8px;gap:4px;height:100%;background-color:var(--color-surface);border-right:1px solid var(--color-surface-border)}.menu-item{display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:8px;color:var(--color-secondary-info);text-decoration:none;transition:background-color .15s,color .15s}.menu-item:hover{background-color:var(--color-surface-light);color:var(--color-interactive)}.menu-item.active,.menu-item.active:hover{background-color:var(--color-interactive, #00e5ff);color:var(--color-surface-dark, #0d1117)}.menu-item.disabled{opacity:.25;cursor:not-allowed}.menu-item.disabled:hover{background-color:transparent;color:var(--color-secondary-info)}.menu-spacer{flex:1}.menu-item.terminal-toggle{border:none;background:none;padding:0;cursor:pointer;margin-bottom:4px}.help-button{margin-bottom:8px;padding:4px 6px;border:1px solid var(--color-surface-border);border-radius:6px;background:none;font-size:.7rem;font-family:Roboto Mono,monospace;cursor:pointer;color:var(--color-interactive);transition:background-color .15s}.help-button:hover{background-color:var(--color-surface-light)}.help-modifier{opacity:.6}.help-key{font-weight:700}\n"] }]
|
|
2352
2386
|
}] });
|
|
2353
2387
|
|
|
2354
2388
|
class MainContent {
|