@hypequery/cli 1.21.2 → 1.23.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 +1 -0
- package/dist/bin/cli.js +77 -12
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +33 -15
- package/dist/commands/deploy.d.ts +5 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +10 -0
- package/dist/commands/deployment-status.d.ts +16 -0
- package/dist/commands/deployment-status.d.ts.map +1 -0
- package/dist/commands/deployment-status.js +36 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +25 -5
- package/dist/commands/generate-datasets.d.ts +0 -6
- package/dist/commands/generate-datasets.d.ts.map +1 -1
- package/dist/commands/generate-datasets.js +17 -1
- package/dist/commands/generate-manifest.d.ts.map +1 -1
- package/dist/commands/generate-manifest.js +4 -0
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +14 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +80 -55
- package/dist/commands/live-source.d.ts.map +1 -1
- package/dist/commands/live-source.js +3 -21
- package/dist/commands/mcp.d.ts +10 -0
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +93 -8
- package/dist/commands/telemetry.d.ts +3 -0
- package/dist/commands/telemetry.d.ts.map +1 -0
- package/dist/commands/telemetry.js +19 -0
- package/dist/generators/chdb.d.ts +3 -0
- package/dist/generators/chdb.d.ts.map +1 -1
- package/dist/generators/chdb.js +2 -0
- package/dist/generators/clickhouse.d.ts +3 -1
- package/dist/generators/clickhouse.d.ts.map +1 -1
- package/dist/generators/clickhouse.js +2 -0
- package/dist/test-utils.d.ts +1 -1
- package/dist/typegen/generate-types.d.ts +3 -0
- package/dist/typegen/generate-types.d.ts.map +1 -1
- package/dist/typegen/generate-types.js +5 -1
- package/dist/typegen/type-parsing.d.ts +1 -1
- package/dist/typegen/type-parsing.d.ts.map +1 -1
- package/dist/typegen/type-parsing.js +11 -7
- package/dist/utils/chdb-gitignore-entry.d.ts +2 -0
- package/dist/utils/chdb-gitignore-entry.d.ts.map +1 -0
- package/dist/utils/chdb-gitignore-entry.js +17 -0
- package/dist/utils/cli-version.d.ts +2 -0
- package/dist/utils/cli-version.d.ts.map +1 -0
- package/dist/utils/cli-version.js +11 -0
- package/dist/utils/cloud-credential-store.d.ts.map +1 -1
- package/dist/utils/cloud-credential-store.js +2 -13
- package/dist/utils/command-exit.d.ts +13 -0
- package/dist/utils/command-exit.d.ts.map +1 -0
- package/dist/utils/command-exit.js +31 -0
- package/dist/utils/config-directory.d.ts +3 -0
- package/dist/utils/config-directory.d.ts.map +1 -0
- package/dist/utils/config-directory.js +14 -0
- package/dist/utils/deployment-target.d.ts +7 -0
- package/dist/utils/deployment-target.d.ts.map +1 -0
- package/dist/utils/deployment-target.js +19 -0
- package/dist/utils/detect-database-environment.d.ts +3 -0
- package/dist/utils/detect-database-environment.d.ts.map +1 -0
- package/dist/utils/detect-database-environment.js +8 -0
- package/dist/utils/detect-database.d.ts.map +1 -1
- package/dist/utils/detect-database.js +4 -8
- package/dist/utils/hosted-endpoints-output.d.ts +4 -0
- package/dist/utils/hosted-endpoints-output.d.ts.map +1 -0
- package/dist/utils/hosted-endpoints-output.js +27 -0
- package/dist/utils/hosted-endpoints.d.ts +37 -0
- package/dist/utils/hosted-endpoints.d.ts.map +1 -0
- package/dist/utils/hosted-endpoints.js +81 -0
- package/dist/utils/init-options.d.ts +11 -0
- package/dist/utils/init-options.d.ts.map +1 -0
- package/dist/utils/init-options.js +28 -0
- package/dist/utils/live-deployment.d.ts +6 -0
- package/dist/utils/live-deployment.d.ts.map +1 -1
- package/dist/utils/live-deployment.js +6 -1
- package/dist/utils/load-api.d.ts +2 -0
- package/dist/utils/load-api.d.ts.map +1 -1
- package/dist/utils/load-api.js +50 -51
- package/dist/utils/load-env-files.d.ts +10 -0
- package/dist/utils/load-env-files.d.ts.map +1 -0
- package/dist/utils/load-env-files.js +27 -0
- package/dist/utils/mcp-remote-check.d.ts +19 -0
- package/dist/utils/mcp-remote-check.d.ts.map +1 -0
- package/dist/utils/mcp-remote-check.js +96 -0
- package/dist/utils/mcp-response.d.ts +3 -0
- package/dist/utils/mcp-response.d.ts.map +1 -0
- package/dist/utils/mcp-response.js +58 -0
- package/dist/utils/project-root.d.ts +14 -0
- package/dist/utils/project-root.d.ts.map +1 -0
- package/dist/utils/project-root.js +41 -0
- package/dist/utils/telemetry/buckets.d.ts +5 -0
- package/dist/utils/telemetry/buckets.d.ts.map +1 -0
- package/dist/utils/telemetry/buckets.js +23 -0
- package/dist/utils/telemetry/catalog-docs.d.ts +3 -0
- package/dist/utils/telemetry/catalog-docs.d.ts.map +1 -0
- package/dist/utils/telemetry/catalog-docs.js +52 -0
- package/dist/utils/telemetry/catalog.d.ts +1124 -0
- package/dist/utils/telemetry/catalog.d.ts.map +1 -0
- package/dist/utils/telemetry/catalog.js +154 -0
- package/dist/utils/telemetry/command-context.d.ts +17 -0
- package/dist/utils/telemetry/command-context.d.ts.map +1 -0
- package/dist/utils/telemetry/command-context.js +30 -0
- package/dist/utils/telemetry/config-schema.d.ts +12 -0
- package/dist/utils/telemetry/config-schema.d.ts.map +1 -0
- package/dist/utils/telemetry/config-schema.js +33 -0
- package/dist/utils/telemetry/config-store.d.ts +35 -0
- package/dist/utils/telemetry/config-store.d.ts.map +1 -0
- package/dist/utils/telemetry/config-store.js +147 -0
- package/dist/utils/telemetry/delivery.d.ts +13 -0
- package/dist/utils/telemetry/delivery.d.ts.map +1 -0
- package/dist/utils/telemetry/delivery.js +26 -0
- package/dist/utils/telemetry/detached-dispatch.d.ts +12 -0
- package/dist/utils/telemetry/detached-dispatch.d.ts.map +1 -0
- package/dist/utils/telemetry/detached-dispatch.js +33 -0
- package/dist/utils/telemetry/detached-sender.d.ts +2 -0
- package/dist/utils/telemetry/detached-sender.d.ts.map +1 -0
- package/dist/utils/telemetry/detached-sender.js +31 -0
- package/dist/utils/telemetry/dev-session.d.ts +15 -0
- package/dist/utils/telemetry/dev-session.d.ts.map +1 -0
- package/dist/utils/telemetry/dev-session.js +25 -0
- package/dist/utils/telemetry/domains.d.ts +69 -0
- package/dist/utils/telemetry/domains.d.ts.map +1 -0
- package/dist/utils/telemetry/domains.js +64 -0
- package/dist/utils/telemetry/endpoint.d.ts +3 -0
- package/dist/utils/telemetry/endpoint.d.ts.map +1 -0
- package/dist/utils/telemetry/endpoint.js +15 -0
- package/dist/utils/telemetry/environment.d.ts +27 -0
- package/dist/utils/telemetry/environment.d.ts.map +1 -0
- package/dist/utils/telemetry/environment.js +95 -0
- package/dist/utils/telemetry/error-code.d.ts +4 -0
- package/dist/utils/telemetry/error-code.d.ts.map +1 -0
- package/dist/utils/telemetry/error-code.js +57 -0
- package/dist/utils/telemetry/flags.d.ts +5 -0
- package/dist/utils/telemetry/flags.d.ts.map +1 -0
- package/dist/utils/telemetry/flags.js +41 -0
- package/dist/utils/telemetry/generation-metrics.d.ts +15 -0
- package/dist/utils/telemetry/generation-metrics.d.ts.map +1 -0
- package/dist/utils/telemetry/generation-metrics.js +35 -0
- package/dist/utils/telemetry/identity.d.ts +12 -0
- package/dist/utils/telemetry/identity.d.ts.map +1 -0
- package/dist/utils/telemetry/identity.js +42 -0
- package/dist/utils/telemetry/init-funnel.d.ts +15 -0
- package/dist/utils/telemetry/init-funnel.d.ts.map +1 -0
- package/dist/utils/telemetry/init-funnel.js +25 -0
- package/dist/utils/telemetry/invocation.d.ts +22 -0
- package/dist/utils/telemetry/invocation.d.ts.map +1 -0
- package/dist/utils/telemetry/invocation.js +46 -0
- package/dist/utils/telemetry/lifecycle.d.ts +32 -0
- package/dist/utils/telemetry/lifecycle.d.ts.map +1 -0
- package/dist/utils/telemetry/lifecycle.js +99 -0
- package/dist/utils/telemetry/mcp-session.d.ts +10 -0
- package/dist/utils/telemetry/mcp-session.d.ts.map +1 -0
- package/dist/utils/telemetry/mcp-session.js +36 -0
- package/dist/utils/telemetry/notice.d.ts +11 -0
- package/dist/utils/telemetry/notice.d.ts.map +1 -0
- package/dist/utils/telemetry/notice.js +24 -0
- package/dist/utils/telemetry/opt-out.d.ts +16 -0
- package/dist/utils/telemetry/opt-out.d.ts.map +1 -0
- package/dist/utils/telemetry/opt-out.js +32 -0
- package/dist/utils/telemetry/post-json.d.ts +13 -0
- package/dist/utils/telemetry/post-json.d.ts.map +1 -0
- package/dist/utils/telemetry/post-json.js +32 -0
- package/dist/utils/telemetry/project-context.d.ts +15 -0
- package/dist/utils/telemetry/project-context.d.ts.map +1 -0
- package/dist/utils/telemetry/project-context.js +86 -0
- package/dist/utils/telemetry/schema.d.ts +72 -0
- package/dist/utils/telemetry/schema.d.ts.map +1 -0
- package/dist/utils/telemetry/schema.js +17 -0
- package/dist/utils/telemetry/session-config.d.ts +6 -0
- package/dist/utils/telemetry/session-config.d.ts.map +1 -0
- package/dist/utils/telemetry/session-config.js +17 -0
- package/dist/utils/telemetry/settings-lock.d.ts +36 -0
- package/dist/utils/telemetry/settings-lock.d.ts.map +1 -0
- package/dist/utils/telemetry/settings-lock.js +102 -0
- package/dist/utils/telemetry/transport.d.ts +25 -0
- package/dist/utils/telemetry/transport.d.ts.map +1 -0
- package/dist/utils/telemetry/transport.js +76 -0
- package/dist/utils/telemetry/validation.d.ts +10 -0
- package/dist/utils/telemetry/validation.d.ts.map +1 -0
- package/dist/utils/telemetry/validation.js +77 -0
- package/dist/utils/telemetry/value-formats.d.ts +30 -0
- package/dist/utils/telemetry/value-formats.d.ts.map +1 -0
- package/dist/utils/telemetry/value-formats.js +13 -0
- package/package.json +16 -12
|
@@ -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
|
-
|
|
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;
|
package/dist/utils/load-api.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export declare function loadApiModule(modulePath: string): Promise<any>;
|
|
2
2
|
export declare function loadModule(modulePath: string): Promise<any>;
|
|
3
|
+
/** Remove this process's bundles; the CLI awaits this before it exits. */
|
|
4
|
+
export declare function cleanupLoadedApiArtifacts(): Promise<void>;
|
|
3
5
|
export declare function findNearestTsconfig(filePath: string): Promise<string | null>;
|
|
4
6
|
//# sourceMappingURL=load-api.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-api.d.ts","sourceRoot":"","sources":["../../src/utils/load-api.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"load-api.d.ts","sourceRoot":"","sources":["../../src/utils/load-api.ts"],"names":[],"mappings":"AAgBA,wBAAsB,aAAa,CAAC,UAAU,EAAE,MAAM,gBA2DrD;AAED,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,gBAwClD;AAuED,0EAA0E;AAC1E,wBAAsB,yBAAyB,kBAE9C;AAqED,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,0BA8BzD"}
|
package/dist/utils/load-api.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { pathToFileURL } from 'node:url';
|
|
2
|
-
import {
|
|
2
|
+
import { rmdirSync, rmSync } from 'node:fs';
|
|
3
|
+
import { access, mkdtemp, mkdir, writeFile } from 'node:fs/promises';
|
|
3
4
|
import os from 'node:os';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
import { build } from 'esbuild';
|
|
7
|
+
import { loadEnvFiles } from './load-env-files.js';
|
|
8
|
+
import { findProjectRoot } from './project-root.js';
|
|
6
9
|
if (typeof process.setMaxListeners === 'function') {
|
|
7
10
|
process.setMaxListeners(0);
|
|
8
11
|
}
|
|
@@ -68,6 +71,11 @@ export async function loadModule(modulePath) {
|
|
|
68
71
|
const relativePath = path.relative(process.cwd(), resolved);
|
|
69
72
|
throw new Error(`File not found: ${relativePath}`);
|
|
70
73
|
}
|
|
74
|
+
// The CLI loaded the launch directory's env files at startup. An entry in
|
|
75
|
+
// another project (`hypequery mcp /abs/path/api.ts`) reads its connection
|
|
76
|
+
// settings from that project's env files, so load those too before its
|
|
77
|
+
// client module runs. Values already set keep precedence.
|
|
78
|
+
await loadEnvFiles(await findProjectRoot(resolved));
|
|
71
79
|
const extension = path.extname(resolved).toLowerCase();
|
|
72
80
|
const isTypeScript = TYPESCRIPT_EXTENSIONS.has(extension);
|
|
73
81
|
const moduleUrl = isTypeScript
|
|
@@ -92,23 +100,26 @@ export async function loadModule(modulePath) {
|
|
|
92
100
|
}
|
|
93
101
|
}
|
|
94
102
|
const globalState = globalThis;
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
const
|
|
103
|
+
// One bundle directory per project root, keyed by that root.
|
|
104
|
+
const tempDirPromises = globalState.__hypequeryCliTempDirPromises ??= new Map();
|
|
105
|
+
const tempFiles = globalState.__hypequeryCliTempFiles ??= new Set();
|
|
106
|
+
const tempDirs = globalState.__hypequeryCliTempDirs ??= new Set();
|
|
107
|
+
const tempRoots = globalState.__hypequeryCliTempRoots ??= new Set();
|
|
98
108
|
let cleanupHooksInstalled = globalState.__hypequeryCliCleanupInstalled ?? false;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
function ensureTempDir() {
|
|
109
|
+
/**
|
|
110
|
+
* The bundle has to live inside the entry's project: its bare imports
|
|
111
|
+
* (`@hypequery/serve`, ...) resolve from the bundle's location, and the
|
|
112
|
+
* process's working directory may be anywhere (MCP clients launch from `/`).
|
|
113
|
+
*/
|
|
114
|
+
function ensureTempDir(projectRoot) {
|
|
106
115
|
installCleanupHooks();
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
let promise = tempDirPromises.get(projectRoot);
|
|
117
|
+
if (!promise) {
|
|
118
|
+
promise = (async () => {
|
|
119
|
+
const projectTempRoot = path.join(projectRoot, '.hypequery', 'tmp');
|
|
110
120
|
try {
|
|
111
121
|
await mkdir(projectTempRoot, { recursive: true });
|
|
122
|
+
tempRoots.add(projectTempRoot);
|
|
112
123
|
const dir = await mkdtemp(path.join(projectTempRoot, 'bundle-'));
|
|
113
124
|
tempDirs.add(dir);
|
|
114
125
|
return dir;
|
|
@@ -119,65 +130,53 @@ function ensureTempDir() {
|
|
|
119
130
|
return fallbackDir;
|
|
120
131
|
}
|
|
121
132
|
})();
|
|
122
|
-
|
|
133
|
+
tempDirPromises.set(projectRoot, promise);
|
|
123
134
|
}
|
|
124
|
-
return
|
|
135
|
+
return promise;
|
|
125
136
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
await Promise.all(Array.from(tempFiles).map(async (file) => {
|
|
137
|
+
// Synchronous so it completes inside an 'exit' handler.
|
|
138
|
+
function cleanupTempArtifacts() {
|
|
139
|
+
for (const target of [...tempFiles, ...tempDirs]) {
|
|
130
140
|
try {
|
|
131
|
-
|
|
141
|
+
rmSync(target, { recursive: true, force: true });
|
|
132
142
|
}
|
|
133
143
|
catch {
|
|
134
144
|
// ignore cleanup failures
|
|
135
145
|
}
|
|
136
|
-
}
|
|
146
|
+
}
|
|
137
147
|
tempFiles.clear();
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
148
|
+
tempDirs.clear();
|
|
149
|
+
tempDirPromises.clear();
|
|
150
|
+
// Only remove the shared tmp root once empty: another hypequery process in the
|
|
151
|
+
// same project (e.g. `hypequery dev` beside an MCP client) may still use it.
|
|
152
|
+
for (const root of tempRoots) {
|
|
143
153
|
try {
|
|
144
|
-
|
|
154
|
+
rmdirSync(root);
|
|
145
155
|
}
|
|
146
156
|
catch {
|
|
147
|
-
//
|
|
157
|
+
// not empty or already gone
|
|
148
158
|
}
|
|
149
|
-
}));
|
|
150
|
-
tempDirs.clear();
|
|
151
|
-
const projectTempRoot = path.join(process.cwd(), '.hypequery', 'tmp');
|
|
152
|
-
try {
|
|
153
|
-
await rm(projectTempRoot, { recursive: true, force: true });
|
|
154
|
-
}
|
|
155
|
-
catch {
|
|
156
|
-
// ignore cleanup failures
|
|
157
159
|
}
|
|
160
|
+
tempRoots.clear();
|
|
158
161
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
/** Remove this process's bundles; the CLI awaits this before it exits. */
|
|
163
|
+
export async function cleanupLoadedApiArtifacts() {
|
|
164
|
+
cleanupTempArtifacts();
|
|
162
165
|
}
|
|
163
166
|
function installCleanupHooks() {
|
|
164
167
|
if (cleanupHooksInstalled)
|
|
165
168
|
return;
|
|
166
169
|
cleanupHooksInstalled = true;
|
|
167
170
|
globalState.__hypequeryCliCleanupInstalled = true;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
process.once(signal, () => {
|
|
173
|
-
cleanupTempArtifacts().catch(() => undefined);
|
|
174
|
-
process.exit();
|
|
175
|
-
});
|
|
176
|
-
});
|
|
171
|
+
// Synchronous, so it still completes when this is the last thing to run.
|
|
172
|
+
process.once('exit', cleanupTempArtifacts);
|
|
173
|
+
// Signal shutdown belongs to the CLI/owning server. Exiting from a module
|
|
174
|
+
// cleanup listener bypassed server teardown and the bounded telemetry flush.
|
|
177
175
|
}
|
|
178
176
|
async function bundleTypeScriptModule(entryPath) {
|
|
179
177
|
const relativePath = path.relative(process.cwd(), entryPath);
|
|
180
178
|
const tsconfigPath = await findNearestTsconfig(entryPath);
|
|
179
|
+
const projectRoot = await findProjectRoot(entryPath);
|
|
181
180
|
try {
|
|
182
181
|
const result = await build({
|
|
183
182
|
entryPoints: [entryPath],
|
|
@@ -188,7 +187,7 @@ async function bundleTypeScriptModule(entryPath) {
|
|
|
188
187
|
sourcemap: 'inline',
|
|
189
188
|
write: false,
|
|
190
189
|
logLevel: 'silent',
|
|
191
|
-
absWorkingDir:
|
|
190
|
+
absWorkingDir: projectRoot,
|
|
192
191
|
packages: 'external',
|
|
193
192
|
tsconfig: tsconfigPath ?? undefined,
|
|
194
193
|
loader: {
|
|
@@ -202,7 +201,7 @@ async function bundleTypeScriptModule(entryPath) {
|
|
|
202
201
|
if (!output) {
|
|
203
202
|
throw new Error('esbuild produced no output');
|
|
204
203
|
}
|
|
205
|
-
const tempDir = await ensureTempDir();
|
|
204
|
+
const tempDir = await ensureTempDir(projectRoot);
|
|
206
205
|
const timestamp = Date.now();
|
|
207
206
|
const tempFile = path.join(tempDir, `${path.basename(entryPath, path.extname(entryPath))}-${timestamp}.mjs`);
|
|
208
207
|
const contents = `${output.text}\n` +
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads the `.env*` cascade from `directory` into `process.env`.
|
|
3
|
+
*
|
|
4
|
+
* `dotenv` never overwrites a variable that is already set, so a real
|
|
5
|
+
* environment variable beats every file, and a directory loaded earlier (the
|
|
6
|
+
* launch directory, at startup) beats one loaded later. Each directory is read
|
|
7
|
+
* once per process; `hypequery dev` reloads the API on every change.
|
|
8
|
+
*/
|
|
9
|
+
export declare function loadEnvFiles(directory: string): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=load-env-files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-env-files.d.ts","sourceRoot":"","sources":["../../src/utils/load-env-files.ts"],"names":[],"mappings":"AAKA;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAcnE"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { envFiles } from './env-files.js';
|
|
3
|
+
const loadedDirectories = new Set();
|
|
4
|
+
/**
|
|
5
|
+
* Loads the `.env*` cascade from `directory` into `process.env`.
|
|
6
|
+
*
|
|
7
|
+
* `dotenv` never overwrites a variable that is already set, so a real
|
|
8
|
+
* environment variable beats every file, and a directory loaded earlier (the
|
|
9
|
+
* launch directory, at startup) beats one loaded later. Each directory is read
|
|
10
|
+
* once per process; `hypequery dev` reloads the API on every change.
|
|
11
|
+
*/
|
|
12
|
+
export async function loadEnvFiles(directory) {
|
|
13
|
+
const resolved = path.resolve(directory);
|
|
14
|
+
if (loadedDirectories.has(resolved))
|
|
15
|
+
return;
|
|
16
|
+
loadedDirectories.add(resolved);
|
|
17
|
+
try {
|
|
18
|
+
const { config } = await import('dotenv');
|
|
19
|
+
for (const file of envFiles(process.env.NODE_ENV)) {
|
|
20
|
+
// Missing files are a no-op.
|
|
21
|
+
config({ path: path.join(resolved, file) });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// dotenv is optional; continue if not available
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -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 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finds the directory that owns `filePath`: the nearest ancestor with a
|
|
3
|
+
* `package.json`, or the file's own directory when there is none. Bare imports
|
|
4
|
+
* in the file resolve from here, whatever the process's working directory is.
|
|
5
|
+
*/
|
|
6
|
+
export declare function findProjectRoot(filePath: string): Promise<string>;
|
|
7
|
+
/**
|
|
8
|
+
* Imports `specifier` as `fromFile` would see it, so a package installed in the
|
|
9
|
+
* user's project is found even when the CLI runs from elsewhere (for example
|
|
10
|
+
* through `npx -y @hypequery/cli`). Falls back to `fallback` — the CLI's own
|
|
11
|
+
* resolution — when the project does not have the package.
|
|
12
|
+
*/
|
|
13
|
+
export declare function importFromProject<T>(specifier: string, fromFile: string, fallback: () => Promise<T>): Promise<T>;
|
|
14
|
+
//# sourceMappingURL=project-root.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-root.d.ts","sourceRoot":"","sources":["../../src/utils/project-root.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAcvE;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACzB,OAAO,CAAC,CAAC,CAAC,CAQZ"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { access } from 'node:fs/promises';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
/**
|
|
6
|
+
* Finds the directory that owns `filePath`: the nearest ancestor with a
|
|
7
|
+
* `package.json`, or the file's own directory when there is none. Bare imports
|
|
8
|
+
* in the file resolve from here, whatever the process's working directory is.
|
|
9
|
+
*/
|
|
10
|
+
export async function findProjectRoot(filePath) {
|
|
11
|
+
const start = path.dirname(path.resolve(filePath));
|
|
12
|
+
let dir = start;
|
|
13
|
+
while (true) {
|
|
14
|
+
try {
|
|
15
|
+
await access(path.join(dir, 'package.json'));
|
|
16
|
+
return dir;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
const parent = path.dirname(dir);
|
|
20
|
+
if (parent === dir)
|
|
21
|
+
return start;
|
|
22
|
+
dir = parent;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Imports `specifier` as `fromFile` would see it, so a package installed in the
|
|
28
|
+
* user's project is found even when the CLI runs from elsewhere (for example
|
|
29
|
+
* through `npx -y @hypequery/cli`). Falls back to `fallback` — the CLI's own
|
|
30
|
+
* resolution — when the project does not have the package.
|
|
31
|
+
*/
|
|
32
|
+
export async function importFromProject(specifier, fromFile, fallback) {
|
|
33
|
+
let resolved;
|
|
34
|
+
try {
|
|
35
|
+
resolved = createRequire(path.resolve(fromFile)).resolve(specifier);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return fallback();
|
|
39
|
+
}
|
|
40
|
+
return import(/* @vite-ignore */ pathToFileURL(resolved).href);
|
|
41
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { COUNT_BUCKETS, DURATION_BUCKETS } from './domains.js';
|
|
2
|
+
/** Invalid measurements are omitted rather than coerced into a misleading bucket. */
|
|
3
|
+
export declare function countBucket(count: number): typeof COUNT_BUCKETS[number] | undefined;
|
|
4
|
+
export declare function durationBucket(milliseconds: number): typeof DURATION_BUCKETS[number] | undefined;
|
|
5
|
+
//# sourceMappingURL=buckets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buckets.d.ts","sourceRoot":"","sources":["../../../src/utils/telemetry/buckets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAE/D,qFAAqF;AACrF,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,aAAa,CAAC,MAAM,CAAC,GAAG,SAAS,CAQnF;AAED,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,gBAAgB,CAAC,MAAM,CAAC,GAAG,SAAS,CAIhG"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { DURATION_BUCKETS } from './domains.js';
|
|
2
|
+
/** Invalid measurements are omitted rather than coerced into a misleading bucket. */
|
|
3
|
+
export function countBucket(count) {
|
|
4
|
+
if (!Number.isSafeInteger(count) || count < 0)
|
|
5
|
+
return undefined;
|
|
6
|
+
if (count === 0)
|
|
7
|
+
return '0';
|
|
8
|
+
if (count === 1)
|
|
9
|
+
return '1';
|
|
10
|
+
if (count <= 5)
|
|
11
|
+
return '2-5';
|
|
12
|
+
if (count <= 20)
|
|
13
|
+
return '6-20';
|
|
14
|
+
if (count <= 100)
|
|
15
|
+
return '21-100';
|
|
16
|
+
return '101+';
|
|
17
|
+
}
|
|
18
|
+
export function durationBucket(milliseconds) {
|
|
19
|
+
if (!Number.isFinite(milliseconds) || milliseconds < 0)
|
|
20
|
+
return undefined;
|
|
21
|
+
const boundaries = [100, 1_000, 10_000, 60_000, 600_000, 3_600_000];
|
|
22
|
+
return DURATION_BUCKETS[boundaries.findIndex(boundary => milliseconds < boundary)] ?? '1h+';
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog-docs.d.ts","sourceRoot":"","sources":["../../../src/utils/telemetry/catalog-docs.ts"],"names":[],"mappings":"AA6BA,6FAA6F;AAC7F,wBAAgB,sBAAsB,IAAI,MAAM,CA4B/C"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { COMMON_PROPERTIES, EVENT_CATALOG, EVENT_SCHEMA_VERSION } from './catalog.js';
|
|
2
|
+
import { VALUE_FORMATS } from './value-formats.js';
|
|
3
|
+
import { COMMAND_FLAGS, GLOBAL_FLAGS } from './domains.js';
|
|
4
|
+
function domain(field) {
|
|
5
|
+
switch (field.kind) {
|
|
6
|
+
case 'boolean': return '`true`, `false`';
|
|
7
|
+
case 'enum': return field.values.map(value => `\`${value}\``).join(', ');
|
|
8
|
+
case 'enum_list': return `Unique list of: ${field.values.map(value => `\`${value}\``).join(', ')}`;
|
|
9
|
+
case 'record': return 'Object with only the documented keys below';
|
|
10
|
+
case 'format': return VALUE_FORMATS[field.format].description;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function rows(fields, prefix = '') {
|
|
14
|
+
return Object.entries(fields).flatMap(([name, field]) => {
|
|
15
|
+
const key = `${prefix}${name}`;
|
|
16
|
+
return [
|
|
17
|
+
`| \`${key}\` | ${field.optional ? 'Optional' : 'Required'} | ${domain(field)} | ${field.description} |`,
|
|
18
|
+
...(field.kind === 'record' ? rows(field.fields, `${key}.`) : []),
|
|
19
|
+
];
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function table(fields) {
|
|
23
|
+
return ['| Property | Presence | Allowed values | Question answered |', '| --- | --- | --- | --- |', ...rows(fields)].join('\n');
|
|
24
|
+
}
|
|
25
|
+
/** Generated section only; the public page retains its human-written policy and controls. */
|
|
26
|
+
export function renderTelemetryCatalog() {
|
|
27
|
+
const parts = [
|
|
28
|
+
'{/* Generated by pnpm --filter @hypequery/cli telemetry:docs. Do not edit this section by hand. */}',
|
|
29
|
+
'## Event reference',
|
|
30
|
+
`Schema version: **${EVENT_SCHEMA_VERSION}**. Default collection is inactive. Debug or explicitly configured ingest mode emits command/crash lifecycle events, init and generation metrics, and dev/MCP session events. Cloud command and project-feature instrumentation follows separately.`,
|
|
31
|
+
'Unknown events, properties, record keys, flag names, enum values, and malformed identities or versions are rejected. No argument values, paths, names, SQL, messages, stacks, hosts, tenants, or credentials are permitted.',
|
|
32
|
+
'Counts use inclusive integer ranges: 0, 1, 2–5, 6–20, 21–100, and 101 or more. Duration and nonzero size buckets include their lower bound and exclude their upper bound. Raw counts, durations, ports and byte sizes are never sent.',
|
|
33
|
+
'### Common properties',
|
|
34
|
+
'Every event includes these fields. Unknown environment details use the documented unknown value; unavailable optional fields are omitted.',
|
|
35
|
+
table(COMMON_PROPERTIES),
|
|
36
|
+
];
|
|
37
|
+
for (const [name, definition] of Object.entries(EVENT_CATALOG)) {
|
|
38
|
+
parts.push(`### \`${name}\``, definition.purpose, table(definition.properties));
|
|
39
|
+
if ('variants' in definition) {
|
|
40
|
+
for (const [command, fields] of Object.entries(definition.variants)) {
|
|
41
|
+
parts.push(`#### \`${name}\`: \`${command}\``);
|
|
42
|
+
if (name === 'cli_command_completed') {
|
|
43
|
+
const flags = [...COMMAND_FLAGS[command], ...GLOBAL_FLAGS];
|
|
44
|
+
parts.push(`Allowed \`flags_used\`: ${flags.map(flag => `\`${flag}\``).join(', ')}. The two type-generation aliases remain distinct command values.`);
|
|
45
|
+
}
|
|
46
|
+
parts.push(Object.keys(fields).length ? table(fields) : 'No additional properties.');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
parts.push('MCP client identity/version is excluded from v1 pending the privacy decision. Auth strategies are recorded only as none/configured/unknown, since user-supplied functions have no safe built-in type identifier.', 'Cache scopes are classified only as unscoped/tenant/explicit/unknown from configuration presence. Explicit scopes are user content and are never read into a payload.', 'Project IDs follow HQ-233: the CLI uses the shared global salt, then the ingest proxy re-keys the digest with a server-side HMAC secret before storage. A hash is a pseudonymous project signal, not proof of anonymity. Cloud accounts are never linked to install IDs.');
|
|
51
|
+
return `${parts.join('\n\n')}\n`;
|
|
52
|
+
}
|