@nomac/cli 0.5.2 → 0.5.3
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/package.json +1 -1
- package/src/api.mjs +3 -1
- package/src/client-info.mjs +33 -0
- package/src/mac-transport.mjs +3 -1
- package/src/mcp.mjs +3 -1
package/package.json
CHANGED
package/src/api.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loadApiKey, loadApiUrl } from "./config.mjs";
|
|
2
|
+
import { clientHeaders } from "./client-info.mjs";
|
|
2
3
|
|
|
3
4
|
export class CliError extends Error {
|
|
4
5
|
constructor(message, friendly, status, code, details) {
|
|
@@ -16,7 +17,7 @@ export async function apiPublic(method, path, body) {
|
|
|
16
17
|
const url = `${loadApiUrl()}${path}`;
|
|
17
18
|
const res = await fetch(url, {
|
|
18
19
|
method,
|
|
19
|
-
headers: body !== undefined ? { "content-type": "application/json" } : {},
|
|
20
|
+
headers: { ...clientHeaders(), ...(body !== undefined ? { "content-type": "application/json" } : {}) },
|
|
20
21
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
21
22
|
});
|
|
22
23
|
const text = await res.text();
|
|
@@ -41,6 +42,7 @@ export async function api(method, path, { body, raw, idempotencyKey } = {}) {
|
|
|
41
42
|
const res = await fetch(url, {
|
|
42
43
|
method,
|
|
43
44
|
headers: {
|
|
45
|
+
...clientHeaders(),
|
|
44
46
|
authorization: `Bearer ${key}`,
|
|
45
47
|
...(raw
|
|
46
48
|
? { "content-type": "application/gzip" }
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* How requests identify this CLI to nomac.app for product analytics: version,
|
|
6
|
+
* OS and, under `nomac mcp`, the agent and tool making the call. No hostnames,
|
|
7
|
+
* paths or arguments.
|
|
8
|
+
*/
|
|
9
|
+
export const CLI_VERSION = (() => {
|
|
10
|
+
try { return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version; } catch { return "0.0.0"; }
|
|
11
|
+
})();
|
|
12
|
+
export const USER_AGENT = `nomac-cli/${CLI_VERSION} (${process.platform}; node ${process.versions.node})`;
|
|
13
|
+
|
|
14
|
+
/** Set by the stdio MCP server: the connected agent (from initialize) and the running tool. */
|
|
15
|
+
export const mcpContext = { client: undefined };
|
|
16
|
+
export const toolScope = new AsyncLocalStorage();
|
|
17
|
+
|
|
18
|
+
export function clientHeaders() {
|
|
19
|
+
const tool = toolScope.getStore()?.tool;
|
|
20
|
+
return {
|
|
21
|
+
"user-agent": USER_AGENT,
|
|
22
|
+
...(mcpContext.client ? { "x-nomac-mcp-client": mcpContext.client } : {}),
|
|
23
|
+
...(tool ? { "x-nomac-tool": tool } : {}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** "claude-code/2.1.4" from an MCP clientInfo, or undefined. */
|
|
28
|
+
export function mcpClientName(info) {
|
|
29
|
+
const name = typeof info?.name === "string" ? info.name.toLowerCase().replace(/[^a-z0-9._+-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) : "";
|
|
30
|
+
if (!name) return undefined;
|
|
31
|
+
const version = typeof info?.version === "string" ? /^\d{1,4}(\.\d{1,6}){0,3}/.exec(info.version)?.[0] : undefined;
|
|
32
|
+
return version ? `${name}/${version}` : name;
|
|
33
|
+
}
|
package/src/mac-transport.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { HttpsProxyAgent } from "https-proxy-agent";
|
|
|
4
4
|
import { getProxyForUrl } from "proxy-from-env";
|
|
5
5
|
import WebSocket, { createWebSocketStream } from "ws";
|
|
6
6
|
import { CliError } from "./api.mjs";
|
|
7
|
+
import { clientHeaders } from "./client-info.mjs";
|
|
7
8
|
|
|
8
9
|
export function sessionId(value) {
|
|
9
10
|
const id = String(value ?? "").replace(/^nomac-/, "");
|
|
@@ -60,6 +61,7 @@ function macRequestOnce(method, path, { body, raw, requestKey, ...options } = {}
|
|
|
60
61
|
};
|
|
61
62
|
const request = (target.protocol === "https:" ? https : http).request(target, {
|
|
62
63
|
method, agent, ca, headers: {
|
|
64
|
+
...clientHeaders(),
|
|
63
65
|
authorization: `Bearer ${token}`,
|
|
64
66
|
...(payload ? { "content-type": raw ? "application/octet-stream" : "application/json", "content-length": payload.length } : {}),
|
|
65
67
|
...(requestKey ? { "idempotency-key": requestKey } : {}),
|
|
@@ -99,7 +101,7 @@ export function tunnel(value, { input = process.stdin, output = process.stdout,
|
|
|
99
101
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
100
102
|
return new Promise((resolve, reject) => {
|
|
101
103
|
const ws = new WebSocket(url, {
|
|
102
|
-
agent, ca, headers: { authorization: `Bearer ${token}` },
|
|
104
|
+
agent, ca, headers: { ...clientHeaders(), authorization: `Bearer ${token}` },
|
|
103
105
|
handshakeTimeout: 15_000, perMessageDeflate: false, maxPayload: 1024 * 1024,
|
|
104
106
|
followRedirects: false,
|
|
105
107
|
});
|
package/src/mcp.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { readFileSync } from "node:fs";
|
|
|
7
7
|
const lifecycleSchemas = JSON.parse(readFileSync(new URL("./lifecycle-schemas.json", import.meta.url), "utf8"));
|
|
8
8
|
import { createInterface } from "node:readline";
|
|
9
9
|
import { api } from "./api.mjs";
|
|
10
|
+
import { mcpClientName, mcpContext, toolScope } from "./client-info.mjs";
|
|
10
11
|
import { loadProjectState, saveProjectState } from "./config.mjs";
|
|
11
12
|
import { packTarball } from "./pack.mjs";
|
|
12
13
|
import { uploadSourceSnapshot } from "./upload.mjs";
|
|
@@ -401,6 +402,7 @@ export async function runMcpServer() {
|
|
|
401
402
|
try {
|
|
402
403
|
switch (method) {
|
|
403
404
|
case "initialize":
|
|
405
|
+
mcpContext.client = mcpClientName(params?.clientInfo);
|
|
404
406
|
reply({
|
|
405
407
|
protocolVersion: params?.protocolVersion ?? PROTOCOL_FALLBACK,
|
|
406
408
|
capabilities: { tools: {} },
|
|
@@ -422,7 +424,7 @@ export async function runMcpServer() {
|
|
|
422
424
|
const tool = TOOLS.find((t) => t.name === params?.name);
|
|
423
425
|
if (!tool) return fail(-32602, `unknown tool: ${params?.name}`);
|
|
424
426
|
try {
|
|
425
|
-
reply(await tool.handler(params?.arguments ?? {}));
|
|
427
|
+
reply(await toolScope.run({ tool: tool.name }, () => tool.handler(params?.arguments ?? {})));
|
|
426
428
|
} catch (err) {
|
|
427
429
|
reply({
|
|
428
430
|
content: [{ type: "text", text: err?.details ? JSON.stringify({ error: { ...err.details, message: err.friendly ?? err.message } }, null, 2) : err?.friendly ?? err?.message ?? String(err) }],
|