@memberjunction/cli-core 0.0.1 → 5.42.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 CHANGED
@@ -1,45 +1,138 @@
1
1
  # @memberjunction/cli-core
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
4
-
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
6
-
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
8
-
9
- ## Purpose
10
-
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@memberjunction/cli-core`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
15
-
16
- ## What is OIDC Trusted Publishing?
17
-
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
19
-
20
- ## Setup Instructions
21
-
22
- To properly configure OIDC trusted publishing for this package:
23
-
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
28
-
29
- ## DO NOT USE THIS PACKAGE
30
-
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
36
-
37
- ## More Information
38
-
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
42
-
43
- ---
44
-
45
- **Maintained for OIDC setup purposes only**
3
+ Pluggable core for the MemberJunction `mj` CLI. Provides the primitives that let
4
+ commands be **machine-readable**, **discoverable**, and **pluggable** — so AI
5
+ coding agents (Claude Code, OpenCode, …) can drive `mj` reliably, and third
6
+ parties can add commands without modifying MJCLI.
7
+
8
+ ## Why
9
+
10
+ `mj codegen`, `mj sync push/pull`, etc. are run constantly by humans *and* AI
11
+ agents. Humans get clean spinners and a summary box. Agents get four problems:
12
+
13
+ 1. **No machine-readable output** parsing chalk + box-drawing is fragile.
14
+ 2. **Errors interleaved mid-run** no collected error list.
15
+ 3. **The banner burns context** — hundreds of captured chars before content.
16
+ 4. **No runtime signal** agents pick a timeout and kill healthy long-running
17
+ commands (`codegen`, `migrate`) midway.
18
+
19
+ This package fixes all four with one architecture, and makes the CLI open to
20
+ third-party plugins as a side effect.
21
+
22
+ ## Architecture
23
+
24
+ > **Principle:** plugins return *data* (`MJCLIResult`); the host *renders* it.
25
+ > No `ora`, `chalk`, or `console` inside a plugin's `Execute()`.
26
+
27
+ ```
28
+ mj (oclif root)
29
+ ├─ loads mj-cli-plugins.json @RegisterClass populates ClassFactory
30
+ ├─ composes `mj usage` / `mj <domain> usage` from each plugin's static Usage
31
+ └─ routes a command BaseCLIPlugin subclass
32
+ │ injects MJCLIRuntimeHost (per --format)
33
+
34
+ BaseCLIPlugin MJCLIRuntimeHost
35
+ - abstract Execute(): MJCLIResult - text: ora spinner + chalk
36
+ - this.Host.StartStep / Log / … - json: events→stderr, result→stdout
37
+ - declares static Usage - md: fenced ```json block
38
+ ```
39
+
40
+ ## Output formats (`--format`)
41
+
42
+ | Format | Result destination | Decorative output | Use |
43
+ |---|---|---|---|
44
+ | `text` (default) | — (plugin renders) | stdout (spinners/chalk) | humans |
45
+ | `json` | **stdout** (clean JSON) | **stderr** (`{event:…}`) | agents, `\| jq` |
46
+ | `md` | stdout (fenced block) | stderr | AI chat UIs |
47
+
48
+ `--verbose` / `--no-banner` are inherited by every plugin too.
49
+
50
+ ## Progressive-disclosure usage (for agents)
51
+
52
+ ```bash
53
+ mj usage --format=json | jq '.data.domains[].domain' # tier 1 (~200 tokens)
54
+ mj sync usage --format=json | jq '.data.commands[].runtime' # tier 2 (one domain)
55
+ mj sync push --help # tier 3 (full oclif help)
56
+ ```
57
+
58
+ Tier 1 returns a domain map; tier 2 returns one domain's commands, flags,
59
+ examples, and **runtime hints**. The guidance string tells the agent to check
60
+ usage rather than guess flags. An agent discovers the surface in ~500–700 tokens
61
+ instead of pulling the whole ~13k command tree.
62
+
63
+ ## Runtime hints / timeouts
64
+
65
+ Every plugin declares a `RuntimeHint` (`fast` <5s · `moderate` 5–60s · `slow`
66
+ >60s · `variable`). It's surfaced two ways so agents budget timeouts:
67
+
68
+ 1. In usage output (read before invoking).
69
+ 2. As a pre-execution advisory — `{event:'start', command, runtime}` on stderr
70
+ (JSON mode) or a dim note (text). Suppressed for `fast` commands.
71
+
72
+ ```bash
73
+ timeout_s=$(mj codegen usage --format=json | jq '.data.commands[0].runtime.typicalSeconds')
74
+ ```
75
+
76
+ ## Authoring a plugin
77
+
78
+ 1. Create a package that depends on `@memberjunction/cli-core`.
79
+ 2. Subclass `BaseCLIPlugin`, register it, declare flags + `static Usage`, and
80
+ implement `Execute()`:
81
+
82
+ ```typescript
83
+ import { Flags } from '@oclif/core';
84
+ import { RegisterClass } from '@memberjunction/global';
85
+ import { BaseCLIPlugin, type MJCLIResult, type PluginUsage } from '@memberjunction/cli-core';
86
+
87
+ @RegisterClass(BaseCLIPlugin, 'widgets:build')
88
+ export class WidgetsBuildPlugin extends BaseCLIPlugin {
89
+ static description = 'Build widgets';
90
+ static flags = { dir: Flags.string({ description: 'Source directory' }) };
91
+
92
+ static Usage: PluginUsage = {
93
+ domain: 'widgets',
94
+ command: 'widgets:build',
95
+ summary: 'Compile widgets from source.',
96
+ flags: [{ name: '--dir', type: 'string', description: 'Source directory' }],
97
+ examples: ['mj widgets build --dir=src', 'mj widgets build --format=json'],
98
+ runtime: { class: 'moderate', typicalSeconds: 20, note: 'scales with widget count' },
99
+ };
100
+
101
+ protected async Execute(): Promise<MJCLIResult> {
102
+ const start = Date.now();
103
+ const { flags } = await this.parse(WidgetsBuildPlugin);
104
+ this.Host.StartStep('Building widgets');
105
+ // … do work; collect errors instead of throwing …
106
+ this.Host.SucceedStep('Built widgets');
107
+ if (this.Host.Format === 'text') this.Host.Log('Done.'); // human-only rich text
108
+ return { success: true, command: 'widgets:build', durationSeconds: (Date.now() - start) / 1000, data: { /* … */ } };
109
+ }
110
+
111
+ // Optional: release resources / force-exit to kill lingering handles.
112
+ protected async Cleanup(result: MJCLIResult): Promise<void> { /* close pools */ }
113
+ }
114
+ ```
115
+
116
+ 3. Ship the plugin behind a **light subpath** (e.g. `@my-org/pkg/plugins`) that
117
+ static-imports only `cli-core` + light deps and dynamic-imports any heavy
118
+ engine inside `Execute()`. This keeps oclif manifest generation and `mj usage`
119
+ enumeration cheap.
120
+
121
+ 4. Register it so `mj` loads it:
122
+
123
+ ```bash
124
+ mj plugin add @my-org/pkg/plugins
125
+ # appends to mj-cli-plugins.json — no MJCLI change needed
126
+ ```
127
+
128
+ In the MemberJunction monorepo, MJCLI also keeps a one-line oclif shim under
129
+ `src/commands/` (`export { WidgetsBuildPlugin as default } from '…'`) so oclif
130
+ discovers and routes the command. The plugin's logic stays in its home package.
131
+
132
+ ## Exports
133
+
134
+ - `BaseCLIPlugin` — abstract base (extends oclif `Command`).
135
+ - `MJCLIRuntimeHost` / `IMJCLIRuntimeHost` — the stdio host.
136
+ - `CLIPluginRegistry` — loads `mj-cli-plugins.json`, composes tier-1/tier-2 usage.
137
+ - Types: `MJCLIResult`, `OutputFormat`, `PluginUsage`, `RuntimeHint`, …
138
+ - `PLUGIN_CONFIG_FILENAME` — `"mj-cli-plugins.json"`.
@@ -0,0 +1,56 @@
1
+ import { Command } from '@oclif/core';
2
+ import type { IMJCLIRuntimeHost, MJCLIResult, PluginUsage } from './types.js';
3
+ /**
4
+ * Abstract base for every pluggable `mj` command (plan D1/D2).
5
+ *
6
+ * Extends oclif's {@link Command}, so oclif still owns flag parsing, help
7
+ * generation, and routing. We wrap only the *execution* layer: subclasses
8
+ * implement {@link BaseCLIPlugin.Execute} (pure logic, returns data) and the
9
+ * shared {@link BaseCLIPlugin.run} wires up the {@link IMJCLIRuntimeHost}, emits
10
+ * the runtime advisory, renders the result per `--format`, and sets the exit code.
11
+ *
12
+ * The global flags `--format`, `--verbose`, and `--no-banner` are declared on
13
+ * {@link BaseCLIPlugin.baseFlags} and inherited by every subclass via oclif's
14
+ * native `baseFlags` merging — no per-command duplication (plan D3).
15
+ */
16
+ export declare abstract class BaseCLIPlugin extends Command {
17
+ /** Inherited by every subclass through oclif's static `baseFlags` mechanism. */
18
+ static baseFlags: {
19
+ format: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
20
+ verbose: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
21
+ 'no-banner': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
22
+ };
23
+ /**
24
+ * Every plugin declares its own usage + runtime metadata. The CLI root reads
25
+ * this off the registered classes to assemble the progressive-disclosure
26
+ * `mj usage` / `mj <domain> usage` surface and the timeout advisory.
27
+ * Subclasses MUST override.
28
+ */
29
+ static Usage: PluginUsage;
30
+ protected Host: IMJCLIRuntimeHost;
31
+ /** Parsed flags, captured once in {@link run}; read via {@link GetFlags}. */
32
+ private parsedFlags;
33
+ /**
34
+ * The flags parsed for this command. Subclasses call this in {@link Execute}
35
+ * instead of re-parsing — the parse happens once, in {@link run}.
36
+ *
37
+ * The `as unknown as T` is the ONE place the cross-package `@oclif/core` copy
38
+ * split is bridged: cli-core nests its own oclif, so the inferred parse type
39
+ * isn't nameable from a strict consumer package (TS2742). Confining the cast
40
+ * here keeps every plugin's `Execute()` cast-free. Pass
41
+ * `Interfaces.InferredFlags<typeof YourPlugin.flags>` as `T`.
42
+ */
43
+ protected GetFlags<T>(): T;
44
+ /**
45
+ * oclif entry point — do NOT override in subclasses. Override {@link Execute}.
46
+ */
47
+ run(): Promise<void>;
48
+ /**
49
+ * Optional post-Emit hook. Override to release resources (DB pools, singletons)
50
+ * and, when necessary, `process.exit()` to terminate lingering background work.
51
+ */
52
+ protected Cleanup(_result: MJCLIResult): Promise<void>;
53
+ /** Subclasses implement this — pure logic, no direct stdio. */
54
+ protected abstract Execute(): Promise<MJCLIResult>;
55
+ }
56
+ //# sourceMappingURL=base-cli-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-cli-plugin.d.ts","sourceRoot":"","sources":["../src/base-cli-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAC;AAE7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAgB,WAAW,EAAE,MAAM,SAAS,CAAC;AAEzF;;;;;;;;;;;;GAYG;AACH,8BAAsB,aAAc,SAAQ,OAAO;IACjD,gFAAgF;IAChF,OAAgB,SAAS;;;;MAQvB;IAEF;;;;;OAKG;IACH,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC;IAE1B,SAAS,CAAC,IAAI,EAAG,iBAAiB,CAAC;IAEnC,6EAA6E;IAC7E,OAAO,CAAC,WAAW,CAAU;IAE7B;;;;;;;;;OASG;IACH,SAAS,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC;IAI1B;;OAEG;IACG,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAkC1B;;;OAGG;cACa,OAAO,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5D,+DAA+D;IAC/D,SAAS,CAAC,QAAQ,CAAC,OAAO,IAAI,OAAO,CAAC,WAAW,CAAC;CACnD"}
@@ -0,0 +1,78 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { MJCLIRuntimeHost } from './runtime-host.js';
3
+ /**
4
+ * Abstract base for every pluggable `mj` command (plan D1/D2).
5
+ *
6
+ * Extends oclif's {@link Command}, so oclif still owns flag parsing, help
7
+ * generation, and routing. We wrap only the *execution* layer: subclasses
8
+ * implement {@link BaseCLIPlugin.Execute} (pure logic, returns data) and the
9
+ * shared {@link BaseCLIPlugin.run} wires up the {@link IMJCLIRuntimeHost}, emits
10
+ * the runtime advisory, renders the result per `--format`, and sets the exit code.
11
+ *
12
+ * The global flags `--format`, `--verbose`, and `--no-banner` are declared on
13
+ * {@link BaseCLIPlugin.baseFlags} and inherited by every subclass via oclif's
14
+ * native `baseFlags` merging — no per-command duplication (plan D3).
15
+ */
16
+ export class BaseCLIPlugin extends Command {
17
+ /** Inherited by every subclass through oclif's static `baseFlags` mechanism. */
18
+ static { this.baseFlags = {
19
+ format: Flags.string({
20
+ options: ['text', 'json', 'md'],
21
+ default: 'text',
22
+ description: 'Output format: text (human), json (machine-readable), md (Markdown-fenced)',
23
+ }),
24
+ verbose: Flags.boolean({ char: 'v', default: false, description: 'Show detailed output' }),
25
+ 'no-banner': Flags.boolean({ default: false, description: 'Suppress the startup banner and runtime advisory' }),
26
+ }; }
27
+ /**
28
+ * The flags parsed for this command. Subclasses call this in {@link Execute}
29
+ * instead of re-parsing — the parse happens once, in {@link run}.
30
+ *
31
+ * The `as unknown as T` is the ONE place the cross-package `@oclif/core` copy
32
+ * split is bridged: cli-core nests its own oclif, so the inferred parse type
33
+ * isn't nameable from a strict consumer package (TS2742). Confining the cast
34
+ * here keeps every plugin's `Execute()` cast-free. Pass
35
+ * `Interfaces.InferredFlags<typeof YourPlugin.flags>` as `T`.
36
+ */
37
+ GetFlags() {
38
+ return this.parsedFlags;
39
+ }
40
+ /**
41
+ * oclif entry point — do NOT override in subclasses. Override {@link Execute}.
42
+ */
43
+ async run() {
44
+ // Parse against the concrete subclass so `baseFlags` + the subclass `flags`
45
+ // both resolve. `this.constructor` is the concrete command class at runtime.
46
+ const ctor = this.constructor;
47
+ const { flags } = await this.parse(ctor);
48
+ this.parsedFlags = flags;
49
+ const f = flags;
50
+ const format = f.format ?? 'text';
51
+ const verbose = !!f.verbose;
52
+ const noBanner = !!f['no-banner'];
53
+ this.Host = new MJCLIRuntimeHost(format, verbose, noBanner);
54
+ // Announce runtime expectation up front (stderr in JSON mode) so an agent
55
+ // reading the stream can budget its timeout — see plan §5/§6.
56
+ if (ctor.Usage) {
57
+ this.Host.AnnounceRuntime(ctor.Usage);
58
+ }
59
+ const result = await this.Execute();
60
+ this.Host.Emit(result);
61
+ // Optional cleanup hook (e.g. close DB pools, reset singletons). Runs after
62
+ // Emit so the result is always rendered even when cleanup hard-exits.
63
+ await this.Cleanup(result);
64
+ // Default exit handling. Plugins that must force-exit to kill lingering
65
+ // handles (e.g. embedding workers) do so inside Cleanup().
66
+ if (!result.success) {
67
+ this.exit(1);
68
+ }
69
+ }
70
+ /**
71
+ * Optional post-Emit hook. Override to release resources (DB pools, singletons)
72
+ * and, when necessary, `process.exit()` to terminate lingering background work.
73
+ */
74
+ async Cleanup(_result) {
75
+ // no-op by default
76
+ }
77
+ }
78
+ //# sourceMappingURL=base-cli-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-cli-plugin.js","sourceRoot":"","sources":["../src/base-cli-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGlD;;;;;;;;;;;;GAYG;AACH,MAAM,OAAgB,aAAc,SAAQ,OAAO;IACjD,gFAAgF;aAChE,cAAS,GAAG;QAC1B,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC;YACnB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC;YAC/B,OAAO,EAAE,MAAM;YACf,WAAW,EAAE,4EAA4E;SAC1F,CAAC;QACF,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,sBAAsB,EAAE,CAAC;QAC1F,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,kDAAkD,EAAE,CAAC;KAChH,CAAC;IAeF;;;;;;;;;OASG;IACO,QAAQ;QAChB,OAAO,IAAI,CAAC,WAA2B,CAAC;IAC1C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,GAAG;QACP,4EAA4E;QAC5E,6EAA6E;QAC7E,MAAM,IAAI,GAAG,IAAI,CAAC,WAAmC,CAAC;QACtD,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;QAEzB,MAAM,CAAC,GAAG,KAAsE,CAAC;QACjF,MAAM,MAAM,GAAI,CAAC,CAAC,MAAuB,IAAI,MAAM,CAAC;QACpD,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;QAC5B,MAAM,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;QAElC,IAAI,CAAC,IAAI,GAAG,IAAI,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QAE5D,0EAA0E;QAC1E,8DAA8D;QAC9D,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxC,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACpC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAEvB,4EAA4E;QAC5E,sEAAsE;QACtE,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAE3B,wEAAwE;QACxE,2DAA2D;QAC3D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACf,CAAC;IACH,CAAC;IAED;;;OAGG;IACO,KAAK,CAAC,OAAO,CAAC,OAAoB;QAC1C,mBAAmB;IACrB,CAAC"}
@@ -0,0 +1,7 @@
1
+ export * from './types.js';
2
+ export { MJCLIRuntimeHost } from './runtime-host.js';
3
+ export { BaseCLIPlugin } from './base-cli-plugin.js';
4
+ export { SerializeResult } from './serialize.js';
5
+ export { CLIPluginRegistry, PLUGIN_CONFIG_FILENAME, } from './plugin-registry.js';
6
+ export type { UsageDomainSummary, UsageDomainMap, UsageDomainDetail, PluginLoadResult, } from './plugin-registry.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from './types.js';
2
+ export { MJCLIRuntimeHost } from './runtime-host.js';
3
+ export { BaseCLIPlugin } from './base-cli-plugin.js';
4
+ export { SerializeResult } from './serialize.js';
5
+ export { CLIPluginRegistry, PLUGIN_CONFIG_FILENAME, } from './plugin-registry.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,72 @@
1
+ import type { MJCLIResult, PluginUsage, RuntimeHint } from './types.js';
2
+ /** Filename enumerating active plugin entry points (plan §4 / D8). */
3
+ export declare const PLUGIN_CONFIG_FILENAME = "mj-cli-plugins.json";
4
+ /** Outcome of {@link CLIPluginRegistry.LoadPluginsFromConfig}. */
5
+ export interface PluginLoadResult {
6
+ /** Specifiers that imported successfully. */
7
+ loaded: string[];
8
+ /** Specifiers that failed to import, with the error, so callers can log them. */
9
+ failed: Array<{
10
+ specifier: string;
11
+ error: string;
12
+ }>;
13
+ }
14
+ /** Tier-1 domain map entry. */
15
+ export interface UsageDomainSummary {
16
+ domain: string;
17
+ summary: string;
18
+ runtime: RuntimeHint['class'];
19
+ }
20
+ /** Tier-1 result payload. */
21
+ export interface UsageDomainMap {
22
+ guidance: string;
23
+ domains: UsageDomainSummary[];
24
+ }
25
+ /** Tier-2 result payload (one domain's commands). */
26
+ export interface UsageDomainDetail {
27
+ domain: string;
28
+ commands: PluginUsage[];
29
+ }
30
+ /**
31
+ * Loads CLI plugin packages and composes the progressive-disclosure usage surface
32
+ * (`mj usage` → `mj <domain> usage`) dynamically from each registered plugin's
33
+ * `static Usage` (plan §5, the linearis model). There is no central hardcoded
34
+ * help file — usage is whatever plugins are loaded, including third-party ones.
35
+ */
36
+ export declare class CLIPluginRegistry {
37
+ /**
38
+ * Walks up from {@link startDir} looking for `mj-cli-plugins.json`, then
39
+ * dynamic-imports each listed entry point so its `@RegisterClass(BaseCLIPlugin, …)`
40
+ * decorators populate the ClassFactory. Safe to call repeatedly — imports are
41
+ * cached by the module loader. Returns the list of specifiers it loaded.
42
+ */
43
+ static LoadPluginsFromConfig(startDir?: string): Promise<PluginLoadResult>;
44
+ /**
45
+ * Resolves a plugin entry point. Package specifiers (`@scope/pkg`,
46
+ * `@scope/pkg/plugins`) import as-is; relative paths resolve against the config
47
+ * file's directory and convert to a file URL for ESM import.
48
+ */
49
+ private static importSpecifier;
50
+ /** First `mj-cli-plugins.json` found walking up from {@link startDir}. */
51
+ private static findConfig;
52
+ /** Every registered plugin's usage metadata, de-duplicated by command key. */
53
+ static GetAllUsage(): PluginUsage[];
54
+ /** Tier-1: the domain map — each domain + one-line summary + runtime class. */
55
+ static BuildDomainMap(): UsageDomainMap;
56
+ /** Tier-2: every command in {@link domain} with summary, flags, examples, runtime. */
57
+ static BuildDomainDetail(domain: string): UsageDomainDetail;
58
+ /** Wraps a tier payload in the universal {@link MJCLIResult} shape. */
59
+ static AsResult(command: string, data: Record<string, unknown>): MJCLIResult;
60
+ /**
61
+ * A domain summary: if a single command's summary best represents the domain
62
+ * use it; otherwise join the command summaries compactly.
63
+ */
64
+ private static domainSummary;
65
+ /**
66
+ * The "loosest" runtime class across a domain's commands, so an agent setting a
67
+ * single domain-wide timeout errs on the generous side.
68
+ * Ordering: variable > slow > moderate > fast.
69
+ */
70
+ private static domainRuntimeClass;
71
+ }
72
+ //# sourceMappingURL=plugin-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-registry.d.ts","sourceRoot":"","sources":["../src/plugin-registry.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAErE,sEAAsE;AACtE,eAAO,MAAM,sBAAsB,wBAAwB,CAAC;AAM5D,kEAAkE;AAClE,MAAM,WAAW,gBAAgB;IAC/B,6CAA6C;IAC7C,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,iFAAiF;IACjF,MAAM,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrD;AAED,+BAA+B;AAC/B,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;CAC/B;AAED,6BAA6B;AAC7B,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,kBAAkB,EAAE,CAAC;CAC/B;AAED,qDAAqD;AACrD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB;AAID;;;;;GAKG;AACH,qBAAa,iBAAiB;IAC5B;;;;;OAKG;WACU,qBAAqB,CAAC,QAAQ,GAAE,MAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA+B/F;;;;OAIG;mBACkB,eAAe;IASpC,0EAA0E;IAC1E,OAAO,CAAC,MAAM,CAAC,UAAU;IAYzB,8EAA8E;IAC9E,MAAM,CAAC,WAAW,IAAI,WAAW,EAAE;IAYnC,+EAA+E;IAC/E,MAAM,CAAC,cAAc,IAAI,cAAc;IAoBvC,sFAAsF;IACtF,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB;IAQ3D,uEAAuE;IACvE,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW;IAI5E;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAU5B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;CAQlC"}
@@ -0,0 +1,152 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { dirname, isAbsolute, resolve } from 'path';
3
+ import { pathToFileURL } from 'url';
4
+ import { MJGlobal } from '@memberjunction/global';
5
+ import { BaseCLIPlugin } from './base-cli-plugin.js';
6
+ /** Filename enumerating active plugin entry points (plan §4 / D8). */
7
+ export const PLUGIN_CONFIG_FILENAME = 'mj-cli-plugins.json';
8
+ const GUIDANCE = 'Run `mj <domain> usage` before invoking. Do NOT guess flags or subcommands.';
9
+ /**
10
+ * Loads CLI plugin packages and composes the progressive-disclosure usage surface
11
+ * (`mj usage` → `mj <domain> usage`) dynamically from each registered plugin's
12
+ * `static Usage` (plan §5, the linearis model). There is no central hardcoded
13
+ * help file — usage is whatever plugins are loaded, including third-party ones.
14
+ */
15
+ export class CLIPluginRegistry {
16
+ /**
17
+ * Walks up from {@link startDir} looking for `mj-cli-plugins.json`, then
18
+ * dynamic-imports each listed entry point so its `@RegisterClass(BaseCLIPlugin, …)`
19
+ * decorators populate the ClassFactory. Safe to call repeatedly — imports are
20
+ * cached by the module loader. Returns the list of specifiers it loaded.
21
+ */
22
+ static async LoadPluginsFromConfig(startDir = process.cwd()) {
23
+ const result = { loaded: [], failed: [] };
24
+ const configPath = this.findConfig(startDir);
25
+ if (!configPath)
26
+ return result;
27
+ let parsed;
28
+ try {
29
+ parsed = JSON.parse(readFileSync(configPath, 'utf-8'));
30
+ }
31
+ catch (e) {
32
+ result.failed.push({ specifier: configPath, error: `Invalid ${PLUGIN_CONFIG_FILENAME}: ${e instanceof Error ? e.message : String(e)}` });
33
+ return result;
34
+ }
35
+ const specifiers = Array.isArray(parsed.plugins) ? parsed.plugins : [];
36
+ const configDir = dirname(configPath);
37
+ for (const specifier of specifiers) {
38
+ try {
39
+ await this.importSpecifier(specifier, configDir);
40
+ result.loaded.push(specifier);
41
+ }
42
+ catch (e) {
43
+ // A broken/optional plugin must not take down the whole CLI; record the
44
+ // failure so the caller can surface it under --verbose rather than
45
+ // swallowing it silently.
46
+ result.failed.push({ specifier, error: e instanceof Error ? e.message : String(e) });
47
+ }
48
+ }
49
+ return result;
50
+ }
51
+ /**
52
+ * Resolves a plugin entry point. Package specifiers (`@scope/pkg`,
53
+ * `@scope/pkg/plugins`) import as-is; relative paths resolve against the config
54
+ * file's directory and convert to a file URL for ESM import.
55
+ */
56
+ static async importSpecifier(specifier, configDir) {
57
+ if (specifier.startsWith('.') || isAbsolute(specifier)) {
58
+ const abs = isAbsolute(specifier) ? specifier : resolve(configDir, specifier);
59
+ await import(pathToFileURL(abs).href);
60
+ }
61
+ else {
62
+ await import(specifier);
63
+ }
64
+ }
65
+ /** First `mj-cli-plugins.json` found walking up from {@link startDir}. */
66
+ static findConfig(startDir) {
67
+ let dir = resolve(startDir);
68
+ // eslint-disable-next-line no-constant-condition
69
+ while (true) {
70
+ const candidate = resolve(dir, PLUGIN_CONFIG_FILENAME);
71
+ if (existsSync(candidate))
72
+ return candidate;
73
+ const parent = dirname(dir);
74
+ if (parent === dir)
75
+ return null;
76
+ dir = parent;
77
+ }
78
+ }
79
+ /** Every registered plugin's usage metadata, de-duplicated by command key. */
80
+ static GetAllUsage() {
81
+ const regs = MJGlobal.Instance.ClassFactory.GetAllRegistrations(BaseCLIPlugin);
82
+ const byCommand = new Map();
83
+ for (const reg of regs) {
84
+ const usage = reg.SubClass?.Usage;
85
+ if (usage?.domain && usage?.command && !byCommand.has(usage.command)) {
86
+ byCommand.set(usage.command, usage);
87
+ }
88
+ }
89
+ return [...byCommand.values()];
90
+ }
91
+ /** Tier-1: the domain map — each domain + one-line summary + runtime class. */
92
+ static BuildDomainMap() {
93
+ const usages = this.GetAllUsage();
94
+ const byDomain = new Map();
95
+ for (const u of usages) {
96
+ const list = byDomain.get(u.domain) ?? [];
97
+ list.push(u);
98
+ byDomain.set(u.domain, list);
99
+ }
100
+ const domains = [...byDomain.entries()]
101
+ .map(([domain, cmds]) => ({
102
+ domain,
103
+ summary: this.domainSummary(domain, cmds),
104
+ runtime: this.domainRuntimeClass(cmds),
105
+ }))
106
+ .sort((a, b) => a.domain.localeCompare(b.domain));
107
+ return { guidance: GUIDANCE, domains };
108
+ }
109
+ /** Tier-2: every command in {@link domain} with summary, flags, examples, runtime. */
110
+ static BuildDomainDetail(domain) {
111
+ const target = domain.trim().toLowerCase();
112
+ const commands = this.GetAllUsage()
113
+ .filter((u) => u.domain.toLowerCase() === target)
114
+ .sort((a, b) => a.command.localeCompare(b.command));
115
+ return { domain, commands };
116
+ }
117
+ /** Wraps a tier payload in the universal {@link MJCLIResult} shape. */
118
+ static AsResult(command, data) {
119
+ return { success: true, command, durationSeconds: 0, data };
120
+ }
121
+ /**
122
+ * A domain summary: if a single command's summary best represents the domain
123
+ * use it; otherwise join the command summaries compactly.
124
+ */
125
+ static domainSummary(domain, cmds) {
126
+ if (cmds.length === 1)
127
+ return cmds[0].summary;
128
+ // Prefer a command whose key equals the domain (e.g. 'codegen').
129
+ const headline = cmds.find((c) => c.command === domain);
130
+ if (headline)
131
+ return headline.summary;
132
+ // Avoid a run-on line for 3+ commands: lead with the first summary + a count.
133
+ if (cmds.length >= 3)
134
+ return `${cmds[0].summary} (+${cmds.length - 1} more commands)`;
135
+ return cmds.map((c) => c.summary).join(' ');
136
+ }
137
+ /**
138
+ * The "loosest" runtime class across a domain's commands, so an agent setting a
139
+ * single domain-wide timeout errs on the generous side.
140
+ * Ordering: variable > slow > moderate > fast.
141
+ */
142
+ static domainRuntimeClass(cmds) {
143
+ const order = ['fast', 'moderate', 'slow', 'variable'];
144
+ let worst = 'fast';
145
+ for (const c of cmds) {
146
+ if (order.indexOf(c.runtime.class) > order.indexOf(worst))
147
+ worst = c.runtime.class;
148
+ }
149
+ return worst;
150
+ }
151
+ }
152
+ //# sourceMappingURL=plugin-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-registry.js","sourceRoot":"","sources":["../src/plugin-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ import type { IMJCLIRuntimeHost, LogLevel, MJCLIResult, OutputFormat, PluginUsage } from './types.js';
2
+ /**
3
+ * Default implementation of {@link IMJCLIRuntimeHost}.
4
+ *
5
+ * - **Text mode** (default): an `ora` spinner with a live elapsed timer for steps,
6
+ * chalk-colored logs, and a generic human summary on {@link MJCLIRuntimeHost.Emit}
7
+ * when the plugin hasn't already rendered its own. Plugins that want rich,
8
+ * command-specific text (e.g. a push summary box) build the string themselves
9
+ * and pass it through {@link MJCLIRuntimeHost.Log} — the host never hard-codes
10
+ * per-command formatting.
11
+ * - **JSON mode**: every decorative call (steps, logs, runtime advisory) goes to
12
+ * **stderr** so the {@link MJCLIResult} JSON on **stdout** stays clean and
13
+ * pipeable (plan D4: `mj sync push --format=json | jq .errors`).
14
+ * - **MD mode**: the result is emitted as a fenced ```json block on stdout
15
+ * (forward-looking slot for AI chat UIs, plan D10).
16
+ */
17
+ export declare class MJCLIRuntimeHost implements IMJCLIRuntimeHost {
18
+ readonly Format: OutputFormat;
19
+ readonly Verbose: boolean;
20
+ private readonly noBanner;
21
+ /** Process-start, used for the "· N total" running clock in text mode. */
22
+ private readonly startTime;
23
+ private spinner;
24
+ private stepBaseMessage;
25
+ private stepStart;
26
+ private ticker;
27
+ constructor(format?: OutputFormat, verbose?: boolean, noBanner?: boolean);
28
+ /** Spinners/colors are only appropriate in text mode on a real TTY. */
29
+ private get textMode();
30
+ private fmtMs;
31
+ private stopTicker;
32
+ /**
33
+ * Begin the live elapsed timer for the current step. Each call resets the
34
+ * clock so the timer reflects the CURRENT step. Unref'd so it never holds the
35
+ * event loop open on its own.
36
+ */
37
+ private startTicker;
38
+ /** Structured progress event on stderr (JSON mode) so stdout stays result-only. */
39
+ private emitStderrEvent;
40
+ StartStep(label: string): void;
41
+ UpdateStep(label: string): void;
42
+ SucceedStep(label: string, detail?: string): void;
43
+ FailStep(label: string, detail?: string): void;
44
+ Log(message: string, level?: LogLevel): void;
45
+ AnnounceRuntime(usage: PluginUsage): void;
46
+ Emit(result: MJCLIResult): void;
47
+ }
48
+ //# sourceMappingURL=runtime-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-host.d.ts","sourceRoot":"","sources":["../src/runtime-host.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGnG;;;;;;;;;;;;;;GAcG;AACH,qBAAa,gBAAiB,YAAW,iBAAiB;IACxD,SAAgB,MAAM,EAAE,YAAY,CAAC;IACrC,SAAgB,OAAO,EAAE,OAAO,CAAC;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IAEnC,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IAExC,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,eAAe,CAAM;IAC7B,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,MAAM,CAA+B;gBAEjC,MAAM,GAAE,YAAqB,EAAE,OAAO,UAAQ,EAAE,QAAQ,UAAQ;IAS5E,uEAAuE;IACvE,OAAO,KAAK,QAAQ,GAEnB;IAED,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,UAAU;IAOlB;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAenB,mFAAmF;IACnF,OAAO,CAAC,eAAe;IAIhB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAW9B,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAU/B,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAejD,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAc9C,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,QAAiB,GAAG,IAAI;IAapD,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAezC,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;CAWvC"}
@@ -0,0 +1,167 @@
1
+ import ora from 'ora-classic';
2
+ import chalk from 'chalk';
3
+ import { SerializeResult } from './serialize.js';
4
+ /**
5
+ * Default implementation of {@link IMJCLIRuntimeHost}.
6
+ *
7
+ * - **Text mode** (default): an `ora` spinner with a live elapsed timer for steps,
8
+ * chalk-colored logs, and a generic human summary on {@link MJCLIRuntimeHost.Emit}
9
+ * when the plugin hasn't already rendered its own. Plugins that want rich,
10
+ * command-specific text (e.g. a push summary box) build the string themselves
11
+ * and pass it through {@link MJCLIRuntimeHost.Log} — the host never hard-codes
12
+ * per-command formatting.
13
+ * - **JSON mode**: every decorative call (steps, logs, runtime advisory) goes to
14
+ * **stderr** so the {@link MJCLIResult} JSON on **stdout** stays clean and
15
+ * pipeable (plan D4: `mj sync push --format=json | jq .errors`).
16
+ * - **MD mode**: the result is emitted as a fenced ```json block on stdout
17
+ * (forward-looking slot for AI chat UIs, plan D10).
18
+ */
19
+ export class MJCLIRuntimeHost {
20
+ constructor(format = 'text', verbose = false, noBanner = false) {
21
+ /** Process-start, used for the "· N total" running clock in text mode. */
22
+ this.startTime = Date.now();
23
+ this.spinner = null;
24
+ this.stepBaseMessage = '';
25
+ this.stepStart = 0;
26
+ this.ticker = null;
27
+ this.Format = format;
28
+ this.Verbose = verbose;
29
+ // `--no-banner` is handled globally by the CLI prerun hook (it strips the flag
30
+ // from argv so not-yet-migrated commands don't fail oclif's strict parser) and
31
+ // signalled here via env, so honor either source.
32
+ this.noBanner = noBanner || process.env.MJ_CLI_NO_BANNER === '1';
33
+ }
34
+ /** Spinners/colors are only appropriate in text mode on a real TTY. */
35
+ get textMode() {
36
+ return this.Format === 'text';
37
+ }
38
+ fmtMs(ms) {
39
+ return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
40
+ }
41
+ stopTicker() {
42
+ if (this.ticker) {
43
+ clearInterval(this.ticker);
44
+ this.ticker = null;
45
+ }
46
+ }
47
+ /**
48
+ * Begin the live elapsed timer for the current step. Each call resets the
49
+ * clock so the timer reflects the CURRENT step. Unref'd so it never holds the
50
+ * event loop open on its own.
51
+ */
52
+ startTicker(message) {
53
+ this.stopTicker();
54
+ this.stepBaseMessage = message;
55
+ this.stepStart = Date.now();
56
+ if (this.spinner) {
57
+ this.ticker = setInterval(() => {
58
+ if (!this.spinner)
59
+ return;
60
+ const step = this.fmtMs(Date.now() - this.stepStart);
61
+ const total = this.fmtMs(Date.now() - this.startTime);
62
+ this.spinner.text = `${this.stepBaseMessage} ${chalk.gray(`· ${step} · ${total} total`)}`;
63
+ }, 100);
64
+ this.ticker.unref?.();
65
+ }
66
+ }
67
+ /** Structured progress event on stderr (JSON mode) so stdout stays result-only. */
68
+ emitStderrEvent(event) {
69
+ process.stderr.write(JSON.stringify(event) + '\n');
70
+ }
71
+ StartStep(label) {
72
+ if (this.textMode) {
73
+ if (!this.spinner)
74
+ this.spinner = ora();
75
+ this.spinner.start(label);
76
+ this.startTicker(label);
77
+ }
78
+ else if (this.Format === 'json') {
79
+ this.emitStderrEvent({ event: 'step', label });
80
+ }
81
+ // md mode: steps are noise in a fenced block — suppress.
82
+ }
83
+ UpdateStep(label) {
84
+ if (this.textMode) {
85
+ if (!this.spinner)
86
+ this.spinner = ora();
87
+ this.spinner.text = label;
88
+ this.startTicker(label);
89
+ }
90
+ else if (this.Format === 'json') {
91
+ this.emitStderrEvent({ event: 'step', label });
92
+ }
93
+ }
94
+ SucceedStep(label, detail) {
95
+ this.stopTicker();
96
+ if (this.textMode) {
97
+ const elapsed = this.stepStart ? chalk.gray(` (${this.fmtMs(Date.now() - this.stepStart)})`) : '';
98
+ const text = detail ? `${label} ${chalk.gray(detail)}${elapsed}` : `${label}${elapsed}`;
99
+ if (this.spinner) {
100
+ this.spinner.stopAndPersist({ symbol: chalk.green('✓'), text });
101
+ }
102
+ else {
103
+ process.stdout.write(`${chalk.green('✓')} ${text}\n`);
104
+ }
105
+ }
106
+ else if (this.Format === 'json') {
107
+ this.emitStderrEvent({ event: 'step-done', label, detail });
108
+ }
109
+ }
110
+ FailStep(label, detail) {
111
+ this.stopTicker();
112
+ if (this.textMode) {
113
+ const text = detail ? `${label} ${chalk.gray(detail)}` : label;
114
+ if (this.spinner) {
115
+ this.spinner.fail(text);
116
+ }
117
+ else {
118
+ process.stderr.write(`${chalk.red('✗')} ${text}\n`);
119
+ }
120
+ }
121
+ else if (this.Format === 'json') {
122
+ this.emitStderrEvent({ event: 'step-failed', label, detail });
123
+ }
124
+ }
125
+ Log(message, level = 'info') {
126
+ if (this.textMode) {
127
+ // A spinner mid-render would garble a raw write; stop it first.
128
+ if (this.spinner?.isSpinning)
129
+ this.spinner.stop();
130
+ const painted = level === 'error' ? chalk.red(message) : level === 'warn' ? chalk.yellow(message) : message;
131
+ // eslint-disable-next-line no-console
132
+ (level === 'error' ? console.error : console.log)(painted);
133
+ }
134
+ else {
135
+ // Keep stdout clean for the JSON/MD result — all human logging → stderr.
136
+ process.stderr.write(message + '\n');
137
+ }
138
+ }
139
+ AnnounceRuntime(usage) {
140
+ // Fast commands don't warrant an advisory — it would just be noise.
141
+ if (!usage?.runtime || usage.runtime.class === 'fast')
142
+ return;
143
+ if (this.noBanner)
144
+ return;
145
+ if (this.Format === 'json') {
146
+ this.emitStderrEvent({ event: 'start', command: usage.command, runtime: usage.runtime });
147
+ }
148
+ else if (this.textMode) {
149
+ const r = usage.runtime;
150
+ const secs = r.typicalSeconds ? `~${r.typicalSeconds}s` : r.class;
151
+ const note = r.note ? ` — ${r.note}` : '';
152
+ process.stderr.write(chalk.gray(`⏱ ${usage.command}: typically ${secs} (${r.class})${note}\n`));
153
+ }
154
+ }
155
+ Emit(result) {
156
+ this.stopTicker();
157
+ if (this.spinner?.isSpinning)
158
+ this.spinner.stop();
159
+ if (this.Format === 'json' || this.Format === 'md') {
160
+ process.stdout.write(SerializeResult(result, this.Format) + '\n');
161
+ }
162
+ // Text mode: the plugin is responsible for its own rich human output (via
163
+ // Log/StartStep/SucceedStep). Emit deliberately prints nothing extra so we
164
+ // never double-render a summary.
165
+ }
166
+ }
167
+ //# sourceMappingURL=runtime-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-host.js","sourceRoot":"","sources":["../src/runtime-host.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { MJCLIResult, OutputFormat } from './types.js';
2
+ /**
3
+ * Single source of truth for serializing an {@link MJCLIResult} per format.
4
+ * Both the runtime host's `Emit` and the usage commands call this so JSON/MD
5
+ * envelopes are always rendered identically. Returns the empty string for
6
+ * `text` — in text mode the plugin renders its own human output, not a result
7
+ * blob. No trailing newline; callers add one.
8
+ */
9
+ export declare function SerializeResult(result: MJCLIResult, format: OutputFormat): string;
10
+ //# sourceMappingURL=serialize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEzD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,GAAG,MAAM,CAIjF"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Single source of truth for serializing an {@link MJCLIResult} per format.
3
+ * Both the runtime host's `Emit` and the usage commands call this so JSON/MD
4
+ * envelopes are always rendered identically. Returns the empty string for
5
+ * `text` — in text mode the plugin renders its own human output, not a result
6
+ * blob. No trailing newline; callers add one.
7
+ */
8
+ export function SerializeResult(result, format) {
9
+ if (format === 'json')
10
+ return JSON.stringify(result, null, 2);
11
+ if (format === 'md')
12
+ return '```json\n' + JSON.stringify(result, null, 2) + '\n```';
13
+ return '';
14
+ }
15
+ //# sourceMappingURL=serialize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize.js","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,MAAmB,EAAE,MAAoB;IACvE,IAAI,MAAM,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC9D,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC;IACpF,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Core type definitions for the pluggable MJ CLI.
3
+ *
4
+ * The guiding principle (plan D2): plugins return *data* — a {@link MJCLIResult} —
5
+ * and the {@link IMJCLIRuntimeHost} renders it per the active {@link OutputFormat}.
6
+ * No `ora`, `chalk`, or `console` calls live inside a plugin's business logic.
7
+ */
8
+ /**
9
+ * Output format selected via the global `--format` flag.
10
+ * - `text`: human-readable (the default; spinners + chalk).
11
+ * - `json`: machine-readable result on stdout, decorative output on stderr.
12
+ * - `md`: Markdown-fenced result block, for AI chat UIs (forward-looking, plan D10).
13
+ */
14
+ export type OutputFormat = 'text' | 'json' | 'md';
15
+ /** Severity for {@link IMJCLIRuntimeHost.Log}. */
16
+ export type LogLevel = 'info' | 'warn' | 'error';
17
+ /**
18
+ * How long a command typically runs, so an AI agent wrapping `mj` in a shell
19
+ * timeout can budget correctly rather than killing a healthy long-running
20
+ * command midway (plan §1d / D12).
21
+ */
22
+ export interface RuntimeHint {
23
+ /**
24
+ * - `fast`: <5s (no start advisory emitted).
25
+ * - `moderate`: 5–60s.
26
+ * - `slow`: >60s.
27
+ * - `variable`: depends on scope — see {@link RuntimeHint.note}.
28
+ */
29
+ class: 'fast' | 'moderate' | 'slow' | 'variable';
30
+ /** Best-guess midpoint (seconds) an agent can use to set a timeout. */
31
+ typicalSeconds?: number;
32
+ /** e.g. 'scales with entity count', 'full migration ≫ incremental'. */
33
+ note?: string;
34
+ }
35
+ /** One flag described in a plugin's usage metadata. */
36
+ export interface PluginUsageFlag {
37
+ name: string;
38
+ type: string;
39
+ description: string;
40
+ }
41
+ /**
42
+ * Per-plugin usage + runtime metadata. Drives the progressive-disclosure
43
+ * `mj usage` (tier 1) and `mj <domain> usage` (tier 2) surface and the timeout
44
+ * advisory (plan §5). Co-located with the plugin so it can never drift from the
45
+ * actual flags.
46
+ */
47
+ export interface PluginUsage {
48
+ /** Groups commands in `mj usage` — e.g. 'sync', 'codegen', 'migrate'. */
49
+ domain: string;
50
+ /** The invocation key — e.g. 'sync:push', 'codegen', 'migrate'. */
51
+ command: string;
52
+ /** One line, shown in the `mj usage` domain map. Keep it terse. */
53
+ summary: string;
54
+ /** Fuller prose, shown only in `mj <domain> usage`. */
55
+ description?: string;
56
+ flags?: PluginUsageFlag[];
57
+ /** Copy-pasteable invocations. */
58
+ examples?: string[];
59
+ runtime: RuntimeHint;
60
+ }
61
+ /** A single failure, collected (not interleaved) so an agent can read them as a list. */
62
+ export interface MJCLIResultError {
63
+ /** Entity name, file path, phase — whatever is relevant. */
64
+ context?: string;
65
+ message: string;
66
+ }
67
+ /**
68
+ * Universal result shape every plugin returns (plan D6). Command-specific detail
69
+ * goes in the typed {@link MJCLIResult.data} field; failures always go in
70
+ * {@link MJCLIResult.errors} with full detail (not just a count).
71
+ */
72
+ export interface MJCLIResult {
73
+ success: boolean;
74
+ /** 'sync:push', 'codegen', 'migrate', etc. */
75
+ command: string;
76
+ durationSeconds: number;
77
+ data?: Record<string, unknown>;
78
+ errors?: MJCLIResultError[];
79
+ warnings?: string[];
80
+ }
81
+ /**
82
+ * The runtime host abstracts all stdio. A plugin talks to the host through this
83
+ * interface; the host decides whether a call becomes a spinner line, a JSON event
84
+ * on stderr, or nothing — based on the active {@link OutputFormat}.
85
+ */
86
+ export interface IMJCLIRuntimeHost {
87
+ /** The active output format. Plugins gate human-only text rendering on this. */
88
+ readonly Format: OutputFormat;
89
+ /** Whether `--verbose` was set. */
90
+ readonly Verbose: boolean;
91
+ StartStep(label: string): void;
92
+ UpdateStep(label: string): void;
93
+ SucceedStep(label: string, detail?: string): void;
94
+ FailStep(label: string, detail?: string): void;
95
+ Log(message: string, level?: LogLevel): void;
96
+ /**
97
+ * Runtime advisory, emitted before work starts so an agent can budget its
98
+ * timeout (plan §5/§6). Text mode: a dim one-liner on stderr. JSON mode: a
99
+ * `{event:'start', ...}` line on stderr. Suppressed for `fast` commands.
100
+ */
101
+ AnnounceRuntime(usage: PluginUsage): void;
102
+ /** Final result — host serializes per the active `--format`. */
103
+ Emit(result: MJCLIResult): void;
104
+ }
105
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAElD,kDAAkD;AAClD,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEjD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,CAAC;IACjD,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,uDAAuD;AACvD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,eAAe,EAAE,CAAC;IAC1B,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,yFAAyF;AACzF,MAAM,WAAW,gBAAgB;IAC/B,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,mCAAmC;IACnC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAG1B,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClD,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAG/C,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAE7C;;;;OAIG;IACH,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IAE1C,gEAAgE;IAChE,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;CACjC"}
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Core type definitions for the pluggable MJ CLI.
3
+ *
4
+ * The guiding principle (plan D2): plugins return *data* — a {@link MJCLIResult} —
5
+ * and the {@link IMJCLIRuntimeHost} renders it per the active {@link OutputFormat}.
6
+ * No `ora`, `chalk`, or `console` calls live inside a plugin's business logic.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
package/package.json CHANGED
@@ -1,10 +1,43 @@
1
1
  {
2
2
  "name": "@memberjunction/cli-core",
3
- "version": "0.0.1",
4
- "description": "OIDC trusted publishing setup package for @memberjunction/cli-core",
3
+ "type": "module",
4
+ "version": "5.42.0",
5
+ "description": "Pluggable MJ CLI core — BaseCLIPlugin, runtime host, and progressive-disclosure usage primitives shared by mj CLI plugins",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc && tsc-alias -f",
10
+ "watch": "tsc --watch",
11
+ "clean": "rimraf dist",
12
+ "test": "vitest run",
13
+ "test:watch": "vitest"
14
+ },
5
15
  "keywords": [
6
- "oidc",
7
- "trusted-publishing",
8
- "setup"
9
- ]
16
+ "memberjunction",
17
+ "cli",
18
+ "oclif",
19
+ "plugin"
20
+ ],
21
+ "author": "MemberJunction",
22
+ "license": "ISC",
23
+ "dependencies": {
24
+ "@memberjunction/global": "5.42.0",
25
+ "@oclif/core": "^3.27.0",
26
+ "chalk": "^5.6.2",
27
+ "ora-classic": "^5.4.2"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^24.10.11",
31
+ "rimraf": "6.1.2",
32
+ "tsc-alias": "^1.8.10",
33
+ "typescript": "^5.9.3",
34
+ "vitest": "^4.0.18"
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/MemberJunction/MJ"
42
+ }
10
43
  }