@hypequery/cli 1.21.1 → 1.22.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.
Files changed (34) hide show
  1. package/README.md +3 -25
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +13 -0
  4. package/dist/commands/deploy.d.ts +5 -0
  5. package/dist/commands/deploy.d.ts.map +1 -1
  6. package/dist/commands/deploy.js +10 -0
  7. package/dist/commands/deployment-status.d.ts +16 -0
  8. package/dist/commands/deployment-status.d.ts.map +1 -0
  9. package/dist/commands/deployment-status.js +36 -0
  10. package/dist/commands/live-source.d.ts.map +1 -1
  11. package/dist/commands/live-source.js +3 -21
  12. package/dist/commands/mcp.d.ts +10 -0
  13. package/dist/commands/mcp.d.ts.map +1 -1
  14. package/dist/commands/mcp.js +48 -0
  15. package/dist/test-utils.d.ts +1 -1
  16. package/dist/utils/deployment-target.d.ts +7 -0
  17. package/dist/utils/deployment-target.d.ts.map +1 -0
  18. package/dist/utils/deployment-target.js +19 -0
  19. package/dist/utils/hosted-endpoints-output.d.ts +4 -0
  20. package/dist/utils/hosted-endpoints-output.d.ts.map +1 -0
  21. package/dist/utils/hosted-endpoints-output.js +27 -0
  22. package/dist/utils/hosted-endpoints.d.ts +37 -0
  23. package/dist/utils/hosted-endpoints.d.ts.map +1 -0
  24. package/dist/utils/hosted-endpoints.js +81 -0
  25. package/dist/utils/live-deployment.d.ts +6 -0
  26. package/dist/utils/live-deployment.d.ts.map +1 -1
  27. package/dist/utils/live-deployment.js +6 -1
  28. package/dist/utils/mcp-remote-check.d.ts +19 -0
  29. package/dist/utils/mcp-remote-check.d.ts.map +1 -0
  30. package/dist/utils/mcp-remote-check.js +96 -0
  31. package/dist/utils/mcp-response.d.ts +3 -0
  32. package/dist/utils/mcp-response.d.ts.map +1 -0
  33. package/dist/utils/mcp-response.js +58 -0
  34. package/package.json +10 -10
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  The fastest way to start a type-safe ClickHouse analytics backend in TypeScript.
4
4
 
5
- `@hypequery/cli` connects to ClickHouse or embedded chDB, generates schema types, scaffolds queries or semantic datasets, and runs a local API with interactive documentation. The same CLI can generate React route manifests and deploy a verified analytics bundle when you are ready.
5
+ `@hypequery/cli` connects to ClickHouse or embedded chDB, generates schema types, scaffolds queries or semantic datasets, and runs a local API with interactive documentation. It also generates React route manifests.
6
6
 
7
7
  ## Start here
8
8
 
@@ -18,29 +18,9 @@ For a semantic layer:
18
18
 
19
19
  ```bash
20
20
  npx hypequery init --style datasets
21
- npx hypequery login
22
- npx hypequery deploy analytics/cloud.ts
23
- ```
24
-
25
- The dataset scaffold writes `analytics/cloud.ts` alongside the local
26
- `analytics/api.ts`. Its Cloud publication is explicit:
27
-
28
- ```ts
29
- import { publishToCloud } from '@hypequery/datasets';
30
- import { datasets } from './datasets.js';
31
-
32
- export const cloud = publishToCloud({
33
- datasets,
34
- access: { roles: [], scopes: [] },
35
- });
21
+ npx hypequery dev --open
36
22
  ```
37
23
 
38
- `api.ts` remains the local Serve entrypoint. Cloud deployment builds from
39
- `cloud.ts` and does not load Serve configuration. This first Cloud publisher
40
- accepts datasets only; it rejects named metrics rather than dropping them.
41
- The required access policy makes the authenticated roles and scopes visible
42
- before an existing project moves away from Serve-based publishing.
43
-
44
24
  For embedded analytics without a ClickHouse server:
45
25
 
46
26
  ```bash
@@ -58,9 +38,8 @@ npx hypequery init \
58
38
  | `hypequery generate` | Fresh TypeScript types from ClickHouse or chDB |
59
39
  | `hypequery generate:datasets` | Dataset definitions scaffolded from tables |
60
40
  | `hypequery generate:manifest` | A browser-safe route manifest for React hooks |
61
- | `hypequery login` | An authenticated Cloud target |
62
- | `hypequery deploy analytics/cloud.ts` | A verified dataset-only Cloud deployment |
63
41
  | `hypequery pull` / `diff` | Live source inspection and comparison |
42
+ | `hypequery deployment:status` | Live release and Cloud-provided connection URLs |
64
43
 
65
44
  Non-interactive ClickHouse commands read `CLICKHOUSE_URL`, `CLICKHOUSE_DATABASE`, `CLICKHOUSE_USERNAME`, and `CLICKHOUSE_PASSWORD`.
66
45
 
@@ -69,7 +48,6 @@ Non-interactive ClickHouse commands read `CLICKHOUSE_URL`, `CLICKHOUSE_DATABASE`
69
48
  - Get correct ClickHouse runtime types instead of guessing interfaces.
70
49
  - Move from one local query to datasets, APIs, React, and MCP without changing tools.
71
50
  - Keep generated types and route manifests reproducible in CI.
72
- - Build closed, hash-verified deployment artifacts from the same source.
73
51
 
74
52
  ## Learn more
75
53
 
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiCpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAqQD,OAAO,EAAE,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqCpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAkRD,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/dist/cli.js CHANGED
@@ -9,6 +9,7 @@ import { generateDatasetsCommand } from './commands/generate-datasets.js';
9
9
  import { generateManifestCommand } from './commands/generate-manifest.js';
10
10
  import { buildDeploymentCommand, prepareDeploymentReleaseCommand, validateDeploymentCommand, } from './commands/deployment.js';
11
11
  import { deployCommand, submitDeploymentCommand, } from './commands/deploy.js';
12
+ import { deploymentStatusCommand, } from './commands/deployment-status.js';
12
13
  import { loginCommand, logoutCommand, } from './commands/login.js';
13
14
  import { diffCommand, pullCommand, } from './commands/live-source.js';
14
15
  import { isPromptCancelled } from './utils/prompts.js';
@@ -133,6 +134,7 @@ program
133
134
  .option('--path <path>', 'Analytics directory (loads <path>/api.ts or <path>/queries.ts)')
134
135
  .option('--tenant <id>', 'Trusted tenant applied to every tenant-scoped dataset')
135
136
  .option('--self-test', 'Check the entrypoint and exit without speaking MCP')
137
+ .option('--url <url>', 'With --self-test: check a hosted MCP endpoint (key from HYPEQUERY_API_KEY)')
136
138
  .action(runCommand(async (file, options) => {
137
139
  await mcpCommand(file, options);
138
140
  }));
@@ -198,6 +200,16 @@ program
198
200
  .action(runCommand(async (bundle, options) => {
199
201
  await submitDeploymentCommand(bundle, options);
200
202
  }));
203
+ program
204
+ .command('deployment:status')
205
+ .description('Show the live release and hosted connection details for a Cloud target')
206
+ .option('--project <project>', 'Target project identifier (advanced override)')
207
+ .option('--environment <environment>', 'Target environment identifier (advanced override)')
208
+ .option('--endpoint <url>', 'HTTPS submission endpoint; requires HYPEQUERY_API_TOKEN')
209
+ .option('--mcp-config', 'Also print MCP client configuration for the hosted endpoint')
210
+ .action(runCommand(async (options) => {
211
+ await deploymentStatusCommand(options);
212
+ }));
201
213
  program
202
214
  .command('deploy <source>')
203
215
  .description('Build, prepare, and deploy a dataset-only Cloud publication')
@@ -208,6 +220,7 @@ program
208
220
  .option('--no-source', 'Exclude project source files from the deployment bundle')
209
221
  .option('--endpoint <url>', 'HTTPS submission endpoint; requires HYPEQUERY_API_TOKEN')
210
222
  .option('--replace-restored', 'Intentionally replace a restored live release')
223
+ .option('--mcp-config', 'Also print MCP client configuration for the hosted endpoint')
211
224
  .action(runCommand(async (source, options) => {
212
225
  await deployCommand(source, options);
213
226
  }));
@@ -1,11 +1,14 @@
1
1
  import { createHttpDeploymentUploadTransport, type DeploymentSubmissionResponse } from '../utils/deployment-upload.js';
2
2
  import { type StoredCloudCredential } from '../utils/cloud-credential-store.js';
3
3
  import { fetchLiveDeployment } from '../utils/live-deployment.js';
4
+ import { fetchHostedEndpoints } from '../utils/hosted-endpoints.js';
4
5
  import { buildDeploymentCommand, prepareDeploymentReleaseCommand } from './deployment.js';
5
6
  export interface SubmitDeploymentOptions {
6
7
  release?: string;
7
8
  endpoint?: string;
8
9
  replaceRestored?: boolean;
10
+ /** Also print MCP client configuration for the hosted endpoint. */
11
+ mcpConfig?: boolean;
9
12
  }
10
13
  export interface DeployOptions {
11
14
  endpoint?: string;
@@ -15,12 +18,14 @@ export interface DeployOptions {
15
18
  bundleOutput?: string;
16
19
  releaseOutput?: string;
17
20
  source?: boolean;
21
+ mcpConfig?: boolean;
18
22
  }
19
23
  export interface SubmitDeploymentDependencies {
20
24
  readonly env?: Readonly<Record<string, string | undefined>>;
21
25
  readonly createTransport?: typeof createHttpDeploymentUploadTransport;
22
26
  readonly loadCredential?: () => Promise<StoredCloudCredential | null>;
23
27
  readonly fetchLive?: typeof fetchLiveDeployment;
28
+ readonly fetchEndpoints?: typeof fetchHostedEndpoints;
24
29
  }
25
30
  export interface DeployDependencies extends SubmitDeploymentDependencies {
26
31
  readonly buildDeployment?: typeof buildDeploymentCommand;
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AASA,OAAO,EACL,mCAAmC,EAEnC,KAAK,4BAA4B,EAElC,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EACL,KAAK,qBAAqB,EAC3B,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EACL,sBAAsB,EACtB,+BAA+B,EAChC,MAAM,iBAAiB,CAAC;AAMzB,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,mCAAmC,CAAC;IACtE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,mBAAmB,CAAC;CACjD;AAED,MAAM,WAAW,kBAAmB,SAAQ,4BAA4B;IACtE,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,sBAAsB,CAAC;IACzD,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,+BAA+B,CAAC;IAC3E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,uBAAuB,CAAC;CAC5D;AA4DD,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,uBAA4B,EACrC,YAAY,GAAE,4BAAiC,GAC9C,OAAO,CAAC,4BAA4B,CAAC,CA0EvC;AAsBD,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,aAAkB,EAC3B,YAAY,GAAE,kBAAuB,GACpC,OAAO,CAAC,4BAA4B,CAAC,CAuCvC"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AASA,OAAO,EACL,mCAAmC,EAEnC,KAAK,4BAA4B,EAElC,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EACL,KAAK,qBAAqB,EAC3B,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EACL,oBAAoB,EACrB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACL,sBAAsB,EACtB,+BAA+B,EAChC,MAAM,iBAAiB,CAAC;AAMzB,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,mEAAmE;IACnE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,mCAAmC,CAAC;IACtE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,mBAAmB,CAAC;IAChD,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,oBAAoB,CAAC;CACvD;AAED,MAAM,WAAW,kBAAmB,SAAQ,4BAA4B;IACtE,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,sBAAsB,CAAC;IACzD,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,+BAA+B,CAAC;IAC3E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,uBAAuB,CAAC;CAC5D;AA4DD,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,uBAA4B,EACrC,YAAY,GAAE,4BAAiC,GAC9C,OAAO,CAAC,4BAA4B,CAAC,CAiFvC;AAsBD,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,aAAkB,EAC3B,YAAY,GAAE,kBAAuB,GACpC,OAAO,CAAC,4BAA4B,CAAC,CAwCvC"}
@@ -6,6 +6,8 @@ import { createHttpDeploymentUploadTransport, DeploymentUploadError, } from '../
6
6
  import { logger } from '../utils/logger.js';
7
7
  import { resolveDeploymentCredential } from '../utils/cloud-deployment-access.js';
8
8
  import { fetchLiveDeployment } from '../utils/live-deployment.js';
9
+ import { fetchHostedEndpoints, } from '../utils/hosted-endpoints.js';
10
+ import { printHostedEndpoints } from '../utils/hosted-endpoints-output.js';
9
11
  import { buildDeploymentCommand, prepareDeploymentReleaseCommand, } from './deployment.js';
10
12
  const MAX_RELEASE_FILE_BYTES = 16 * 1024;
11
13
  const DEFAULT_BUNDLE_PATH = 'analytics/hypequery-deployment';
@@ -120,6 +122,13 @@ export async function submitDeploymentCommand(bundlePath, options = {}, dependen
120
122
  : 'Deployment release already exists');
121
123
  logger.info(`Release identity: ${result.releaseIdentity}`);
122
124
  logger.info(`Bundle identity: ${result.bundleIdentity}`);
125
+ const endpoints = await (dependencies.fetchEndpoints ?? fetchHostedEndpoints)({
126
+ endpoint: deploymentEndpoint,
127
+ token,
128
+ target: release.release.target,
129
+ });
130
+ if (endpoints)
131
+ printHostedEndpoints(endpoints, options.mcpConfig === true);
123
132
  return result;
124
133
  }
125
134
  /**
@@ -172,5 +181,6 @@ export async function deployCommand(sourcePath, options = {}, dependencies = {})
172
181
  release: releasePath,
173
182
  endpoint: options.endpoint,
174
183
  replaceRestored: options.replaceRestored,
184
+ mcpConfig: options.mcpConfig,
175
185
  }, dependencies);
176
186
  }
@@ -0,0 +1,16 @@
1
+ import { type CloudDeploymentAccessDependencies } from '../utils/cloud-deployment-access.js';
2
+ import { fetchHostedEndpoints } from '../utils/hosted-endpoints.js';
3
+ import { fetchLiveDeployment } from '../utils/live-deployment.js';
4
+ export interface DeploymentStatusOptions {
5
+ readonly endpoint?: string;
6
+ readonly project?: string;
7
+ readonly environment?: string;
8
+ readonly mcpConfig?: boolean;
9
+ }
10
+ export interface DeploymentStatusDependencies extends CloudDeploymentAccessDependencies {
11
+ readonly fetchLive?: typeof fetchLiveDeployment;
12
+ readonly fetchEndpoints?: typeof fetchHostedEndpoints;
13
+ }
14
+ /** Read the current target and its hosted connection details without deploying. */
15
+ export declare function deploymentStatusCommand(options?: DeploymentStatusOptions, dependencies?: DeploymentStatusDependencies): Promise<void>;
16
+ //# sourceMappingURL=deployment-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployment-status.d.ts","sourceRoot":"","sources":["../../src/commands/deployment-status.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,iCAAiC,EACvC,MAAM,qCAAqC,CAAC;AAE7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAGlE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,4BAA6B,SAAQ,iCAAiC;IACrF,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,mBAAmB,CAAC;IAChD,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,oBAAoB,CAAC;CACvD;AAED,mFAAmF;AACnF,wBAAsB,uBAAuB,CAC3C,OAAO,GAAE,uBAA4B,EACrC,YAAY,GAAE,4BAAiC,GAC9C,OAAO,CAAC,IAAI,CAAC,CAyBf"}
@@ -0,0 +1,36 @@
1
+ import { resolveDeploymentCredential, } from '../utils/cloud-deployment-access.js';
2
+ import { deploymentTargetFromOptions } from '../utils/deployment-target.js';
3
+ import { fetchHostedEndpoints } from '../utils/hosted-endpoints.js';
4
+ import { printHostedEndpoints } from '../utils/hosted-endpoints-output.js';
5
+ import { fetchLiveDeployment } from '../utils/live-deployment.js';
6
+ import { logger } from '../utils/logger.js';
7
+ /** Read the current target and its hosted connection details without deploying. */
8
+ export async function deploymentStatusCommand(options = {}, dependencies = {}) {
9
+ const access = await resolveDeploymentCredential(options.endpoint, dependencies);
10
+ const target = deploymentTargetFromOptions(options, access.storedCredential);
11
+ const live = await (dependencies.fetchLive ?? fetchLiveDeployment)({
12
+ endpoint: access.endpoint,
13
+ token: access.token,
14
+ target,
15
+ resource: 'state',
16
+ });
17
+ logger.info(`Target: ${target.project} / ${target.environment}`);
18
+ if (live?.active) {
19
+ logger.success(`Live release: ${live.active.releaseIdentity}`);
20
+ logger.info(`Activated: ${live.active.activatedAt}`);
21
+ if (live.active.restored)
22
+ logger.info('This release was restored from history.');
23
+ }
24
+ else {
25
+ logger.info('No live release yet.');
26
+ }
27
+ const endpoints = await (dependencies.fetchEndpoints ?? fetchHostedEndpoints)({
28
+ endpoint: access.endpoint,
29
+ token: access.token,
30
+ target,
31
+ });
32
+ if (endpoints)
33
+ printHostedEndpoints(endpoints, options.mcpConfig === true);
34
+ else
35
+ logger.warn('Cloud did not return hosted endpoint details.');
36
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"live-source.d.ts","sourceRoot":"","sources":["../../src/commands/live-source.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,iCAAiC,EACvC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,+BAA+B,EAAE,MAAM,wCAAwC,CAAC;AACzF,OAAO,EACL,mBAAmB,EAEpB,MAAM,6BAA6B,CAAC;AAGrC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,WAAY,SAAQ,iBAAiB;IACpD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAuB,SAAQ,iCAAiC;IAC/E,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,mBAAmB,CAAC;IAChD,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,+BAA+B,CAAC;CACjE;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;AAkEF,wBAAsB,WAAW,CAC/B,OAAO,GAAE,WAAgB,EACzB,YAAY,GAAE,sBAA2B,mBAuC1C;AAED,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,iBAAsB,EAC/B,YAAY,GAAE,sBAA2B,GACxC,OAAO,CAAC,SAAS,oBAAoB,EAAE,CAAC,CA0B1C"}
1
+ {"version":3,"file":"live-source.d.ts","sourceRoot":"","sources":["../../src/commands/live-source.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,iCAAiC,EACvC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,+BAA+B,EAAE,MAAM,wCAAwC,CAAC;AAEzF,OAAO,EACL,mBAAmB,EAEpB,MAAM,6BAA6B,CAAC;AAGrC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,WAAY,SAAQ,iBAAiB;IACpD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAuB,SAAQ,iCAAiC;IAC/E,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,mBAAmB,CAAC;IAChD,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,+BAA+B,CAAC;CACjE;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;AA2CF,wBAAsB,WAAW,CAC/B,OAAO,GAAE,WAAgB,EACzB,YAAY,GAAE,sBAA2B,mBAuC1C;AAED,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,iBAAsB,EAC/B,YAAY,GAAE,sBAA2B,GACxC,OAAO,CAAC,SAAS,oBAAoB,EAAE,CAAC,CA0B1C"}
@@ -1,37 +1,19 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import { mkdir, rename, rm, stat, writeFile } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
- import { validateProtocolDeploymentReleaseTarget, } from '@hypequery/protocol';
5
- import { CLOUD_SOURCE_SCOPE, } from '../utils/cloud-credential-store.js';
4
+ import { CLOUD_SOURCE_SCOPE } from '../utils/cloud-credential-store.js';
6
5
  import { resolveDeploymentCredential, } from '../utils/cloud-deployment-access.js';
7
6
  import { captureDeploymentSourceSnapshot } from '../utils/deployment-source-snapshot.js';
7
+ import { deploymentTargetFromOptions } from '../utils/deployment-target.js';
8
8
  import { fetchLiveDeployment, } from '../utils/live-deployment.js';
9
9
  import { logger } from '../utils/logger.js';
10
- function targetFromOptions(options, credential) {
11
- if ((options.project === undefined) !== (options.environment === undefined)) {
12
- throw new Error('Pass both --project and --environment, or omit both.');
13
- }
14
- const input = options.project === undefined
15
- ? credential?.target
16
- : { project: options.project, environment: options.environment };
17
- if (!input) {
18
- throw new Error('Missing deployment target. Run `hypequery login` or pass both '
19
- + '--project and --environment.');
20
- }
21
- try {
22
- return validateProtocolDeploymentReleaseTarget(input);
23
- }
24
- catch {
25
- throw new Error('The deployment target is invalid.');
26
- }
27
- }
28
10
  async function liveSource(options, dependencies) {
29
11
  const access = await resolveDeploymentCredential(options.endpoint, dependencies);
30
12
  if (access.storedCredential
31
13
  && access.storedCredential.scope !== CLOUD_SOURCE_SCOPE) {
32
14
  throw new Error('The stored CLI credential cannot read deployed source. Run `hypequery login` again.');
33
15
  }
34
- const target = targetFromOptions(options, access.storedCredential);
16
+ const target = deploymentTargetFromOptions(options, access.storedCredential);
35
17
  const live = await (dependencies.fetchLive ?? fetchLiveDeployment)({
36
18
  endpoint: access.endpoint,
37
19
  token: access.token,
@@ -1,4 +1,5 @@
1
1
  import { type CloseableMcpServer } from '../utils/mcp-lifecycle.js';
2
+ import { type RemoteMcpCheck } from '../utils/mcp-remote-check.js';
2
3
  export interface McpOptions {
3
4
  /** Analytics directory, matching `hypequery dev --path`. */
4
5
  path?: string;
@@ -6,6 +7,8 @@ export interface McpOptions {
6
7
  tenant?: string;
7
8
  /** Check the entrypoint and exit instead of speaking MCP over stdio. */
8
9
  selfTest?: boolean;
10
+ /** With `selfTest`: check this hosted MCP endpoint instead of a local entrypoint. */
11
+ url?: string;
9
12
  }
10
13
  export interface McpDependencies {
11
14
  loadApi?: (file: string) => Promise<unknown>;
@@ -14,6 +17,13 @@ export interface McpDependencies {
14
17
  analytics: unknown;
15
18
  tenantId?: string;
16
19
  }) => Promise<CloseableMcpServer>;
20
+ /** Loads the optional `@hypequery/mcp` peer; injected in tests. */
21
+ loadMcp?: () => Promise<unknown>;
22
+ checkRemote?: (input: {
23
+ url: string;
24
+ key: string;
25
+ }) => Promise<RemoteMcpCheck>;
26
+ env?: Readonly<Record<string, string | undefined>>;
17
27
  }
18
28
  export declare function mcpCommand(file?: string, options?: McpOptions, dependencies?: McpDependencies): Promise<void>;
19
29
  //# sourceMappingURL=mcp.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAQvF,MAAM,WAAW,UAAU;IACzB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE;QACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClC,SAAS,EAAE,OAAO,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACnC;AAyCD,wBAAsB,UAAU,CAC9B,IAAI,CAAC,EAAE,MAAM,EACb,OAAO,GAAE,UAAe,EACxB,YAAY,GAAE,eAAoB,GACjC,OAAO,CAAC,IAAI,CAAC,CAgDf"}
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAQvF,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnF,MAAM,WAAW,UAAU;IACzB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qFAAqF;IACrF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE;QACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClC,SAAS,EAAE,OAAO,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAClC,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAC/E,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;CACpD;AAoED,wBAAsB,UAAU,CAC9B,IAAI,CAAC,EAAE,MAAM,EACb,OAAO,GAAE,UAAe,EACxB,YAAY,GAAE,eAAoB,GACjC,OAAO,CAAC,IAAI,CAAC,CAkEf"}
@@ -5,6 +5,35 @@ import path from 'node:path';
5
5
  import { findApiFileForPath, findQueriesFile } from '../utils/find-files.js';
6
6
  import { loadApiModule } from '../utils/load-api.js';
7
7
  import { logger } from '../utils/logger.js';
8
+ import { MCP_API_KEY_VARIABLE } from '../utils/hosted-endpoints.js';
9
+ import { checkRemoteMcp } from '../utils/mcp-remote-check.js';
10
+ function fail(message, hints = []) {
11
+ logger.error(message);
12
+ if (hints.length > 0) {
13
+ logger.newline();
14
+ for (const hint of hints)
15
+ logger.indent(hint);
16
+ }
17
+ logger.newline();
18
+ process.exit(1);
19
+ }
20
+ /** `--self-test --url`: initialize and list tools against a hosted endpoint. */
21
+ async function remoteSelfTest(url, dependencies) {
22
+ const key = (dependencies.env ?? process.env)[MCP_API_KEY_VARIABLE]?.trim();
23
+ if (!key) {
24
+ fail(`Set ${MCP_API_KEY_VARIABLE} to a runtime API key to check a hosted endpoint.`, [
25
+ 'Create one in Cloud under Keys. It is read from the environment so it never',
26
+ 'appears in your shell history.',
27
+ ]);
28
+ }
29
+ const result = await (dependencies.checkRemote ?? checkRemoteMcp)({ url, key });
30
+ if (!result.ok)
31
+ fail(`Hosted MCP check failed: ${result.reason}`);
32
+ logger.success(`Connected to ${result.server ?? 'the hosted MCP endpoint'}`);
33
+ logger.info(`Tools: ${result.tools}`);
34
+ logger.newline();
35
+ logger.info('The hosted endpoint is ready for MCP clients. No tool was called.');
36
+ }
8
37
  function entrypointNotFound() {
9
38
  logger.error('Could not find a hypequery API file to serve over MCP');
10
39
  logger.newline();
@@ -41,8 +70,17 @@ function resolveSource(api, file) {
41
70
  return readServeMcpSource(api) ?? noDatasets(file);
42
71
  }
43
72
  export async function mcpCommand(file, options = {}, dependencies = {}) {
73
+ if (options.url && !options.selfTest) {
74
+ fail('--url is only used with --self-test.', [
75
+ 'MCP clients connect to the hosted endpoint directly; this command serves a local project.',
76
+ ]);
77
+ }
44
78
  const restoreConsole = routeConsoleOutputToStderr();
45
79
  try {
80
+ if (options.url) {
81
+ await remoteSelfTest(options.url, dependencies);
82
+ return;
83
+ }
46
84
  const entrypoint = await resolveEntrypoint(file, options);
47
85
  const loadApi = dependencies.loadApi ?? loadApiModule;
48
86
  const source = resolveSource(await loadApi(entrypoint), entrypoint);
@@ -62,6 +100,16 @@ export async function mcpCommand(file, options = {}, dependencies = {}) {
62
100
  }
63
101
  const analytics = source.resolveAnalytics();
64
102
  if (options.selfTest) {
103
+ // Serving needs the optional `@hypequery/mcp` peer. Without this check
104
+ // the self-test passed and the real command then failed on import.
105
+ try {
106
+ await (dependencies.loadMcp ?? (() => import('@hypequery/mcp')))();
107
+ }
108
+ catch {
109
+ fail('@hypequery/mcp is not installed, so this project cannot be served over MCP.', [
110
+ 'npm install @hypequery/mcp',
111
+ ]);
112
+ }
65
113
  logger.success(`Loaded ${path.relative(process.cwd(), entrypoint)}`);
66
114
  logger.info(`Datasets: ${names.join(', ')}`);
67
115
  logger.info(options.tenant
@@ -8,7 +8,7 @@ export declare class ProcessExitError extends Error {
8
8
  constructor(code: number);
9
9
  }
10
10
  export declare function mockProcessExit(): {
11
- exitMock: Mock<(...args: any[]) => any>;
11
+ exitMock: Mock;
12
12
  restore: () => void;
13
13
  };
14
14
  //# sourceMappingURL=test-utils.d.ts.map
@@ -0,0 +1,7 @@
1
+ import { type ProtocolDeploymentReleaseTarget } from '@hypequery/protocol';
2
+ import type { StoredCloudCredential } from './cloud-credential-store.js';
3
+ export declare function deploymentTargetFromOptions(options: {
4
+ readonly project?: string;
5
+ readonly environment?: string;
6
+ }, credential: StoredCloudCredential | undefined): ProtocolDeploymentReleaseTarget;
7
+ //# sourceMappingURL=deployment-target.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployment-target.d.ts","sourceRoot":"","sources":["../../src/utils/deployment-target.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAEzE,wBAAgB,2BAA2B,CACzC,OAAO,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,EACrE,UAAU,EAAE,qBAAqB,GAAG,SAAS,GAC5C,+BAA+B,CAkBjC"}
@@ -0,0 +1,19 @@
1
+ import { validateProtocolDeploymentReleaseTarget, } from '@hypequery/protocol';
2
+ export function deploymentTargetFromOptions(options, credential) {
3
+ if ((options.project === undefined) !== (options.environment === undefined)) {
4
+ throw new Error('Pass both --project and --environment, or omit both.');
5
+ }
6
+ const input = options.project === undefined
7
+ ? credential?.target
8
+ : { project: options.project, environment: options.environment };
9
+ if (!input) {
10
+ throw new Error('Missing deployment target. Run `hypequery login` or pass both '
11
+ + '--project and --environment.');
12
+ }
13
+ try {
14
+ return validateProtocolDeploymentReleaseTarget(input);
15
+ }
16
+ catch {
17
+ throw new Error('The deployment target is invalid.');
18
+ }
19
+ }
@@ -0,0 +1,4 @@
1
+ import { type HostedEndpoints } from './hosted-endpoints.js';
2
+ /** Print Cloud's URLs without ever reading or writing runtime API keys. */
3
+ export declare function printHostedEndpoints(endpoints: HostedEndpoints, mcpConfig: boolean): void;
4
+ //# sourceMappingURL=hosted-endpoints-output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hosted-endpoints-output.d.ts","sourceRoot":"","sources":["../../src/utils/hosted-endpoints-output.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AAE/B,2EAA2E;AAC3E,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,eAAe,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAqBzF"}
@@ -0,0 +1,27 @@
1
+ import { logger } from './logger.js';
2
+ import { MCP_API_KEY_VARIABLE, mcpClientConfiguration, } from './hosted-endpoints.js';
3
+ /** Print Cloud's URLs without ever reading or writing runtime API keys. */
4
+ export function printHostedEndpoints(endpoints, mcpConfig) {
5
+ logger.newline();
6
+ logger.info('Hosted endpoints');
7
+ logger.indent(`REST ${endpoints.rest.baseUrl}`);
8
+ const width = Math.max(0, ...endpoints.rest.datasets.map((dataset) => dataset.name.length));
9
+ for (const dataset of endpoints.rest.datasets) {
10
+ logger.indent(` ${dataset.name.padEnd(width)} POST ${dataset.url}`);
11
+ }
12
+ logger.indent(`MCP ${endpoints.mcp.url}`);
13
+ logger.newline();
14
+ if (endpoints.active) {
15
+ logger.info(`Set ${MCP_API_KEY_VARIABLE} from your secret store. Check MCP without running a query:`);
16
+ logger.indent(`hypequery mcp --self-test --url ${endpoints.mcp.url}`);
17
+ }
18
+ else {
19
+ logger.info('These URLs will serve the target after a release is live.');
20
+ }
21
+ if (mcpConfig) {
22
+ logger.newline();
23
+ logger.info(`MCP client configuration (the client reads the key from ${MCP_API_KEY_VARIABLE}):`);
24
+ for (const line of mcpClientConfiguration(endpoints.mcp.url).split('\n'))
25
+ logger.indent(line);
26
+ }
27
+ }
@@ -0,0 +1,37 @@
1
+ import type { ProtocolDeploymentReleaseTarget } from '@hypequery/protocol';
2
+ import { type LiveDeploymentFetch } from './live-deployment.js';
3
+ /** Where Cloud serves a deployed target. Cloud owns this layout, not the CLI. */
4
+ export type HostedEndpoints = {
5
+ readonly active: boolean;
6
+ readonly rest: {
7
+ readonly baseUrl: string;
8
+ readonly datasets: readonly {
9
+ readonly name: string;
10
+ readonly url: string;
11
+ }[];
12
+ };
13
+ readonly mcp: {
14
+ readonly url: string;
15
+ };
16
+ };
17
+ export declare function parseHostedEndpoints(input: unknown): HostedEndpoints | undefined;
18
+ /**
19
+ * Asks Cloud where the target is served. Never throws: the deployment has
20
+ * already succeeded, so an older Cloud (404), a network failure, or an
21
+ * unexpected response just means there is nothing to print.
22
+ */
23
+ export declare function fetchHostedEndpoints(input: {
24
+ readonly endpoint: string;
25
+ readonly token: string;
26
+ readonly target: ProtocolDeploymentReleaseTarget;
27
+ readonly fetch?: LiveDeploymentFetch;
28
+ }): Promise<HostedEndpoints | undefined>;
29
+ /** The environment variable generated configuration reads the key from. */
30
+ export declare const MCP_API_KEY_VARIABLE = "HYPEQUERY_API_KEY";
31
+ /**
32
+ * Client configuration for the hosted MCP endpoint. The key is a placeholder
33
+ * the client expands from its environment: the CLI never writes a credential
34
+ * into anything it prints.
35
+ */
36
+ export declare function mcpClientConfiguration(mcpUrl: string): string;
37
+ //# sourceMappingURL=hosted-endpoints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hosted-endpoints.d.ts","sourceRoot":"","sources":["../../src/utils/hosted-endpoints.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAW,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAIzE,iFAAiF;AACjF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE;QACb,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,QAAQ,EAAE,SAAS;YAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;KAC/E,CAAC;IACF,QAAQ,CAAC,GAAG,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;CACxC,CAAC;AAYF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAkBhF;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,EAAE;IAChD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,QAAQ,CAAC,KAAK,CAAC,EAAE,mBAAmB,CAAC;CACtC,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAiBvC;AAED,2EAA2E;AAC3E,eAAO,MAAM,oBAAoB,sBAAsB,CAAC;AAExD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAc7D"}
@@ -0,0 +1,81 @@
1
+ import { liveUrl } from './live-deployment.js';
2
+ const MAX_RESPONSE_BYTES = 256 * 1024;
3
+ function httpUrl(value) {
4
+ if (typeof value !== 'string')
5
+ return undefined;
6
+ try {
7
+ const url = new URL(value);
8
+ return url.protocol === 'https:' || url.protocol === 'http:' ? url.toString() : undefined;
9
+ }
10
+ catch {
11
+ return undefined;
12
+ }
13
+ }
14
+ export function parseHostedEndpoints(input) {
15
+ if (typeof input !== 'object' || input === null)
16
+ return undefined;
17
+ const value = input;
18
+ if (value.kind !== 'hypequery-hosted-endpoints' || value.version !== 1)
19
+ return undefined;
20
+ const baseUrl = httpUrl(value.rest?.baseUrl);
21
+ const mcp = httpUrl(value.mcp?.url);
22
+ if (!baseUrl || !mcp || !Array.isArray(value.rest?.datasets))
23
+ return undefined;
24
+ const datasets = [];
25
+ for (const dataset of value.rest.datasets) {
26
+ const url = httpUrl(dataset?.url);
27
+ if (typeof dataset?.name !== 'string' || !url)
28
+ return undefined;
29
+ datasets.push(Object.freeze({ name: dataset.name, url }));
30
+ }
31
+ return Object.freeze({
32
+ active: value.active === true,
33
+ rest: Object.freeze({ baseUrl, datasets: Object.freeze(datasets) }),
34
+ mcp: Object.freeze({ url: mcp }),
35
+ });
36
+ }
37
+ /**
38
+ * Asks Cloud where the target is served. Never throws: the deployment has
39
+ * already succeeded, so an older Cloud (404), a network failure, or an
40
+ * unexpected response just means there is nothing to print.
41
+ */
42
+ export async function fetchHostedEndpoints(input) {
43
+ try {
44
+ const url = liveUrl(input.endpoint, input.target, 'endpoints');
45
+ if (!url)
46
+ return undefined;
47
+ const response = await (input.fetch ?? fetch)(url, {
48
+ method: 'GET',
49
+ headers: { Accept: 'application/json', Authorization: `Bearer ${input.token}` },
50
+ redirect: 'error',
51
+ signal: AbortSignal.timeout(10_000),
52
+ });
53
+ if (!response.ok)
54
+ return undefined;
55
+ const bytes = new Uint8Array(await response.arrayBuffer());
56
+ if (bytes.byteLength > MAX_RESPONSE_BYTES)
57
+ return undefined;
58
+ return parseHostedEndpoints(JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes)));
59
+ }
60
+ catch {
61
+ return undefined;
62
+ }
63
+ }
64
+ /** The environment variable generated configuration reads the key from. */
65
+ export const MCP_API_KEY_VARIABLE = 'HYPEQUERY_API_KEY';
66
+ /**
67
+ * Client configuration for the hosted MCP endpoint. The key is a placeholder
68
+ * the client expands from its environment: the CLI never writes a credential
69
+ * into anything it prints.
70
+ */
71
+ export function mcpClientConfiguration(mcpUrl) {
72
+ return JSON.stringify({
73
+ mcpServers: {
74
+ hypequery: {
75
+ type: 'http',
76
+ url: mcpUrl,
77
+ headers: { Authorization: `Bearer \${${MCP_API_KEY_VARIABLE}}` },
78
+ },
79
+ },
80
+ }, null, 2);
81
+ }
@@ -26,6 +26,12 @@ export type LiveDeployment = {
26
26
  };
27
27
  };
28
28
  export type LiveDeploymentFetch = typeof fetch;
29
+ /**
30
+ * The Cloud target resource beside a submissions endpoint, or undefined when
31
+ * the endpoint is not a Cloud submissions URL. Refuses anything but https (or
32
+ * loopback http) so a credential is never sent in the clear.
33
+ */
34
+ export declare function liveUrl(endpoint: string, target: ProtocolDeploymentReleaseTarget, resource: 'state' | 'source' | 'endpoints'): string | undefined;
29
35
  export declare function fetchLiveDeployment(input: {
30
36
  readonly endpoint: string;
31
37
  readonly token: string;
@@ -1 +1 @@
1
- {"version":3,"file":"live-deployment.d.ts","sourceRoot":"","sources":["../../src/utils/live-deployment.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,+BAA+B,EACrC,MAAM,qBAAqB,CAAC;AAO7B,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,wBAAwB,EAAE,CAAC;IACpD,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAClB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;QACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;KACzB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,IAAI,GAAG;QACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;QACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;QAC3B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;QAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC;KACxC,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,OAAO,KAAK,CAAC;AA8K/C,wBAAsB,mBAAmB,CAAC,KAAK,EAAE;IAC/C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,QAAQ,CAAC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,mBAAmB,CAAC;CACtC,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAuCtC"}
1
+ {"version":3,"file":"live-deployment.d.ts","sourceRoot":"","sources":["../../src/utils/live-deployment.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,+BAA+B,EACrC,MAAM,qBAAqB,CAAC;AAO7B,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,wBAAwB,EAAE,CAAC;IACpD,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAClB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;QACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;KACzB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,IAAI,GAAG;QACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;QACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;QAC3B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;QAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC;KACxC,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,OAAO,KAAK,CAAC;AAE/C;;;;GAIG;AACH,wBAAgB,OAAO,CACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,+BAA+B,EACvC,QAAQ,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GACzC,MAAM,GAAG,SAAS,CAkBpB;AAsJD,wBAAsB,mBAAmB,CAAC,KAAK,EAAE;IAC/C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,QAAQ,CAAC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,mBAAmB,CAAC;CACtC,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAuCtC"}
@@ -3,7 +3,12 @@ import path from 'node:path';
3
3
  import { DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS, validateProtocolDeploymentReleaseTarget, } from '@hypequery/protocol';
4
4
  const SHA256_PATTERN = /^[0-9a-f]{64}$/;
5
5
  const MAX_RESPONSE_BYTES = Math.ceil(DEFAULT_PROTOCOL_DEPLOYMENT_BUNDLE_LIMITS.maxSourceBytes * 1.4) + 1_000_000;
6
- function liveUrl(endpoint, target, resource) {
6
+ /**
7
+ * The Cloud target resource beside a submissions endpoint, or undefined when
8
+ * the endpoint is not a Cloud submissions URL. Refuses anything but https (or
9
+ * loopback http) so a credential is never sent in the clear.
10
+ */
11
+ export function liveUrl(endpoint, target, resource) {
7
12
  const url = new URL(endpoint);
8
13
  if (!/\/v1\/deployments\/submissions\/?$/.test(url.pathname)) {
9
14
  return undefined;
@@ -0,0 +1,19 @@
1
+ export type RemoteMcpCheck = {
2
+ readonly ok: true;
3
+ readonly tools: number;
4
+ readonly server?: string;
5
+ } | {
6
+ readonly ok: false;
7
+ readonly reason: string;
8
+ };
9
+ /**
10
+ * A non-destructive check of a hosted MCP endpoint: initialize, then list
11
+ * tools. It never calls a tool, so it runs no query and changes nothing.
12
+ * The key is sent only as a bearer header and never included in the result.
13
+ */
14
+ export declare function checkRemoteMcp(input: {
15
+ readonly url: string;
16
+ readonly key: string;
17
+ readonly fetch?: typeof fetch;
18
+ }): Promise<RemoteMcpCheck>;
19
+ //# sourceMappingURL=mcp-remote-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-remote-check.d.ts","sourceRoot":"","sources":["../../src/utils/mcp-remote-check.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACvE;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAkCpD;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE;IAC1C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CAC/B,GAAG,OAAO,CAAC,cAAc,CAAC,CAsD1B"}
@@ -0,0 +1,96 @@
1
+ import { readMcpResponse } from './mcp-response.js';
2
+ const PROTOCOL_VERSION = '2025-06-18';
3
+ const TIMEOUT_MS = 15_000;
4
+ const SUPPORTED_PROTOCOL_VERSIONS = new Set(['2025-03-26', PROTOCOL_VERSION, '2025-11-25']);
5
+ async function rpc(fetchFn, url, key, message, session, protocolVersion = PROTOCOL_VERSION) {
6
+ const response = await fetchFn(url, {
7
+ method: 'POST',
8
+ headers: {
9
+ authorization: `Bearer ${key}`,
10
+ 'content-type': 'application/json',
11
+ accept: 'application/json, text/event-stream',
12
+ 'mcp-protocol-version': protocolVersion,
13
+ ...(session ? { 'mcp-session-id': session } : {}),
14
+ },
15
+ body: JSON.stringify(message),
16
+ redirect: 'error',
17
+ signal: AbortSignal.timeout(TIMEOUT_MS),
18
+ });
19
+ const body = await readMcpResponse(response, message.id);
20
+ return { response, body };
21
+ }
22
+ function refusal(status) {
23
+ if (status === 401)
24
+ return 'the API key was refused (401). Check HYPEQUERY_API_KEY.';
25
+ if (status === 403)
26
+ return 'the API key may not use MCP here (403). Check its scopes.';
27
+ if (status === 404)
28
+ return 'no MCP endpoint is published at this URL (404).';
29
+ return `the endpoint answered HTTP ${status}.`;
30
+ }
31
+ /**
32
+ * A non-destructive check of a hosted MCP endpoint: initialize, then list
33
+ * tools. It never calls a tool, so it runs no query and changes nothing.
34
+ * The key is sent only as a bearer header and never included in the result.
35
+ */
36
+ export async function checkRemoteMcp(input) {
37
+ const fetchFn = input.fetch ?? fetch;
38
+ let url;
39
+ try {
40
+ url = new URL(input.url);
41
+ }
42
+ catch {
43
+ return { ok: false, reason: 'the URL is not valid.' };
44
+ }
45
+ const loopback = url.hostname === 'localhost' || url.hostname === '127.0.0.1';
46
+ if (url.protocol !== 'https:' && !(url.protocol === 'http:' && loopback)) {
47
+ return { ok: false, reason: 'the URL must use https, so the API key is not sent in the clear.' };
48
+ }
49
+ try {
50
+ const init = await rpc(fetchFn, url.href, input.key, {
51
+ jsonrpc: '2.0',
52
+ id: 1,
53
+ method: 'initialize',
54
+ params: {
55
+ protocolVersion: PROTOCOL_VERSION,
56
+ capabilities: {},
57
+ clientInfo: { name: 'hypequery-cli-self-test', version: '1.0.0' },
58
+ },
59
+ });
60
+ if (!init.response.ok)
61
+ return { ok: false, reason: refusal(init.response.status) };
62
+ if (!init.body?.result)
63
+ return { ok: false, reason: 'initialize returned no result.' };
64
+ const protocolVersion = init.body.result.protocolVersion;
65
+ if (typeof protocolVersion !== 'string' || !SUPPORTED_PROTOCOL_VERSIONS.has(protocolVersion)) {
66
+ return { ok: false, reason: 'initialize returned an unsupported protocol version.' };
67
+ }
68
+ const session = init.response.headers.get('mcp-session-id') ?? undefined;
69
+ const initialized = await rpc(fetchFn, url.href, input.key, {
70
+ jsonrpc: '2.0',
71
+ method: 'notifications/initialized',
72
+ }, session, protocolVersion);
73
+ if (!initialized.response.ok)
74
+ return { ok: false, reason: refusal(initialized.response.status) };
75
+ const listed = await rpc(fetchFn, url.href, input.key, {
76
+ jsonrpc: '2.0',
77
+ id: 2,
78
+ method: 'tools/list',
79
+ }, session, protocolVersion);
80
+ if (!listed.response.ok)
81
+ return { ok: false, reason: refusal(listed.response.status) };
82
+ const tools = listed.body?.result?.tools;
83
+ if (!Array.isArray(tools))
84
+ return { ok: false, reason: 'tools/list returned no tools.' };
85
+ const server = init.body.result.serverInfo?.name;
86
+ return { ok: true, tools: tools.length, ...(typeof server === 'string' ? { server } : {}) };
87
+ }
88
+ catch (error) {
89
+ return {
90
+ ok: false,
91
+ reason: error?.name === 'TimeoutError'
92
+ ? 'the endpoint did not answer in time.'
93
+ : 'the endpoint could not be reached.',
94
+ };
95
+ }
96
+ }
@@ -0,0 +1,3 @@
1
+ /** Read a JSON-RPC response, including responses delivered over an SSE stream. */
2
+ export declare function readMcpResponse(response: Response, requestId: unknown): Promise<Record<string, any> | null>;
3
+ //# sourceMappingURL=mcp-response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-response.d.ts","sourceRoot":"","sources":["../../src/utils/mcp-response.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,wBAAsB,eAAe,CACnC,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,OAAO,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CAiDrC"}
@@ -0,0 +1,58 @@
1
+ /** Read a JSON-RPC response, including responses delivered over an SSE stream. */
2
+ export async function readMcpResponse(response, requestId) {
3
+ if (!response.ok || requestId === undefined) {
4
+ await response.body?.cancel();
5
+ return null;
6
+ }
7
+ const contentType = response.headers.get('content-type')?.split(';')[0]?.trim().toLowerCase();
8
+ if (contentType !== 'text/event-stream') {
9
+ try {
10
+ const body = await response.json();
11
+ return body?.jsonrpc === '2.0' && body.id === requestId ? body : null;
12
+ }
13
+ catch (error) {
14
+ if (error?.name === 'TimeoutError' || error?.name === 'AbortError') {
15
+ throw error;
16
+ }
17
+ return null;
18
+ }
19
+ }
20
+ if (!response.body)
21
+ return null;
22
+ const reader = response.body.getReader();
23
+ const decoder = new TextDecoder();
24
+ let pending = '';
25
+ let data = [];
26
+ try {
27
+ for (;;) {
28
+ const chunk = await reader.read();
29
+ pending += decoder.decode(chunk.value, { stream: !chunk.done });
30
+ let newline;
31
+ while ((newline = /\r\n|\r|\n/.exec(pending))) {
32
+ // A CR at the chunk boundary may be the first half of CRLF.
33
+ if (!chunk.done && newline[0] === '\r' && newline.index === pending.length - 1)
34
+ break;
35
+ const line = pending.slice(0, newline.index);
36
+ pending = pending.slice(newline.index + newline[0].length);
37
+ if (line === '') {
38
+ if (data.length > 0) {
39
+ const body = JSON.parse(data.join('\n'));
40
+ data = [];
41
+ if (body?.jsonrpc === '2.0' && body.id === requestId
42
+ && ('result' in body || 'error' in body))
43
+ return body;
44
+ }
45
+ }
46
+ else if (line === 'data' || line.startsWith('data:')) {
47
+ data.push(line === 'data' ? '' : line.slice(5).replace(/^ /, ''));
48
+ }
49
+ }
50
+ if (chunk.done)
51
+ return null;
52
+ }
53
+ }
54
+ finally {
55
+ await reader.cancel();
56
+ reader.releaseLock();
57
+ }
58
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/cli",
3
- "version": "1.21.1",
3
+ "version": "1.22.0",
4
4
  "description": "CLI for scaffolding type-safe ClickHouse analytics, semantic datasets, React manifests, and deployments",
5
5
  "keywords": [
6
6
  "clickhouse",
@@ -22,16 +22,16 @@
22
22
  "dist"
23
23
  ],
24
24
  "dependencies": {
25
- "@clickhouse/client": "^1.18.3",
25
+ "@clickhouse/client": "^1.23.1",
26
26
  "chalk": "^5.3.0",
27
27
  "commander": "^12.0.0",
28
28
  "dotenv": "^16.4.7",
29
- "esbuild": "^0.25.0",
29
+ "esbuild": "^0.28.2",
30
30
  "open": "^10.0.0",
31
31
  "ora": "^8.0.1",
32
32
  "prompts": "^2.4.2",
33
- "@hypequery/deployment": "0.9.2",
34
- "@hypequery/protocol": "0.16.0"
33
+ "@hypequery/deployment": "0.9.4",
34
+ "@hypequery/protocol": "0.18.0"
35
35
  },
36
36
  "optionalDependencies": {
37
37
  "@napi-rs/keyring": "1.3.0"
@@ -48,11 +48,11 @@
48
48
  "devDependencies": {
49
49
  "@types/node": "^20.11.30",
50
50
  "@types/prompts": "^2.4.9",
51
- "@vitest/coverage-v8": "^3.2.6",
51
+ "@vitest/coverage-v8": "^5.0.2",
52
52
  "typescript": "^5.7.3",
53
- "vitest": "^3.2.6",
54
- "@hypequery/mcp": "0.8.3",
55
- "@hypequery/serve": "0.19.1"
53
+ "vitest": "^5.0.2",
54
+ "@hypequery/mcp": "0.8.5",
55
+ "@hypequery/serve": "0.19.3"
56
56
  },
57
57
  "repository": {
58
58
  "type": "git",
@@ -69,7 +69,7 @@
69
69
  "scripts": {
70
70
  "build": "tsc --project tsconfig.json",
71
71
  "dev": "tsc --project tsconfig.json --watch",
72
- "lint": "eslint --ext .ts \"src/**/*.ts\"",
72
+ "lint": "eslint \"src/**/*.ts\"",
73
73
  "test": "vitest run"
74
74
  }
75
75
  }