@yiaany/ghostapi 0.1.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/CHANGELOG.md +29 -0
- package/CONTRIBUTING.md +117 -0
- package/LICENSE +21 -0
- package/README.md +354 -0
- package/SECURITY.md +37 -0
- package/dist/agents/agentPrompt.d.ts +5 -0
- package/dist/agents/agentPrompt.js +30 -0
- package/dist/agents/agentPrompt.js.map +1 -0
- package/dist/ai/aiClient.d.ts +7 -0
- package/dist/ai/aiClient.js +30 -0
- package/dist/ai/aiClient.js.map +1 -0
- package/dist/ai/aiGenerator.d.ts +10 -0
- package/dist/ai/aiGenerator.js +101 -0
- package/dist/ai/aiGenerator.js.map +1 -0
- package/dist/ai/genericInference.d.ts +5 -0
- package/dist/ai/genericInference.js +80 -0
- package/dist/ai/genericInference.js.map +1 -0
- package/dist/ai/index.d.ts +3 -0
- package/dist/ai/index.js +2 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/jsonRepair.d.ts +2 -0
- package/dist/ai/jsonRepair.js +31 -0
- package/dist/ai/jsonRepair.js.map +1 -0
- package/dist/ai/prompts.d.ts +4 -0
- package/dist/ai/prompts.js +81 -0
- package/dist/ai/prompts.js.map +1 -0
- package/dist/behavior/behaviorStore.d.ts +12 -0
- package/dist/behavior/behaviorStore.js +72 -0
- package/dist/behavior/behaviorStore.js.map +1 -0
- package/dist/cache/index.d.ts +9 -0
- package/dist/cache/index.js +34 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cli/errors.d.ts +4 -0
- package/dist/cli/errors.js +9 -0
- package/dist/cli/errors.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +277 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/parser.d.ts +56 -0
- package/dist/cli/parser.js +169 -0
- package/dist/cli/parser.js.map +1 -0
- package/dist/config/localConfig.d.ts +16 -0
- package/dist/config/localConfig.js +58 -0
- package/dist/config/localConfig.js.map +1 -0
- package/dist/config/serverConfig.d.ts +13 -0
- package/dist/config/serverConfig.js +44 -0
- package/dist/config/serverConfig.js.map +1 -0
- package/dist/dashboard/app.js +615 -0
- package/dist/dashboard/dashboard.d.ts +4 -0
- package/dist/dashboard/dashboard.js +20 -0
- package/dist/dashboard/dashboard.js.map +1 -0
- package/dist/dashboard/index.d.ts +1 -0
- package/dist/dashboard/index.html +298 -0
- package/dist/dashboard/index.js +2 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/dashboard/styles.css +349 -0
- package/dist/errors/errorEngine.d.ts +3 -0
- package/dist/errors/errorEngine.js +65 -0
- package/dist/errors/errorEngine.js.map +1 -0
- package/dist/errors/index.d.ts +3 -0
- package/dist/errors/index.js +3 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/providerErrors.d.ts +3 -0
- package/dist/errors/providerErrors.js +6 -0
- package/dist/errors/providerErrors.js.map +1 -0
- package/dist/fault/faultLab.d.ts +27 -0
- package/dist/fault/faultLab.js +89 -0
- package/dist/fault/faultLab.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/landing/assets/index-BXR41fAD.css +1 -0
- package/dist/landing/assets/index-D8_ggMRE.js +55 -0
- package/dist/landing/index.html +15 -0
- package/dist/landing/landing.d.ts +3 -0
- package/dist/landing/landing.js +12 -0
- package/dist/landing/landing.js.map +1 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +52 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/providers/discord.d.ts +2 -0
- package/dist/providers/discord.js +11 -0
- package/dist/providers/discord.js.map +1 -0
- package/dist/providers/generic.d.ts +2 -0
- package/dist/providers/generic.js +13 -0
- package/dist/providers/generic.js.map +1 -0
- package/dist/providers/github.d.ts +2 -0
- package/dist/providers/github.js +11 -0
- package/dist/providers/github.js.map +1 -0
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.js +2 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai.d.ts +2 -0
- package/dist/providers/openai.js +15 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/registry.d.ts +12 -0
- package/dist/providers/registry.js +23 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/resend.d.ts +2 -0
- package/dist/providers/resend.js +12 -0
- package/dist/providers/resend.js.map +1 -0
- package/dist/providers/stripe.d.ts +2 -0
- package/dist/providers/stripe.js +15 -0
- package/dist/providers/stripe.js.map +1 -0
- package/dist/providers/twilio.d.ts +2 -0
- package/dist/providers/twilio.js +14 -0
- package/dist/providers/twilio.js.map +1 -0
- package/dist/providers/types.d.ts +13 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/proxy/cacheKey.d.ts +2 -0
- package/dist/proxy/cacheKey.js +37 -0
- package/dist/proxy/cacheKey.js.map +1 -0
- package/dist/proxy/providerDetector.d.ts +11 -0
- package/dist/proxy/providerDetector.js +74 -0
- package/dist/proxy/providerDetector.js.map +1 -0
- package/dist/proxy/proxyHandler.d.ts +3 -0
- package/dist/proxy/proxyHandler.js +114 -0
- package/dist/proxy/proxyHandler.js.map +1 -0
- package/dist/proxy/requestNormalizer.d.ts +11 -0
- package/dist/proxy/requestNormalizer.js +30 -0
- package/dist/proxy/requestNormalizer.js.map +1 -0
- package/dist/report/safetyReport.d.ts +13 -0
- package/dist/report/safetyReport.js +65 -0
- package/dist/report/safetyReport.js.map +1 -0
- package/dist/rules/aiRules.d.ts +7 -0
- package/dist/rules/aiRules.js +31 -0
- package/dist/rules/aiRules.js.map +1 -0
- package/dist/scenarios/scenarioStore.d.ts +37 -0
- package/dist/scenarios/scenarioStore.js +201 -0
- package/dist/scenarios/scenarioStore.js.map +1 -0
- package/dist/security/headerSanitizer.d.ts +3 -0
- package/dist/security/headerSanitizer.js +12 -0
- package/dist/security/headerSanitizer.js.map +1 -0
- package/dist/security/maskSecrets.d.ts +1 -0
- package/dist/security/maskSecrets.js +2 -0
- package/dist/security/maskSecrets.js.map +1 -0
- package/dist/security/secrets.d.ts +4 -0
- package/dist/security/secrets.js +61 -0
- package/dist/security/secrets.js.map +1 -0
- package/dist/server/createServer.d.ts +8 -0
- package/dist/server/createServer.js +42 -0
- package/dist/server/createServer.js.map +1 -0
- package/dist/server/eventsStore.d.ts +17 -0
- package/dist/server/eventsStore.js +31 -0
- package/dist/server/eventsStore.js.map +1 -0
- package/dist/server/routes.d.ts +3 -0
- package/dist/server/routes.js +189 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/server/sse.d.ts +5 -0
- package/dist/server/sse.js +32 -0
- package/dist/server/sse.js.map +1 -0
- package/dist/setup/sdkDetector.d.ts +9 -0
- package/dist/setup/sdkDetector.js +31 -0
- package/dist/setup/sdkDetector.js.map +1 -0
- package/dist/setup/setupGenerator.d.ts +28 -0
- package/dist/setup/setupGenerator.js +146 -0
- package/dist/setup/setupGenerator.js.map +1 -0
- package/dist/state/index.d.ts +1 -0
- package/dist/state/index.js +2 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/stateExtractor.d.ts +3 -0
- package/dist/state/stateExtractor.js +36 -0
- package/dist/state/stateExtractor.js.map +1 -0
- package/dist/state/stateResolver.d.ts +8 -0
- package/dist/state/stateResolver.js +95 -0
- package/dist/state/stateResolver.js.map +1 -0
- package/dist/state/stateStore.d.ts +4 -0
- package/dist/state/stateStore.js +72 -0
- package/dist/state/stateStore.js.map +1 -0
- package/dist/tests/testGenerator.d.ts +6 -0
- package/dist/tests/testGenerator.js +33 -0
- package/dist/tests/testGenerator.js.map +1 -0
- package/dist/utils/json.d.ts +2 -0
- package/dist/utils/json.js +10 -0
- package/dist/utils/json.js.map +1 -0
- package/docs/release-checklist.md +71 -0
- package/examples/agent-instructions/README.md +39 -0
- package/examples/generic-rest/README.md +27 -0
- package/examples/github-fetch/README.md +35 -0
- package/examples/resend-node/README.md +22 -0
- package/examples/stripe-node/README.md +29 -0
- package/examples/twilio-node/README.md +21 -0
- package/package.json +67 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { extractIdFromPath } from "./stateExtractor.js";
|
|
2
|
+
import { getStateStore, saveToStateStore } from "./stateStore.js";
|
|
3
|
+
import { isJsonObject } from "../utils/json.js";
|
|
4
|
+
export async function resolveState(request, provider) {
|
|
5
|
+
const stateStore = await getStateStore();
|
|
6
|
+
const idToLookUp = extractIdFromPath(request);
|
|
7
|
+
const resourceNamespace = resolveNamespace(request);
|
|
8
|
+
if (request.method === "GET") {
|
|
9
|
+
if (idToLookUp !== undefined) {
|
|
10
|
+
const stateKey = `${provider}:${idToLookUp}`;
|
|
11
|
+
const found = stateStore[stateKey];
|
|
12
|
+
if (found !== undefined) {
|
|
13
|
+
return createResponse(found);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
else if (resourceNamespace !== undefined) {
|
|
17
|
+
const list = Object.keys(stateStore)
|
|
18
|
+
.filter((key) => key.startsWith(`${provider}:`) && objectMatchesNamespace(stateStore[key], resourceNamespace))
|
|
19
|
+
.map((key) => stateStore[key]);
|
|
20
|
+
const formattedList = formatProviderList(provider, list);
|
|
21
|
+
return createResponse(formattedList);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (request.method === "DELETE" && idToLookUp !== undefined) {
|
|
25
|
+
const stateKey = `${provider}:${idToLookUp}`;
|
|
26
|
+
const found = stateStore[stateKey];
|
|
27
|
+
if (found !== undefined) {
|
|
28
|
+
const deletedObj = formatProviderDelete(provider, found, idToLookUp);
|
|
29
|
+
await saveToStateStore(stateKey, deletedObj);
|
|
30
|
+
return createResponse(deletedObj);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
function resolveNamespace(request) {
|
|
36
|
+
const segments = request.path.split("/").filter(Boolean);
|
|
37
|
+
if (segments.length === 0)
|
|
38
|
+
return undefined;
|
|
39
|
+
const id = extractIdFromPath(request);
|
|
40
|
+
if (id !== undefined && segments.length >= 2) {
|
|
41
|
+
// If it's an item path like /v1/customers/cus_1, namespace is "customers"
|
|
42
|
+
return segments[segments.length - 2]?.toLowerCase();
|
|
43
|
+
}
|
|
44
|
+
// If it's a list path like /v1/customers, namespace is "customers"
|
|
45
|
+
return segments[segments.length - 1]?.toLowerCase();
|
|
46
|
+
}
|
|
47
|
+
function objectMatchesNamespace(obj, expectedNamespace) {
|
|
48
|
+
if (!isJsonObject(obj) || typeof obj.object !== "string") {
|
|
49
|
+
// Basic fallback: include all provider items if namespace checking is too rigid without object shapes
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
// Ex: "customer" matches namespace "customers"
|
|
53
|
+
return expectedNamespace.startsWith(obj.object.toLowerCase());
|
|
54
|
+
}
|
|
55
|
+
function formatProviderList(provider, list) {
|
|
56
|
+
if (provider === "stripe") {
|
|
57
|
+
return {
|
|
58
|
+
object: "list",
|
|
59
|
+
data: list,
|
|
60
|
+
has_more: false,
|
|
61
|
+
url: `/v1/list_placeholder` // Placeholder
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (provider === "openai") {
|
|
65
|
+
return {
|
|
66
|
+
object: "list",
|
|
67
|
+
data: list
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
data: list,
|
|
72
|
+
total: list.length
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function formatProviderDelete(provider, existingObj, id) {
|
|
76
|
+
if (provider === "stripe") {
|
|
77
|
+
return {
|
|
78
|
+
id,
|
|
79
|
+
object: isJsonObject(existingObj) && typeof existingObj.object === "string" ? existingObj.object : "removed_object",
|
|
80
|
+
deleted: true
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (isJsonObject(existingObj)) {
|
|
84
|
+
return { ...existingObj, deleted: true };
|
|
85
|
+
}
|
|
86
|
+
return { id, deleted: true };
|
|
87
|
+
}
|
|
88
|
+
function createResponse(body) {
|
|
89
|
+
return {
|
|
90
|
+
status: 200,
|
|
91
|
+
headers: { "content-type": "application/json", "x-ghostapi-state": "HIT" },
|
|
92
|
+
body
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=stateResolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stateResolver.js","sourceRoot":"","sources":["../../src/state/stateResolver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function initializeStateStore(): Promise<void>;
|
|
2
|
+
export declare function getStateStore(): Promise<Record<string, unknown>>;
|
|
3
|
+
export declare function saveToStateStore(id: string, obj: unknown): Promise<void>;
|
|
4
|
+
export declare function clearState(): Promise<void>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { dirname } from "node:path";
|
|
2
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { isJsonObject } from "../utils/json.js";
|
|
4
|
+
const STATE_FILE_PATH = ".ghostapi/state.json";
|
|
5
|
+
let writeLock = Promise.resolve();
|
|
6
|
+
export async function initializeStateStore() {
|
|
7
|
+
const release = await acquireLock();
|
|
8
|
+
try {
|
|
9
|
+
await mkdir(dirname(STATE_FILE_PATH), { recursive: true });
|
|
10
|
+
try {
|
|
11
|
+
await readFile(STATE_FILE_PATH, "utf8");
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
await writeFile(STATE_FILE_PATH, JSON.stringify({}, null, 2), "utf8");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
release();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function getStateStore() {
|
|
22
|
+
try {
|
|
23
|
+
const content = await readFile(STATE_FILE_PATH, "utf8");
|
|
24
|
+
const parsed = JSON.parse(content);
|
|
25
|
+
return isJsonObject(parsed) ? parsed : {};
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export async function saveToStateStore(id, obj) {
|
|
35
|
+
const release = await acquireLock();
|
|
36
|
+
try {
|
|
37
|
+
const state = await getStateStore();
|
|
38
|
+
state[id] = obj;
|
|
39
|
+
await mkdir(dirname(STATE_FILE_PATH), { recursive: true });
|
|
40
|
+
await writeFile(STATE_FILE_PATH, JSON.stringify(state, null, 2), "utf8");
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
release();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export async function clearState() {
|
|
47
|
+
const release = await acquireLock();
|
|
48
|
+
try {
|
|
49
|
+
try {
|
|
50
|
+
await rm(STATE_FILE_PATH, { force: true });
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Ignore if not present
|
|
54
|
+
}
|
|
55
|
+
await mkdir(dirname(STATE_FILE_PATH), { recursive: true });
|
|
56
|
+
await writeFile(STATE_FILE_PATH, JSON.stringify({}, null, 2), "utf8");
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
release();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function acquireLock() {
|
|
63
|
+
let release;
|
|
64
|
+
const nextLock = new Promise((resolve) => {
|
|
65
|
+
release = resolve;
|
|
66
|
+
});
|
|
67
|
+
const currentLock = writeLock;
|
|
68
|
+
writeLock = currentLock.then(() => nextLock);
|
|
69
|
+
await currentLock;
|
|
70
|
+
return release;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=stateStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stateStore.js","sourceRoot":"","sources":["../../src/state/stateStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function generateVitestFromEvent(event) {
|
|
2
|
+
const name = sanitizeName(`${event.method} ${event.path}`);
|
|
3
|
+
const body = extractBody(event.request);
|
|
4
|
+
const bodyLine = body === undefined ? "" : `,\n body: JSON.stringify(${JSON.stringify(body, null, 6).replace(/^/gm, " ").trim()})`;
|
|
5
|
+
const headersLine = body === undefined ? "" : `,\n headers: { "content-type": "application/json" }`;
|
|
6
|
+
return {
|
|
7
|
+
filename: `${name}.test.ts`,
|
|
8
|
+
content: [
|
|
9
|
+
"import { describe, expect, it } from \"vitest\";",
|
|
10
|
+
"",
|
|
11
|
+
`describe(${JSON.stringify(`GhostAPI ${event.provider}`)}, () => {`,
|
|
12
|
+
` it(${JSON.stringify(`${event.method} ${event.path} returns ${event.statusCode}`)}, async () => {`,
|
|
13
|
+
` const response = await fetch(\`http://127.0.0.1:8080${event.path}\`, {`,
|
|
14
|
+
` method: ${JSON.stringify(event.method)}${headersLine}${bodyLine}`,
|
|
15
|
+
" });",
|
|
16
|
+
"",
|
|
17
|
+
` expect(response.status).toBe(${event.statusCode});`,
|
|
18
|
+
" await expect(response.json()).resolves.toMatchSnapshot();",
|
|
19
|
+
" });",
|
|
20
|
+
"});",
|
|
21
|
+
""
|
|
22
|
+
].join("\n")
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function extractBody(request) {
|
|
26
|
+
if (request !== null && typeof request === "object" && "body" in request)
|
|
27
|
+
return request.body;
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
function sanitizeName(value) {
|
|
31
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 80) || "ghostapi-request";
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=testGenerator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testGenerator.js","sourceRoot":"","sources":["../../src/tests/testGenerator.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,uBAAuB,CAAC,KAAiB;IACvD,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3D,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,+BAA+B,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC;IACzI,MAAM,WAAW,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,wDAAwD,CAAC;IACvG,OAAO;QACL,QAAQ,EAAE,GAAG,IAAI,UAAU;QAC3B,OAAO,EAAE;YACP,kDAAkD;YAClD,EAAE;YACF,YAAY,IAAI,CAAC,SAAS,CAAC,YAAY,KAAK,CAAC,QAAQ,EAAE,CAAC,WAAW;YACnE,QAAQ,IAAI,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,YAAY,KAAK,CAAC,UAAU,EAAE,CAAC,iBAAiB;YACpG,2DAA2D,KAAK,CAAC,IAAI,OAAO;YAC5E,iBAAiB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,WAAW,GAAG,QAAQ,EAAE;YACxE,SAAS;YACT,EAAE;YACF,oCAAoC,KAAK,CAAC,UAAU,IAAI;YACxD,+DAA+D;YAC/D,OAAO;YACP,KAAK;YACL,EAAE;SACH,CAAC,IAAI,CAAC,IAAI,CAAC;KACb,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,OAAgB;IACnC,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,MAAM,IAAI,OAAO;QAAE,OAAQ,OAA8B,CAAC,IAAI,CAAC;IACtH,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,kBAAkB,CAAC;AAClH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function isJsonObject(value) {
|
|
2
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
export function hasBooleanFlag(value, flag) {
|
|
5
|
+
if (!isJsonObject(value)) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
return value[flag] === true || value[flag] === "true";
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/utils/json.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAc,EAAE,IAAY;IACzD,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Release Checklist
|
|
2
|
+
|
|
3
|
+
Use this checklist before publishing GhostAPI to GitHub or npm.
|
|
4
|
+
|
|
5
|
+
## Metadata
|
|
6
|
+
|
|
7
|
+
- `package.json` has the correct `name`, `version`, `description`, `license`, `bin`, `files`, `keywords`, and repository metadata.
|
|
8
|
+
- `README.md` explains value, install, CLI, provider coverage, examples, safety, and contributing.
|
|
9
|
+
- `CHANGELOG.md` has an entry for the release.
|
|
10
|
+
- `LICENSE`, `SECURITY.md`, `CONTRIBUTING.md`, and `CODE_OF_CONDUCT.md` are present.
|
|
11
|
+
|
|
12
|
+
## Local Verification
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm run typecheck
|
|
16
|
+
npm test
|
|
17
|
+
npm run build
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Smoke check the built CLI:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
node dist/cli/index.js --help
|
|
24
|
+
node dist/cli/index.js providers list
|
|
25
|
+
node dist/cli/index.js model get
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Run local proxy smoke tests:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
node dist/cli/index.js start --offline --port 8080
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then verify locally:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
curl http://localhost:8080/health
|
|
38
|
+
curl http://localhost:8080/dashboard
|
|
39
|
+
curl -X POST http://localhost:8080/v1/customers -H "content-type: application/json" -d '{"email":"ada@example.com"}'
|
|
40
|
+
curl http://localhost:8080/v1/customers
|
|
41
|
+
curl -X POST http://localhost:8080/emails -H "content-type: application/json" -d '{"from":"dev@example.com","to":"ada@example.com","subject":"Hello"}'
|
|
42
|
+
curl -X POST http://localhost:8080/2010-04-01/Accounts/AC123/Messages.json -H "content-type: application/x-www-form-urlencoded" -d "To=+15551234567&From=+15557654321&Body=Hello"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Package Verification
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm pack --dry-run
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Confirm the package includes only intended release files and does not include:
|
|
52
|
+
|
|
53
|
+
- `.ghostapi/`
|
|
54
|
+
- `.env`
|
|
55
|
+
- `node_modules/`
|
|
56
|
+
- local logs
|
|
57
|
+
- test output
|
|
58
|
+
|
|
59
|
+
## Safety Checks
|
|
60
|
+
|
|
61
|
+
- GhostAPI does not make real provider API calls by default.
|
|
62
|
+
- LLM usage requires explicit API key configuration and receives sanitized requests.
|
|
63
|
+
- Dashboard events are sanitized.
|
|
64
|
+
- Cache keys do not include raw secrets.
|
|
65
|
+
- CLI user errors are actionable and do not print confusing stack traces.
|
|
66
|
+
|
|
67
|
+
## Publish
|
|
68
|
+
|
|
69
|
+
- Tag the release.
|
|
70
|
+
- Push to GitHub.
|
|
71
|
+
- Publish to npm only after package dry-run output has been reviewed.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Agent Instructions
|
|
2
|
+
|
|
3
|
+
Use these snippets in AI coding tools when you want agents to test external integrations safely through GhostAPI.
|
|
4
|
+
|
|
5
|
+
## Universal Instruction
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
When testing external API integrations, use GhostAPI at http://localhost:8080. Do not send real requests to Stripe, Twilio, Resend, GitHub, Gmail or other external services unless explicitly approved by the user.
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Cursor
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
For integration tests and local development, route external API calls through http://localhost:8080. Treat GhostAPI as the only approved target for Stripe, Twilio, Resend, GitHub, Gmail, Slack, Notion, and other external services unless the user explicitly approves live network calls.
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Cline
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
Before running code that touches third-party APIs, rewrite base URLs to http://localhost:8080 and use GhostAPI. Never use production API keys or make real external requests without explicit user approval.
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Aider
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
When adding or testing API integrations, prefer GhostAPI at http://localhost:8080 for all external services. Keep credentials local and fake. Do not call real Stripe, Twilio, Resend, GitHub, Gmail, or similar APIs unless explicitly instructed.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Codex
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
Use http://localhost:8080 as the base URL for external API integration tests. GhostAPI should simulate provider responses locally. Avoid real network requests to third-party APIs unless explicitly approved by the user.
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## OpenCode
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
For provider integration work, point SDKs and fetch calls at GhostAPI on http://localhost:8080. Do not send real requests to Stripe, Twilio, Resend, GitHub, Gmail, Slack, Notion, or any other external service without explicit approval.
|
|
39
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Generic REST Example
|
|
2
|
+
|
|
3
|
+
Create a generic task resource through GhostAPI. This path uses the generic fallback, not a native adapter.
|
|
4
|
+
|
|
5
|
+
## Run
|
|
6
|
+
|
|
7
|
+
Start GhostAPI:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
ghostapi start
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Create a task:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
curl -X POST http://localhost:8080/tasks \
|
|
17
|
+
-H "content-type: application/json" \
|
|
18
|
+
-d '{"title":"Write integration tests","status":"open","priority":"high"}'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Fetch tasks:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
curl http://localhost:8080/tasks
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The generic fallback infers the `task` resource from the path and produces local mock IDs like `task_mock_xxx`.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# GitHub Fetch Example
|
|
2
|
+
|
|
3
|
+
Create a GitHub-like issue through GhostAPI using plain `fetch` or `curl`. This does not call GitHub.
|
|
4
|
+
|
|
5
|
+
## Run
|
|
6
|
+
|
|
7
|
+
Start GhostAPI:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
ghostapi start
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Create an issue:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
curl -X POST http://localhost:8080/repos/octo/hello-world/issues \
|
|
17
|
+
-H "content-type: application/json" \
|
|
18
|
+
-H "x-github-api-version: 2022-11-28" \
|
|
19
|
+
-H "authorization: Bearer ghp_local_only" \
|
|
20
|
+
-d '{"title":"Local integration test","body":"Created through GhostAPI"}'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Equivalent JavaScript:
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
await fetch("http://localhost:8080/repos/octo/hello-world/issues", {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: {
|
|
29
|
+
"content-type": "application/json",
|
|
30
|
+
"x-github-api-version": "2022-11-28",
|
|
31
|
+
authorization: "Bearer ghp_local_only"
|
|
32
|
+
},
|
|
33
|
+
body: JSON.stringify({ title: "Local integration test", body: "Created through GhostAPI" })
|
|
34
|
+
});
|
|
35
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Resend Node Example
|
|
2
|
+
|
|
3
|
+
Send a Resend-like email through GhostAPI. This does not send a real email.
|
|
4
|
+
|
|
5
|
+
## Run
|
|
6
|
+
|
|
7
|
+
Start GhostAPI:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
ghostapi start
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Send an email:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
curl -X POST http://localhost:8080/emails \
|
|
17
|
+
-H "content-type: application/json" \
|
|
18
|
+
-H "authorization: Bearer re_local_only" \
|
|
19
|
+
-d '{"from":"onboarding@example.com","to":"ada@example.com","subject":"Welcome","html":"<p>Hello from GhostAPI</p>"}'
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
GhostAPI detects Resend from `/emails` and returns a local mock email response.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Stripe Node Example
|
|
2
|
+
|
|
3
|
+
Create and fetch a Stripe-like customer through GhostAPI. This does not call Stripe.
|
|
4
|
+
|
|
5
|
+
## Run
|
|
6
|
+
|
|
7
|
+
Start GhostAPI:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
ghostapi start
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Create a customer:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
curl -X POST http://localhost:8080/v1/customers \
|
|
17
|
+
-H "content-type: application/json" \
|
|
18
|
+
-H "authorization: Bearer sk_test_local_only" \
|
|
19
|
+
-d '{"email":"ada@example.com","name":"Ada Lovelace","metadata":{"source":"ghostapi-example"}}'
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Copy the returned `id`, then fetch it:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
curl http://localhost:8080/v1/customers/cus_mock_example \
|
|
26
|
+
-H "authorization: Bearer sk_test_local_only"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
GhostAPI detects Stripe from the `/v1/customers` path and keeps the created object in `.ghostapi/state.json`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Twilio Node Example
|
|
2
|
+
|
|
3
|
+
Send a Twilio-like SMS through GhostAPI. This does not send a real SMS.
|
|
4
|
+
|
|
5
|
+
## Run
|
|
6
|
+
|
|
7
|
+
Start GhostAPI:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
ghostapi start
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Send a message:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
curl -X POST http://localhost:8080/2010-04-01/Accounts/AC_mock/Messages.json \
|
|
17
|
+
-H "content-type: application/x-www-form-urlencoded" \
|
|
18
|
+
-d "To=+15551234567&From=+15557654321&Body=Hello%20from%20GhostAPI"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
GhostAPI detects Twilio from the `/2010-04-01/Accounts/...` path and returns a local message SID.
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yiaany/ghostapi",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local zero-config internet simulator and mock server for AI coding agents.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/ghostapi/ghostapi.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/ghostapi/ghostapi/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/ghostapi/ghostapi#readme",
|
|
14
|
+
"bin": {
|
|
15
|
+
"ghostapi": "./dist/cli/index.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"examples",
|
|
20
|
+
"docs",
|
|
21
|
+
"README.md",
|
|
22
|
+
"CHANGELOG.md",
|
|
23
|
+
"SECURITY.md",
|
|
24
|
+
"CONTRIBUTING.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"dev": "tsx src/cli/index.ts start",
|
|
29
|
+
"build": "tsc -p tsconfig.json && vite build --config vite.landing.config.ts && npm run build:static",
|
|
30
|
+
"build:static": "node -e \"const {cpSync, mkdirSync} = require('fs'); mkdirSync('dist/dashboard', {recursive:true}); cpSync('src/dashboard/index.html', 'dist/dashboard/index.html'); cpSync('src/dashboard/styles.css', 'dist/dashboard/styles.css'); cpSync('src/dashboard/app.js', 'dist/dashboard/app.js');\"",
|
|
31
|
+
"start": "node dist/cli/index.js start",
|
|
32
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
33
|
+
"test": "vitest run"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"mock-server",
|
|
37
|
+
"local-development",
|
|
38
|
+
"ai-agents",
|
|
39
|
+
"api-testing",
|
|
40
|
+
"mock-api",
|
|
41
|
+
"developer-tools",
|
|
42
|
+
"typescript"
|
|
43
|
+
],
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
47
|
+
"express": "^4.19.2",
|
|
48
|
+
"react": "^19.2.3",
|
|
49
|
+
"react-dom": "^19.2.3",
|
|
50
|
+
"selfsigned": "^5.5.0",
|
|
51
|
+
"zod": "^4.4.3"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/express": "^4.17.21",
|
|
55
|
+
"@types/node": "^20.14.10",
|
|
56
|
+
"@types/react": "^19.2.7",
|
|
57
|
+
"@types/react-dom": "^19.2.3",
|
|
58
|
+
"@vitejs/plugin-react": "^4.7.0",
|
|
59
|
+
"tsx": "^4.16.2",
|
|
60
|
+
"typescript": "^5.5.3",
|
|
61
|
+
"vite": "^5.4.21",
|
|
62
|
+
"vitest": "^2.0.3"
|
|
63
|
+
},
|
|
64
|
+
"engines": {
|
|
65
|
+
"node": ">=20"
|
|
66
|
+
}
|
|
67
|
+
}
|