@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.
Files changed (184) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/CONTRIBUTING.md +117 -0
  3. package/LICENSE +21 -0
  4. package/README.md +354 -0
  5. package/SECURITY.md +37 -0
  6. package/dist/agents/agentPrompt.d.ts +5 -0
  7. package/dist/agents/agentPrompt.js +30 -0
  8. package/dist/agents/agentPrompt.js.map +1 -0
  9. package/dist/ai/aiClient.d.ts +7 -0
  10. package/dist/ai/aiClient.js +30 -0
  11. package/dist/ai/aiClient.js.map +1 -0
  12. package/dist/ai/aiGenerator.d.ts +10 -0
  13. package/dist/ai/aiGenerator.js +101 -0
  14. package/dist/ai/aiGenerator.js.map +1 -0
  15. package/dist/ai/genericInference.d.ts +5 -0
  16. package/dist/ai/genericInference.js +80 -0
  17. package/dist/ai/genericInference.js.map +1 -0
  18. package/dist/ai/index.d.ts +3 -0
  19. package/dist/ai/index.js +2 -0
  20. package/dist/ai/index.js.map +1 -0
  21. package/dist/ai/jsonRepair.d.ts +2 -0
  22. package/dist/ai/jsonRepair.js +31 -0
  23. package/dist/ai/jsonRepair.js.map +1 -0
  24. package/dist/ai/prompts.d.ts +4 -0
  25. package/dist/ai/prompts.js +81 -0
  26. package/dist/ai/prompts.js.map +1 -0
  27. package/dist/behavior/behaviorStore.d.ts +12 -0
  28. package/dist/behavior/behaviorStore.js +72 -0
  29. package/dist/behavior/behaviorStore.js.map +1 -0
  30. package/dist/cache/index.d.ts +9 -0
  31. package/dist/cache/index.js +34 -0
  32. package/dist/cache/index.js.map +1 -0
  33. package/dist/cli/errors.d.ts +4 -0
  34. package/dist/cli/errors.js +9 -0
  35. package/dist/cli/errors.js.map +1 -0
  36. package/dist/cli/index.d.ts +2 -0
  37. package/dist/cli/index.js +277 -0
  38. package/dist/cli/index.js.map +1 -0
  39. package/dist/cli/parser.d.ts +56 -0
  40. package/dist/cli/parser.js +169 -0
  41. package/dist/cli/parser.js.map +1 -0
  42. package/dist/config/localConfig.d.ts +16 -0
  43. package/dist/config/localConfig.js +58 -0
  44. package/dist/config/localConfig.js.map +1 -0
  45. package/dist/config/serverConfig.d.ts +13 -0
  46. package/dist/config/serverConfig.js +44 -0
  47. package/dist/config/serverConfig.js.map +1 -0
  48. package/dist/dashboard/app.js +615 -0
  49. package/dist/dashboard/dashboard.d.ts +4 -0
  50. package/dist/dashboard/dashboard.js +20 -0
  51. package/dist/dashboard/dashboard.js.map +1 -0
  52. package/dist/dashboard/index.d.ts +1 -0
  53. package/dist/dashboard/index.html +298 -0
  54. package/dist/dashboard/index.js +2 -0
  55. package/dist/dashboard/index.js.map +1 -0
  56. package/dist/dashboard/styles.css +349 -0
  57. package/dist/errors/errorEngine.d.ts +3 -0
  58. package/dist/errors/errorEngine.js +65 -0
  59. package/dist/errors/errorEngine.js.map +1 -0
  60. package/dist/errors/index.d.ts +3 -0
  61. package/dist/errors/index.js +3 -0
  62. package/dist/errors/index.js.map +1 -0
  63. package/dist/errors/providerErrors.d.ts +3 -0
  64. package/dist/errors/providerErrors.js +6 -0
  65. package/dist/errors/providerErrors.js.map +1 -0
  66. package/dist/fault/faultLab.d.ts +27 -0
  67. package/dist/fault/faultLab.js +89 -0
  68. package/dist/fault/faultLab.js.map +1 -0
  69. package/dist/index.d.ts +2 -0
  70. package/dist/index.js +2 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/landing/assets/index-BXR41fAD.css +1 -0
  73. package/dist/landing/assets/index-D8_ggMRE.js +55 -0
  74. package/dist/landing/index.html +15 -0
  75. package/dist/landing/landing.d.ts +3 -0
  76. package/dist/landing/landing.js +12 -0
  77. package/dist/landing/landing.js.map +1 -0
  78. package/dist/mcp/server.d.ts +1 -0
  79. package/dist/mcp/server.js +52 -0
  80. package/dist/mcp/server.js.map +1 -0
  81. package/dist/providers/discord.d.ts +2 -0
  82. package/dist/providers/discord.js +11 -0
  83. package/dist/providers/discord.js.map +1 -0
  84. package/dist/providers/generic.d.ts +2 -0
  85. package/dist/providers/generic.js +13 -0
  86. package/dist/providers/generic.js.map +1 -0
  87. package/dist/providers/github.d.ts +2 -0
  88. package/dist/providers/github.js +11 -0
  89. package/dist/providers/github.js.map +1 -0
  90. package/dist/providers/index.d.ts +2 -0
  91. package/dist/providers/index.js +2 -0
  92. package/dist/providers/index.js.map +1 -0
  93. package/dist/providers/openai.d.ts +2 -0
  94. package/dist/providers/openai.js +15 -0
  95. package/dist/providers/openai.js.map +1 -0
  96. package/dist/providers/registry.d.ts +12 -0
  97. package/dist/providers/registry.js +23 -0
  98. package/dist/providers/registry.js.map +1 -0
  99. package/dist/providers/resend.d.ts +2 -0
  100. package/dist/providers/resend.js +12 -0
  101. package/dist/providers/resend.js.map +1 -0
  102. package/dist/providers/stripe.d.ts +2 -0
  103. package/dist/providers/stripe.js +15 -0
  104. package/dist/providers/stripe.js.map +1 -0
  105. package/dist/providers/twilio.d.ts +2 -0
  106. package/dist/providers/twilio.js +14 -0
  107. package/dist/providers/twilio.js.map +1 -0
  108. package/dist/providers/types.d.ts +13 -0
  109. package/dist/providers/types.js +2 -0
  110. package/dist/providers/types.js.map +1 -0
  111. package/dist/proxy/cacheKey.d.ts +2 -0
  112. package/dist/proxy/cacheKey.js +37 -0
  113. package/dist/proxy/cacheKey.js.map +1 -0
  114. package/dist/proxy/providerDetector.d.ts +11 -0
  115. package/dist/proxy/providerDetector.js +74 -0
  116. package/dist/proxy/providerDetector.js.map +1 -0
  117. package/dist/proxy/proxyHandler.d.ts +3 -0
  118. package/dist/proxy/proxyHandler.js +114 -0
  119. package/dist/proxy/proxyHandler.js.map +1 -0
  120. package/dist/proxy/requestNormalizer.d.ts +11 -0
  121. package/dist/proxy/requestNormalizer.js +30 -0
  122. package/dist/proxy/requestNormalizer.js.map +1 -0
  123. package/dist/report/safetyReport.d.ts +13 -0
  124. package/dist/report/safetyReport.js +65 -0
  125. package/dist/report/safetyReport.js.map +1 -0
  126. package/dist/rules/aiRules.d.ts +7 -0
  127. package/dist/rules/aiRules.js +31 -0
  128. package/dist/rules/aiRules.js.map +1 -0
  129. package/dist/scenarios/scenarioStore.d.ts +37 -0
  130. package/dist/scenarios/scenarioStore.js +201 -0
  131. package/dist/scenarios/scenarioStore.js.map +1 -0
  132. package/dist/security/headerSanitizer.d.ts +3 -0
  133. package/dist/security/headerSanitizer.js +12 -0
  134. package/dist/security/headerSanitizer.js.map +1 -0
  135. package/dist/security/maskSecrets.d.ts +1 -0
  136. package/dist/security/maskSecrets.js +2 -0
  137. package/dist/security/maskSecrets.js.map +1 -0
  138. package/dist/security/secrets.d.ts +4 -0
  139. package/dist/security/secrets.js +61 -0
  140. package/dist/security/secrets.js.map +1 -0
  141. package/dist/server/createServer.d.ts +8 -0
  142. package/dist/server/createServer.js +42 -0
  143. package/dist/server/createServer.js.map +1 -0
  144. package/dist/server/eventsStore.d.ts +17 -0
  145. package/dist/server/eventsStore.js +31 -0
  146. package/dist/server/eventsStore.js.map +1 -0
  147. package/dist/server/routes.d.ts +3 -0
  148. package/dist/server/routes.js +189 -0
  149. package/dist/server/routes.js.map +1 -0
  150. package/dist/server/sse.d.ts +5 -0
  151. package/dist/server/sse.js +32 -0
  152. package/dist/server/sse.js.map +1 -0
  153. package/dist/setup/sdkDetector.d.ts +9 -0
  154. package/dist/setup/sdkDetector.js +31 -0
  155. package/dist/setup/sdkDetector.js.map +1 -0
  156. package/dist/setup/setupGenerator.d.ts +28 -0
  157. package/dist/setup/setupGenerator.js +146 -0
  158. package/dist/setup/setupGenerator.js.map +1 -0
  159. package/dist/state/index.d.ts +1 -0
  160. package/dist/state/index.js +2 -0
  161. package/dist/state/index.js.map +1 -0
  162. package/dist/state/stateExtractor.d.ts +3 -0
  163. package/dist/state/stateExtractor.js +36 -0
  164. package/dist/state/stateExtractor.js.map +1 -0
  165. package/dist/state/stateResolver.d.ts +8 -0
  166. package/dist/state/stateResolver.js +95 -0
  167. package/dist/state/stateResolver.js.map +1 -0
  168. package/dist/state/stateStore.d.ts +4 -0
  169. package/dist/state/stateStore.js +72 -0
  170. package/dist/state/stateStore.js.map +1 -0
  171. package/dist/tests/testGenerator.d.ts +6 -0
  172. package/dist/tests/testGenerator.js +33 -0
  173. package/dist/tests/testGenerator.js.map +1 -0
  174. package/dist/utils/json.d.ts +2 -0
  175. package/dist/utils/json.js +10 -0
  176. package/dist/utils/json.js.map +1 -0
  177. package/docs/release-checklist.md +71 -0
  178. package/examples/agent-instructions/README.md +39 -0
  179. package/examples/generic-rest/README.md +27 -0
  180. package/examples/github-fetch/README.md +35 -0
  181. package/examples/resend-node/README.md +22 -0
  182. package/examples/stripe-node/README.md +29 -0
  183. package/examples/twilio-node/README.md +21 -0
  184. 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,6 @@
1
+ import type { ProxyEvent } from "../server/eventsStore.js";
2
+ export type GeneratedTest = {
3
+ filename: string;
4
+ content: string;
5
+ };
6
+ export declare function generateVitestFromEvent(event: ProxyEvent): GeneratedTest;
@@ -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,2 @@
1
+ export declare function isJsonObject(value: unknown): value is Record<string, unknown>;
2
+ export declare function hasBooleanFlag(value: unknown, flag: string): boolean;
@@ -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
+ }