@happyvertical/smrt-app-cli 0.43.7 → 0.43.8

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
@@ -17,6 +17,45 @@ pnpm add @happyvertical/smrt-app-cli
17
17
 
18
18
  Node.js 24.18 or newer is required.
19
19
 
20
+ The published package also exposes a configuration-driven `smrt-app` binary.
21
+ It lets policy runners and app operators use the canonical CLI outside a
22
+ consumer repository without maintaining a branded wrapper:
23
+
24
+ ```bash
25
+ SMRT_APP_NAME=work \
26
+ SMRT_APP_ENV_PREFIX=WORK \
27
+ SMRT_APP_CONFIG_DIR=happyvertical-work \
28
+ SMRT_APP_DEFAULT_SERVER_URL=https://work.example \
29
+ pnpm dlx --package @happyvertical/smrt-app-cli@X.Y.Z \
30
+ smrt-app -- auth status
31
+ ```
32
+
33
+ Pin the exact published package version in automation. The four application
34
+ settings are required and are non-secret; command arguments after `--` are
35
+ forwarded unchanged to `createAppCli`. The same values may be passed as
36
+ `--name`, `--env-prefix`, `--config-dir`, and `--default-server-url` before the
37
+ delimiter. Invalid or incomplete configuration fails before the CLI performs
38
+ network or credential access. The executable also applies the HTTPS/loopback
39
+ policy to the effective server URL selected from app-specific environment or
40
+ persisted configuration, and to command-level server overrides.
41
+
42
+ To run the same CLI's stdio MCP bridge, configure its explicit identity and
43
+ select bridge mode:
44
+
45
+ ```bash
46
+ SMRT_APP_NAME=work \
47
+ SMRT_APP_ENV_PREFIX=WORK \
48
+ SMRT_APP_CONFIG_DIR=happyvertical-work \
49
+ SMRT_APP_DEFAULT_SERVER_URL=https://work.example \
50
+ SMRT_APP_MCP_SERVER_NAME=work-mcp \
51
+ SMRT_APP_MCP_SERVER_VERSION=1.0.0 \
52
+ smrt-app --stdio-mcp
53
+ ```
54
+
55
+ `SMRT_APP_*` is reserved for non-secret executable configuration. Runtime
56
+ credentials continue to use the app-specific environment/config resolution
57
+ implemented by `createAppCli`; never pass bearer tokens as executable options.
58
+
20
59
  ## Quick start
21
60
 
22
61
  ```ts
@@ -114,6 +153,8 @@ await cli.startMcpBridge({ name: 'acme-mcp', version: '1.0.0' });
114
153
  The package also ships the generic `smrt-mcp-bridge` binary. The remote app
115
154
  surface is typically mounted with
116
155
  [`@happyvertical/smrt-app-mcp`](../smrt-app-mcp/README.md).
156
+ The binary rejects non-loopback plaintext HTTP origins before resolving or
157
+ sending stored credentials; use HTTPS for remote application servers.
117
158
 
118
159
  The bridge canonicalizes its `tools/list` catalog by tool name and emits a
119
160
  one-day `private` cache lifetime. The catalog is tied to the configured local
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `smrt-app` — configuration-driven executable for a published SMRT app CLI.
3
+ *
4
+ * The wrapper accepts non-secret identity before `--`; everything after `--`
5
+ * is forwarded unchanged to the canonical `createAppCli` command dispatcher.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=smrt-app.d.ts.map
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import { i as runAppCliExecutable } from "../src-DCo67mp8.js";
3
+ //#region src/bin/smrt-app.ts
4
+ /**
5
+ * `smrt-app` — configuration-driven executable for a published SMRT app CLI.
6
+ *
7
+ * The wrapper accepts non-secret identity before `--`; everything after `--`
8
+ * is forwarded unchanged to the canonical `createAppCli` command dispatcher.
9
+ */
10
+ try {
11
+ await runAppCliExecutable();
12
+ } catch (error) {
13
+ const message = error instanceof Error ? error.message : "Unknown error.";
14
+ process.stderr.write(`smrt-app: ${message}\n`);
15
+ process.exitCode = 2;
16
+ }
17
+ //#endregion
18
+ export {};
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { p as runMcpStdioBridge } from "../config-B6arU8x7.js";
2
+ import { m as runMcpStdioBridge } from "../config-CuTFiGxX.js";
3
3
  //#region src/bin/smrt-mcp-bridge.ts
4
4
  /**
5
5
  * `smrt-mcp-bridge` — generic stdio MCP bridge.
@@ -39,6 +39,7 @@ await runMcpStdioBridge({
39
39
  envPrefix,
40
40
  appSlug: readArg("app-slug") ?? process.env.SMRT_MCP_APP_SLUG,
41
41
  defaultServerUrl: readArg("default-server-url") ?? process.env.SMRT_MCP_DEFAULT_SERVER_URL,
42
+ requireSecureServerUrl: true,
42
43
  serverInfo: {
43
44
  name: readArg("name") ?? process.env.SMRT_MCP_SERVER_NAME ?? "smrt-app-mcp",
44
45
  version: readArg("version") ?? process.env.SMRT_MCP_SERVER_VERSION ?? "0.0.0"
@@ -0,0 +1,2 @@
1
+ import { m as runMcpStdioBridge } from "./config-CuTFiGxX.js";
2
+ export { runMcpStdioBridge };
@@ -159,6 +159,27 @@ async function runMcpStdioBridge(options) {
159
159
  * @packageDocumentation
160
160
  */
161
161
  var DEFAULT_LOCAL_SERVER = "http://localhost:5173";
162
+ /** Enforce the executable-safe server URL policy without echoing URL values. */
163
+ function assertSecureServerUrl(value) {
164
+ let url;
165
+ try {
166
+ url = new URL(value);
167
+ } catch {
168
+ throw new Error("Invalid server URL configuration.");
169
+ }
170
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw new Error("Invalid server URL configuration.");
171
+ const localHosts = /* @__PURE__ */ new Set([
172
+ "localhost",
173
+ "127.0.0.1",
174
+ "[::1]"
175
+ ]);
176
+ if (url.protocol === "http:" && !localHosts.has(url.hostname)) throw new Error("Invalid server URL configuration.");
177
+ }
178
+ function normalizeServerUrl(context, value) {
179
+ const normalized = value.replace(/\/+$/u, "");
180
+ if (context.requireSecureServerUrl) assertSecureServerUrl(normalized);
181
+ return normalized;
182
+ }
162
183
  function configFilePath(context) {
163
184
  const override = process.env[`${context.envPrefix}_CLI_CONFIG`];
164
185
  if (override) return override;
@@ -201,7 +222,7 @@ async function saveCliConfig(context, config) {
201
222
  /** Resolve the server URL: env var → config file → `defaultServerUrl`. */
202
223
  async function getServerUrl(context, config) {
203
224
  const resolved = config ?? await loadCliConfig(context);
204
- return (process.env[`${context.envPrefix}_SERVER_URL`] ?? resolved.serverUrl ?? context.defaultServerUrl ?? DEFAULT_LOCAL_SERVER).replace(/\/+$/u, "");
225
+ return normalizeServerUrl(context, process.env[`${context.envPrefix}_SERVER_URL`] ?? resolved.serverUrl ?? context.defaultServerUrl ?? DEFAULT_LOCAL_SERVER);
205
226
  }
206
227
  /** Resolve a bearer token only when it is bound to the exact target server. */
207
228
  async function getStoredToken(context, config, serverUrl) {
@@ -271,7 +292,7 @@ async function requestJson(context, path, init = {}, options = {}) {
271
292
  */
272
293
  async function requestJsonResult(context, path, init = {}, options = {}) {
273
294
  const config = options.loadedConfig ?? await loadCliConfig(context);
274
- const serverUrl = (options.serverUrl ?? await getServerUrl(context, config)).replace(/\/+$/u, "");
295
+ const serverUrl = normalizeServerUrl(context, options.serverUrl ?? await getServerUrl(context, config));
275
296
  const token = await getStoredToken(context, config, serverUrl);
276
297
  const headers = new Headers(init.headers);
277
298
  if (options.requireAuth && options.auth !== false && !token) return failure(401, {
@@ -432,4 +453,4 @@ async function readBodyWithCap(response, maxBytes) {
432
453
  return new TextDecoder().decode(Buffer.concat(chunks.map((c) => Buffer.from(c))));
433
454
  }
434
455
  //#endregion
435
- export { loadCliConfig as a, requestJsonResult as c, createMcpStdioBridge as d, formatMcpCallResult as f, getStoredToken as i, saveAuth as l, toMcpTransportError as m, clearStoredToken as n, redactTransportValue as o, runMcpStdioBridge as p, getServerUrl as r, requestJson as s, AppCliRequestError as t, saveCliConfig as u };
456
+ export { getStoredToken as a, requestJson as c, saveCliConfig as d, createMcpStdioBridge as f, toMcpTransportError as h, getServerUrl as i, requestJsonResult as l, runMcpStdioBridge as m, assertSecureServerUrl as n, loadCliConfig as o, formatMcpCallResult as p, clearStoredToken as r, redactTransportValue as s, AppCliRequestError as t, saveAuth as u };
package/dist/index.d.ts CHANGED
@@ -43,6 +43,16 @@ export declare interface AppCliContext {
43
43
  stderr: NodeJS.WriteStream;
44
44
  }
45
45
 
46
+ export declare interface AppCliExecutableConfig {
47
+ cliOptions: Pick<CreateAppCliOptions, 'name' | 'envPrefix' | 'configDir' | 'defaultServerUrl' | 'requireSecureServerUrl'>;
48
+ argv: string[];
49
+ mcp?: {
50
+ name: string;
51
+ version: string;
52
+ };
53
+ stdioMcp: boolean;
54
+ }
55
+
46
56
  /** Error compatibility wrapper returned by the legacy throwing helper. */
47
57
  export declare class AppCliRequestError extends Error {
48
58
  readonly status: number;
@@ -79,6 +89,9 @@ declare interface AppResultMetadata {
79
89
  expectedVersion?: DeclaredActionField;
80
90
  }
81
91
 
92
+ /** Enforce the executable-safe server URL policy without echoing URL values. */
93
+ export declare function assertSecureServerUrl(value: string): void;
94
+
82
95
  export declare function buildFlagParser(schema: Record<string, unknown> | undefined, options?: ParserOptions): BuildParserResult;
83
96
 
84
97
  export declare interface BuildParserResult {
@@ -134,6 +147,11 @@ export declare interface CliConfigContext {
134
147
  appSlug?: string;
135
148
  /** Fallback server URL if neither env nor config sets one. */
136
149
  defaultServerUrl?: string;
150
+ /**
151
+ * Require HTTPS for every resolved or request-level server URL, while still
152
+ * permitting loopback HTTP for local development.
153
+ */
154
+ requireSecureServerUrl?: boolean;
137
155
  }
138
156
 
139
157
  export declare type CliResource = CliResource_2;
@@ -194,6 +212,8 @@ export declare interface CreateAppCliOptions {
194
212
  configDir?: string;
195
213
  /** Baked-in default server URL when neither env nor config sets one. */
196
214
  defaultServerUrl?: string;
215
+ /** Enforce HTTPS or loopback HTTP for every effective server URL. */
216
+ requireSecureServerUrl?: boolean;
197
217
  /**
198
218
  * App-specific commands. Dispatched BEFORE built-ins (`auth`,
199
219
  * `resources`, `mcp`) and before the resource-slug dispatcher, so an
@@ -393,6 +413,9 @@ declare interface OutputOptions {
393
413
  stdoutIsTty?: boolean;
394
414
  }
395
415
 
416
+ /** Parse and validate the generic executable's non-secret configuration. */
417
+ export declare function parseAppCliExecutableConfig(argv: string[], env?: NodeJS.ProcessEnv): AppCliExecutableConfig;
418
+
396
419
  export declare interface ParsedArgs {
397
420
  /** Body to send (JSON serialised, for POST/PUT/PATCH). May be empty. */
398
421
  body: Record<string, unknown>;
@@ -511,6 +534,15 @@ declare interface ResourceListResponseBody {
511
534
  artifact?: DiscoveryConformanceArtifact;
512
535
  }
513
536
 
537
+ /** Create the canonical app CLI and execute its forwarded command or bridge. */
538
+ export declare function runAppCliExecutable(options?: RunAppCliExecutableOptions): Promise<void>;
539
+
540
+ export declare interface RunAppCliExecutableOptions {
541
+ argv?: string[];
542
+ env?: NodeJS.ProcessEnv;
543
+ createCli?: (options: CreateAppCliOptions) => AppCli;
544
+ }
545
+
514
546
  /**
515
547
  * One-call entry point — start the factory-owned stdio bridge. The returned
516
548
  * promise resolves once the stdio listener is installed.