@franzzemen/brokenstock-shell 0.6.1 → 0.7.1

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.
@@ -32,6 +32,23 @@ import { MatCheckboxModule } from '@angular/material/checkbox';
32
32
  import * as i1$2 from '@angular/material/dialog';
33
33
  import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angular/material/dialog';
34
34
 
35
+ /**
36
+ * Does `url` fall inside any of `patterns`? Query string and fragment are
37
+ * stripped; a pattern matches when the path equals it or continues past it at
38
+ * a `/`; a `:param` segment matches any one non-empty segment. Same rule as
39
+ * the lexicon's `matchesRoute`, tested there and here.
40
+ */
41
+ function matchesRoute(url, patterns) {
42
+ const path = url.split('?')[0].split('#')[0].replace(/\/+$/, '') || '/';
43
+ const segments = path.split('/');
44
+ return patterns.some(pattern => {
45
+ const want = pattern.replace(/\/+$/, '').split('/');
46
+ if (want.length > segments.length)
47
+ return false;
48
+ return want.every((w, i) => (w.startsWith(':') ? segments[i].length > 0 : w === segments[i]));
49
+ });
50
+ }
51
+
35
52
  const SHELL_APP_NAME = new InjectionToken('SHELL_APP_NAME');
36
53
  const SHELL_MENU_ITEMS = new InjectionToken('SHELL_MENU_ITEMS');
37
54
  const SHELL_AUTH_URLS = new InjectionToken('SHELL_AUTH_URLS');
@@ -2557,6 +2574,7 @@ class FaqCommand {
2557
2574
  this.description = 'Open the in-app FAQ help system';
2558
2575
  this.usage = FAQ_USAGE;
2559
2576
  this.requiresAuth = false;
2577
+ this.scope = 'global';
2560
2578
  this.argsSchema = z.array(z.string()).max(50);
2561
2579
  }
2562
2580
  execute(args) {
@@ -2716,6 +2734,7 @@ class CommandService {
2716
2734
  ...(ngDevMode ? [{ debugName: "helpOverlay" }] : /* istanbul ignore next */ []));
2717
2735
  this.authService = inject(AuthenticationService);
2718
2736
  this.registry = inject(CommandRegistryService);
2737
+ this.router = inject(Router);
2719
2738
  this.lastResult = computed(() => {
2720
2739
  const h = this.history();
2721
2740
  return h.length > 0 ? h[h.length - 1].result : null;
@@ -2845,9 +2864,12 @@ class CommandService {
2845
2864
  ' help — List available commands',
2846
2865
  // Unavailable commands stay listed, annotated. Removing them would make
2847
2866
  // the CLI look broken rather than gated.
2867
+ // A command scoped to another screen stays listed too, with its screen
2868
+ // named rather than hidden (trades-tree.prd.md D31): hiding it would
2869
+ // read as a broken CLI.
2848
2870
  ...registered.map(c => {
2849
2871
  const availability = c.isAvailable?.();
2850
- const suffix = availability && !availability.ok ? ` [unavailable — ${availability.reason}]` : '';
2872
+ const suffix = availability && !availability.ok ? ` [unavailable — ${availability.reason}]` : this.#scopeSuffix(c.scope);
2851
2873
  return ` ${c.name} — ${c.description}${suffix}`;
2852
2874
  }),
2853
2875
  ];
@@ -2894,7 +2916,23 @@ class CommandService {
2894
2916
  return notLoggedIn();
2895
2917
  }
2896
2918
  if (hasHelp) {
2897
- return { success: true, message: def.usage, timestamp: new Date(), isHelp: true };
2919
+ // `help <command>` is normalised to `<command> help` above, so this is
2920
+ // the one place a command's usage is shown; the screen it works on goes
2921
+ // here (D31).
2922
+ return { success: true, message: def.usage + this.#whereNote(def.scope), timestamp: new Date(), isHelp: true };
2923
+ }
2924
+ // Screen gate (trades-tree.prd.md D30). A command bound to state only one
2925
+ // screen sets is refused off that screen with the screen named, the way
2926
+ // `tab` always did on its own. A command that navigates to its screen is
2927
+ // never refused here — declaring its routes is what lets `help` say where
2928
+ // it lives, and taking the user there is the handler's job (D32).
2929
+ if (def.scope !== 'global' && def.scope.elsewhere === 'refuse' && !matchesRoute(this.router.url, def.scope.routes)) {
2930
+ return {
2931
+ success: false,
2932
+ message: `'${command}' works on ${def.scope.screen}.`,
2933
+ severity: 'error',
2934
+ timestamp: new Date(),
2935
+ };
2898
2936
  }
2899
2937
  // Readiness gate. Deliberately ahead of argument validation: "you can't use
2900
2938
  // this yet, here's why" is more useful than a complaint about the arguments
@@ -2926,7 +2964,17 @@ class CommandService {
2926
2964
  // user decides whether it's worth unlocking. The gate is noted, not enforced.
2927
2965
  const availability = def.isAvailable?.();
2928
2966
  const note = availability && !availability.ok ? `\n\nCurrently unavailable — ${availability.reason}` : '';
2929
- return { success: true, message: def.usage + note, timestamp: new Date(), isHelp: true };
2967
+ return { success: true, message: def.usage + this.#whereNote(def.scope) + note, timestamp: new Date(), isHelp: true };
2968
+ }
2969
+ /** "Works on the Trades screen." — appended to a scoped command's usage. */
2970
+ #whereNote(scope) {
2971
+ return scope === 'global' ? '' : `\n\nWorks on ${scope.screen}.`;
2972
+ }
2973
+ /** `help`'s annotation for a command scoped to a screen other than the current one. */
2974
+ #scopeSuffix(scope) {
2975
+ if (scope === 'global' || matchesRoute(this.router.url, scope.routes))
2976
+ return '';
2977
+ return ` [${scope.screen}]`;
2930
2978
  }
2931
2979
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: CommandService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2932
2980
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: CommandService, providedIn: 'root' }); }
@@ -3463,5 +3511,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
3463
3511
  * Generated bundle index. Do not edit.
3464
3512
  */
3465
3513
 
3466
- export { AuthenticationService, CLEAR_USAGE, CommandRegistryService, CommandService, CommandTerminal, ConfirmDialog, FAQ_CONTENT_LOADER, FAQ_DEV_EDITOR, FAQ_DEV_MODE, FAQ_DEV_WORKSPACE_ROOT, FAQ_PUBLIC_PUBLISHER, FAQ_PUBLIC_SITE_BASE_URL, FaqAuthoringPanel, FaqHistoryService, FaqMarkdownRendererService, FaqModal, FaqService, HELP_USAGE, HttpFaqContentLoader, KeyboardShortcutService, Login, MainContent, PublicFaqIndex, PublicFaqPage, PublicFaqToc, RegexValidatorDirective, SESSION_TOKEN_KEY, SHELL_APP_NAME, SHELL_AUTH_URLS, SHELL_COMMANDS, SHELL_MENU_ITEMS, SHELL_POST_LOGIN_ROUTE, SHELL_SHARED_SESSION, SessionTokenStore, Shell, SideMenu, StatusBar, TerminalFocusService, TerminalVisibilityService, ThemeService, ThinkingService, audienceAllowed, authGuard, authInterceptor, buildFallbackChain, decideRenewal, guestGuard, provideShellConfig, tokenize };
3514
+ export { AuthenticationService, CLEAR_USAGE, CommandRegistryService, CommandService, CommandTerminal, ConfirmDialog, FAQ_CONTENT_LOADER, FAQ_DEV_EDITOR, FAQ_DEV_MODE, FAQ_DEV_WORKSPACE_ROOT, FAQ_PUBLIC_PUBLISHER, FAQ_PUBLIC_SITE_BASE_URL, FaqAuthoringPanel, FaqHistoryService, FaqMarkdownRendererService, FaqModal, FaqService, HELP_USAGE, HttpFaqContentLoader, KeyboardShortcutService, Login, MainContent, PublicFaqIndex, PublicFaqPage, PublicFaqToc, RegexValidatorDirective, SESSION_TOKEN_KEY, SHELL_APP_NAME, SHELL_AUTH_URLS, SHELL_COMMANDS, SHELL_MENU_ITEMS, SHELL_POST_LOGIN_ROUTE, SHELL_SHARED_SESSION, SessionTokenStore, Shell, SideMenu, StatusBar, TerminalFocusService, TerminalVisibilityService, ThemeService, ThinkingService, audienceAllowed, authGuard, authInterceptor, buildFallbackChain, decideRenewal, guestGuard, matchesRoute, provideShellConfig, tokenize };
3467
3515
  //# sourceMappingURL=franzzemen-brokenstock-shell.mjs.map