Files

190 lines
5.7 KiB
JavaScript
Executable File

#!/usr/bin/env node
// src/agent-skills/scripts/search_docs.ts
import { parseArgs } from "util";
// src/http/index.ts
var PROD_BASE_URL = "https://shopify.dev/";
var SHOP_DEV_BASE_URL = "https://shopify-dev.shop.dev/";
function stagingHost(serverNumber) {
return `https://shopify-dev-staging${serverNumber}.shopifycloud.com/`;
}
function resolveShopifyDevBaseUrl(options) {
const env = options?.env ?? process.env;
const stagingRaw = env.SHOPIFY_DEV_STAGING_SERVER_NUMBER?.trim();
if (stagingRaw) {
if (!/^\d+$/.test(stagingRaw)) {
throw new Error(
`SHOPIFY_DEV_STAGING_SERVER_NUMBER must be a positive integer; got: "${stagingRaw}"`
);
}
const serverNumber = Number(stagingRaw);
if (!Number.isSafeInteger(serverNumber) || serverNumber <= 0) {
throw new Error(
`SHOPIFY_DEV_STAGING_SERVER_NUMBER must be a positive integer; got: "${stagingRaw}"`
);
}
const token = env.MINERVA_TOKEN;
if (!token) {
const audience = stagingHost(serverNumber).replace(/\/$/, "");
throw new Error(
`SHOPIFY_DEV_STAGING_SERVER_NUMBER=${serverNumber} is set but no Minerva token is available. Staging servers are behind Minerva. Get a token via:
export MINERVA_TOKEN=$(devx minerva-auth --client-id 0oa1bphetnkOusboI0x8 --audience ${audience})`
);
}
return {
url: stagingHost(serverNumber),
headers: { Cookie: `MINERVA_TOKEN=${token}` }
};
}
const instrumentationOverride = env.SHOPIFY_DEV_INSTRUMENTATION_URL?.trim();
if (instrumentationOverride && options?.uri?.startsWith("/mcp/usage")) {
return { url: instrumentationOverride, headers: {} };
}
if (env.DEV && env.DEV !== "false") {
return { url: SHOP_DEV_BASE_URL, headers: {} };
}
return { url: PROD_BASE_URL, headers: {} };
}
async function shopifyDevFetch(uri, options) {
let url;
let resolvedHeaders = {};
if (uri.startsWith("http://") || uri.startsWith("https://")) {
url = new URL(uri);
} else {
const resolved = resolveShopifyDevBaseUrl({ uri });
url = new URL(uri, resolved.url);
resolvedHeaders = resolved.headers;
}
if (options?.parameters) {
Object.entries(options.parameters).forEach(([key, value]) => {
url.searchParams.append(key, value);
});
}
const response = await fetch(url.toString(), {
method: options?.method || "GET",
headers: {
Accept: "application/json",
"Cache-Control": "no-cache",
"X-Shopify-Surface": "mcp",
"X-Shopify-MCP-Version": options?.instrumentation?.packageVersion || "",
"X-Shopify-Timestamp": options?.instrumentation?.timestamp || "",
...resolvedHeaders,
...options?.headers
},
...options?.body && { body: options.body }
});
if (!response.ok) {
let errorBody;
try {
errorBody = await response.text();
} catch {
}
throw new Error(
errorBody ? `HTTP ${response.status}: ${errorBody}` : `HTTP error! status: ${response.status}`
);
}
return await response.text();
}
// src/agent-skills/scripts/instrumentation.ts
function isInstrumentationDisabled() {
try {
return process.env.OPT_OUT_INSTRUMENTATION === "true";
} catch {
return false;
}
}
async function reportValidation(toolName, result, context) {
if (isInstrumentationDisabled()) return;
const { model, clientName, clientVersion, ...remainingContext } = context ?? {};
try {
const headers = {
"Content-Type": "application/json",
"X-Shopify-Surface": "skills"
};
if (clientName) headers["X-Shopify-Client-Name"] = String(clientName);
if (clientVersion)
headers["X-Shopify-Client-Version"] = String(clientVersion);
if (model) headers["X-Shopify-Client-Model"] = String(model);
await shopifyDevFetch("/mcp/usage", {
method: "POST",
headers,
body: JSON.stringify({
tool: toolName,
parameters: {
skill: "shopify-dev",
skillVersion: "1.9.1",
...remainingContext
},
result
}),
instrumentation: {
packageVersion: "1.9.1",
timestamp: (/* @__PURE__ */ new Date()).toISOString()
}
});
} catch {
}
}
// src/agent-skills/scripts/search_docs.ts
var { values, positionals } = parseArgs({
options: {
model: { type: "string" },
"client-name": { type: "string" },
"client-version": { type: "string" }
},
allowPositionals: true
});
var query = positionals[0];
if (!query) {
console.error(
"Usage: search_docs.js <query> [--model <id>] [--client-name <name>]"
);
process.exit(1);
}
async function performSearch(query2, apiName) {
const body = { query: query2 };
if (apiName) body.api_name = apiName;
const responseText = await shopifyDevFetch("/assistant/search", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Shopify-Surface": "skills"
},
body: JSON.stringify(body),
instrumentation: {
packageVersion: "1.9.1",
timestamp: (/* @__PURE__ */ new Date()).toISOString()
}
});
try {
const jsonData = JSON.parse(responseText);
return JSON.stringify(jsonData, null, 2);
} catch {
return responseText;
}
}
try {
const result = await performSearch(query, void 0);
process.stdout.write(result);
process.stdout.write("\n");
await reportValidation("search_docs", result, {
model: values.model,
clientName: values["client-name"],
clientVersion: values["client-version"],
query
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`Search failed: ${message}`);
await reportValidation("search_docs", message, {
model: values.model,
clientName: values["client-name"],
clientVersion: values["client-version"],
query
});
process.exit(1);
}