@apifuse/provider-sdk 2.1.0-beta.9 → 2.2.0-beta.10

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 (263) hide show
  1. package/AUTHORING.md +240 -0
  2. package/CHANGELOG.md +93 -0
  3. package/README.md +26 -10
  4. package/SUBMISSION.md +11 -12
  5. package/bin/apifuse-check.ts +44 -59
  6. package/bin/apifuse-create.ts +1 -1
  7. package/bin/apifuse-dev.ts +27 -52
  8. package/bin/apifuse-pack-check.ts +36 -0
  9. package/bin/apifuse-pack-smoke.ts +22 -81
  10. package/bin/apifuse-pack-types.ts +266 -0
  11. package/bin/apifuse-perf.ts +45 -127
  12. package/bin/apifuse-record.ts +53 -70
  13. package/bin/apifuse-submit-check.ts +2177 -353
  14. package/bin/apifuse-sync-assets.ts +117 -0
  15. package/bin/apifuse.ts +1 -1
  16. package/bin/submit-check-delimited-text.ts +50 -0
  17. package/bin/submit-check-xml-semantics.ts +204 -0
  18. package/bin/submit-check-xml.ts +134 -0
  19. package/dist/auth-turn/auth-turn.v1.schema.json +89 -0
  20. package/dist/auth-turn/fixtures/invalid/empty-kind.json +4 -0
  21. package/dist/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
  22. package/dist/auth-turn/fixtures/invalid/missing-kind.json +3 -0
  23. package/dist/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
  24. package/dist/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
  25. package/dist/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
  26. package/dist/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
  27. package/dist/auth-turn/fixtures/valid/abort.json +8 -0
  28. package/dist/auth-turn/fixtures/valid/challenge.json +17 -0
  29. package/dist/auth-turn/fixtures/valid/complete.json +13 -0
  30. package/dist/auth-turn/fixtures/valid/form.json +14 -0
  31. package/dist/auth-turn/fixtures/valid/message.json +13 -0
  32. package/dist/auth-turn/fixtures/valid/multi_choice.json +15 -0
  33. package/dist/auth-turn/fixtures/valid/pending.json +5 -0
  34. package/dist/auth-turn/fixtures/valid/poll.json +9 -0
  35. package/dist/auth-turn/fixtures/valid/redirect.json +16 -0
  36. package/dist/auth-turn/fixtures/valid/retry.json +8 -0
  37. package/dist/auth-turn/fixtures/valid/unknown-kind.json +7 -0
  38. package/dist/auth-turn/index.d.ts +195 -0
  39. package/dist/auth-turn/index.js +133 -0
  40. package/dist/auth.d.ts +76 -0
  41. package/dist/auth.js +427 -0
  42. package/dist/ceremonies/index.d.ts +1 -1
  43. package/dist/ceremonies/index.js +14 -48
  44. package/dist/cli/commands.d.ts +1 -1
  45. package/dist/cli/commands.js +8 -0
  46. package/dist/cli/create.d.ts +3 -0
  47. package/dist/cli/create.js +47 -33
  48. package/dist/cli/prompt-assets.d.ts +80 -0
  49. package/dist/cli/prompt-assets.js +743 -0
  50. package/dist/cli/templates/provider/.agents/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
  51. package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
  52. package/dist/cli/templates/provider/.agents/skills/normalization-standards/SKILL.md.tpl +57 -0
  53. package/dist/cli/templates/provider/.agents/skills/pagination-and-counts/SKILL.md.tpl +52 -0
  54. package/dist/cli/templates/provider/.agents/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
  55. package/dist/cli/templates/provider/.agents/skills/upstream-notes/README.md.tpl +13 -0
  56. package/dist/cli/templates/provider/.dockerignore.tpl +22 -0
  57. package/dist/cli/templates/provider/.gitignore.tpl +22 -0
  58. package/dist/cli/templates/provider/AGENTS.md.tpl +96 -0
  59. package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
  60. package/dist/cli/templates/provider/README.md.tpl +163 -0
  61. package/dist/cli/templates/provider/dev.ts.tpl +5 -0
  62. package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
  63. package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
  64. package/dist/cli/templates/provider/index.ts.tpl +15 -0
  65. package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
  66. package/dist/cli/templates/provider/meta.ts.tpl +7 -0
  67. package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
  68. package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
  69. package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
  70. package/dist/cli/templates/provider/start.ts.tpl +5 -0
  71. package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
  72. package/dist/config/loader.d.ts +149 -8
  73. package/dist/config/loader.js +378 -89
  74. package/dist/contract-serialization.d.ts +2 -2
  75. package/dist/contract-serialization.js +3 -6
  76. package/dist/contract-types.d.ts +2 -2
  77. package/dist/contract.d.ts +3 -3
  78. package/dist/contract.js +5 -6
  79. package/dist/define.d.ts +13 -1
  80. package/dist/define.js +245 -178
  81. package/dist/dev.d.ts +1 -1
  82. package/dist/dev.js +1 -1
  83. package/dist/errors.d.ts +4 -1
  84. package/dist/errors.js +48 -0
  85. package/dist/i18n/catalog.d.ts +2 -2
  86. package/dist/i18n/catalog.js +4 -10
  87. package/dist/i18n/index.d.ts +2 -2
  88. package/dist/i18n/index.js +2 -2
  89. package/dist/i18n/keys.d.ts +2 -2
  90. package/dist/index.d.ts +44 -41
  91. package/dist/index.js +39 -36
  92. package/dist/lint.d.ts +1 -0
  93. package/dist/lint.js +35 -15
  94. package/dist/provider.d.ts +11 -9
  95. package/dist/provider.js +9 -8
  96. package/dist/public-schema-field-lint.d.ts +1 -1
  97. package/dist/recipes/gov-api.js +1 -1
  98. package/dist/runtime/auth-flow.d.ts +1 -1
  99. package/dist/runtime/auth-flow.js +4 -2
  100. package/dist/runtime/browser.d.ts +1 -1
  101. package/dist/runtime/browser.js +214 -25
  102. package/dist/runtime/cache.d.ts +1 -1
  103. package/dist/runtime/cache.js +4 -8
  104. package/dist/runtime/choice.d.ts +1 -1
  105. package/dist/runtime/choice.js +31 -35
  106. package/dist/runtime/credential.d.ts +1 -1
  107. package/dist/runtime/credential.js +1 -1
  108. package/dist/runtime/env.d.ts +1 -1
  109. package/dist/runtime/executor.d.ts +1 -1
  110. package/dist/runtime/executor.js +15 -3
  111. package/dist/runtime/http.d.ts +2 -2
  112. package/dist/runtime/http.js +160 -344
  113. package/dist/runtime/insights.d.ts +1 -1
  114. package/dist/runtime/insights.js +6 -13
  115. package/dist/runtime/instrumentation.d.ts +2 -2
  116. package/dist/runtime/instrumentation.js +56 -19
  117. package/dist/runtime/keyring.js +1 -1
  118. package/dist/runtime/namespace.js +1 -1
  119. package/dist/runtime/otlp.d.ts +1 -1
  120. package/dist/runtime/perf.d.ts +1 -1
  121. package/dist/runtime/provider.d.ts +1 -1
  122. package/dist/runtime/provider.js +1 -2
  123. package/dist/runtime/proxy-errors.d.ts +1 -1
  124. package/dist/runtime/proxy-errors.js +9 -7
  125. package/dist/runtime/proxy-nodemaven.d.ts +35 -0
  126. package/dist/runtime/proxy-nodemaven.js +128 -0
  127. package/dist/runtime/proxy-retry-policy.d.ts +40 -0
  128. package/dist/runtime/proxy-retry-policy.js +326 -0
  129. package/dist/runtime/proxy-telemetry.d.ts +2 -1
  130. package/dist/runtime/proxy-telemetry.js +55 -52
  131. package/dist/runtime/redis.d.ts +1 -1
  132. package/dist/runtime/redis.js +2 -4
  133. package/dist/runtime/request-options.d.ts +1 -1
  134. package/dist/runtime/secrets.d.ts +27 -0
  135. package/dist/runtime/secrets.js +51 -0
  136. package/dist/runtime/state.d.ts +2 -2
  137. package/dist/runtime/state.js +15 -4
  138. package/dist/runtime/stealth.d.ts +7 -4
  139. package/dist/runtime/stealth.js +257 -215
  140. package/dist/runtime/stt.d.ts +1 -1
  141. package/dist/runtime/stt.js +11 -15
  142. package/dist/runtime/trace.d.ts +2 -2
  143. package/dist/runtime/trace.js +2 -4
  144. package/dist/runtime/waterfall.d.ts +1 -1
  145. package/dist/schema.d.ts +1 -1
  146. package/dist/schema.js +7 -15
  147. package/dist/serve.d.ts +1 -1
  148. package/dist/serve.js +1 -1
  149. package/dist/server/index.d.ts +7 -3
  150. package/dist/server/index.js +6 -2
  151. package/dist/server/self-test-input-tokens.d.ts +1 -0
  152. package/dist/server/self-test-input-tokens.js +37 -0
  153. package/dist/server/self-test-redaction.d.ts +20 -0
  154. package/dist/server/self-test-redaction.js +70 -0
  155. package/dist/server/self-test-token.d.ts +30 -0
  156. package/dist/server/self-test-token.js +50 -0
  157. package/dist/server/self-test.d.ts +199 -0
  158. package/dist/server/self-test.js +1113 -0
  159. package/dist/server/serve.d.ts +14 -3
  160. package/dist/server/serve.js +135 -64
  161. package/dist/server/types.d.ts +10 -9
  162. package/dist/server/types.js +3 -7
  163. package/dist/stealth/profiles.d.ts +1 -1
  164. package/dist/stealth/profiles.js +5 -14
  165. package/dist/stream.d.ts +1 -1
  166. package/dist/testing/index.d.ts +2 -2
  167. package/dist/testing/index.js +2 -2
  168. package/dist/testing/run.d.ts +1 -1
  169. package/dist/testing/run.js +12 -15
  170. package/dist/types.d.ts +237 -1
  171. package/dist/user-input.d.ts +30 -0
  172. package/dist/user-input.js +66 -0
  173. package/package.json +16 -5
  174. package/src/auth-turn/auth-turn.v1.schema.json +89 -0
  175. package/src/auth-turn/fixtures/invalid/empty-kind.json +4 -0
  176. package/src/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
  177. package/src/auth-turn/fixtures/invalid/missing-kind.json +3 -0
  178. package/src/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
  179. package/src/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
  180. package/src/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
  181. package/src/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
  182. package/src/auth-turn/fixtures/valid/abort.json +8 -0
  183. package/src/auth-turn/fixtures/valid/challenge.json +17 -0
  184. package/src/auth-turn/fixtures/valid/complete.json +13 -0
  185. package/src/auth-turn/fixtures/valid/form.json +14 -0
  186. package/src/auth-turn/fixtures/valid/message.json +13 -0
  187. package/src/auth-turn/fixtures/valid/multi_choice.json +15 -0
  188. package/src/auth-turn/fixtures/valid/pending.json +5 -0
  189. package/src/auth-turn/fixtures/valid/poll.json +9 -0
  190. package/src/auth-turn/fixtures/valid/redirect.json +16 -0
  191. package/src/auth-turn/fixtures/valid/retry.json +8 -0
  192. package/src/auth-turn/fixtures/valid/unknown-kind.json +7 -0
  193. package/src/auth-turn/index.ts +177 -0
  194. package/src/auth.ts +728 -0
  195. package/src/ceremonies/index.ts +33 -121
  196. package/src/cli/commands.ts +10 -0
  197. package/src/cli/create.ts +69 -99
  198. package/src/cli/prompt-assets.ts +865 -0
  199. package/src/cli/templates/provider/.agents/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
  200. package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
  201. package/src/cli/templates/provider/.agents/skills/normalization-standards/SKILL.md.tpl +57 -0
  202. package/src/cli/templates/provider/.agents/skills/pagination-and-counts/SKILL.md.tpl +52 -0
  203. package/src/cli/templates/provider/.agents/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
  204. package/src/cli/templates/provider/.agents/skills/upstream-notes/README.md.tpl +13 -0
  205. package/src/cli/templates/provider/AGENTS.md.tpl +96 -0
  206. package/src/cli/templates/provider/README.md.tpl +7 -4
  207. package/src/config/loader.ts +543 -208
  208. package/src/contract-serialization.ts +5 -11
  209. package/src/contract-types.ts +2 -2
  210. package/src/contract.ts +13 -28
  211. package/src/define.ts +397 -528
  212. package/src/dev.ts +4 -9
  213. package/src/errors.ts +58 -5
  214. package/src/i18n/catalog.ts +10 -32
  215. package/src/i18n/index.ts +2 -2
  216. package/src/i18n/keys.ts +5 -11
  217. package/src/index.ts +64 -41
  218. package/src/lint.ts +122 -159
  219. package/src/provider.ts +40 -9
  220. package/src/public-schema-field-lint.ts +7 -33
  221. package/src/recipes/gov-api.ts +2 -5
  222. package/src/runtime/auth-flow.ts +6 -6
  223. package/src/runtime/browser.ts +320 -151
  224. package/src/runtime/cache.ts +20 -67
  225. package/src/runtime/choice.ts +79 -132
  226. package/src/runtime/credential.ts +2 -2
  227. package/src/runtime/env.ts +1 -1
  228. package/src/runtime/executor.ts +23 -20
  229. package/src/runtime/http.ts +216 -539
  230. package/src/runtime/insights.ts +15 -53
  231. package/src/runtime/instrumentation.ts +78 -65
  232. package/src/runtime/keyring.ts +7 -19
  233. package/src/runtime/namespace.ts +2 -7
  234. package/src/runtime/otlp.ts +12 -23
  235. package/src/runtime/perf.ts +1 -1
  236. package/src/runtime/provider.ts +4 -9
  237. package/src/runtime/proxy-errors.ts +29 -42
  238. package/src/runtime/proxy-nodemaven.ts +178 -0
  239. package/src/runtime/proxy-retry-policy.ts +469 -0
  240. package/src/runtime/proxy-telemetry.ts +79 -77
  241. package/src/runtime/redis.ts +4 -12
  242. package/src/runtime/request-options.ts +4 -13
  243. package/src/runtime/secrets.ts +64 -0
  244. package/src/runtime/state.ts +41 -110
  245. package/src/runtime/stealth.ts +331 -369
  246. package/src/runtime/stt.ts +38 -94
  247. package/src/runtime/trace.ts +14 -44
  248. package/src/runtime/waterfall.ts +5 -18
  249. package/src/schema.ts +23 -84
  250. package/src/serve.ts +1 -1
  251. package/src/server/index.ts +44 -3
  252. package/src/server/self-test-input-tokens.ts +46 -0
  253. package/src/server/self-test-redaction.ts +97 -0
  254. package/src/server/self-test-token.ts +70 -0
  255. package/src/server/self-test.ts +1450 -0
  256. package/src/server/serve.ts +206 -216
  257. package/src/server/types.ts +7 -19
  258. package/src/stealth/profiles.ts +10 -26
  259. package/src/stream.ts +8 -19
  260. package/src/testing/index.ts +2 -2
  261. package/src/testing/run.ts +24 -64
  262. package/src/types.ts +274 -1
  263. package/src/user-input.ts +118 -0
package/src/cli/create.ts CHANGED
@@ -1,21 +1,18 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
- import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { mkdir, readFile, rm, symlink, writeFile } from "node:fs/promises";
4
4
  import { dirname, relative, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
7
- import {
8
- cancel,
9
- intro,
10
- isCancel,
11
- note,
12
- outro,
13
- select,
14
- text,
15
- } from "@clack/prompts";
7
+ import { cancel, intro, isCancel, note, outro, select, text } from "@clack/prompts";
16
8
  import { z } from "zod";
17
9
 
18
10
  import packageJson from "../../package.json";
11
+ import {
12
+ buildPromptAssetManifest,
13
+ buildPromptAssetPlanEntries,
14
+ PROMPT_ASSET_MANIFEST_PATH,
15
+ } from "./prompt-assets.js";
19
16
 
20
17
  export const PROVIDER_NAME_REGEX = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
21
18
  export const CATEGORY_OPTIONS = [
@@ -28,12 +25,7 @@ export const CATEGORY_OPTIONS = [
28
25
  "communication",
29
26
  "other",
30
27
  ] as const;
31
- export const AUTH_MODE_OPTIONS = [
32
- "none",
33
- "platform-managed",
34
- "credentials",
35
- "oauth2",
36
- ] as const;
28
+ export const AUTH_MODE_OPTIONS = ["none", "platform-managed", "credentials", "oauth2"] as const;
37
29
  export const RUNTIME_OPTIONS = ["standard", "browser"] as const;
38
30
  export const PRESET_OPTIONS = ["standalone", "monorepo"] as const;
39
31
 
@@ -77,7 +69,10 @@ export type CreateResolvedOptions = {
77
69
 
78
70
  export type ProviderPlanFile = {
79
71
  path: string;
72
+ /** File content, or the symlink target (relative path) for kind "symlink". */
80
73
  content: string;
74
+ /** Absent kind means "file" (backward compatible with older consumers). */
75
+ kind?: "file" | "symlink";
81
76
  };
82
77
 
83
78
  export type ProviderCreatePlan = {
@@ -95,9 +90,7 @@ export type ProviderCreatePlan = {
95
90
  workspaceRoot?: string;
96
91
  };
97
92
 
98
- const TEMPLATE_DIR = fileURLToPath(
99
- new URL("./templates/provider/", import.meta.url),
100
- );
93
+ const TEMPLATE_DIR = fileURLToPath(new URL("./templates/provider/", import.meta.url));
101
94
  const HELP_TEXT = `Usage: apifuse create <provider-name> [options]
102
95
  Examples:
103
96
  apifuse create my-provider
@@ -126,9 +119,7 @@ export async function main() {
126
119
  }
127
120
 
128
121
  const parsed = parseArgs(normalizedArgs);
129
- const config = parsed.configPath
130
- ? await loadConfig(parsed.configPath)
131
- : undefined;
122
+ const config = parsed.configPath ? await loadConfig(parsed.configPath) : undefined;
132
123
  const resolved = await resolveCreateOptions(parsed, config, process.cwd());
133
124
  const plan = await buildProviderCreatePlan(resolved, process.cwd());
134
125
 
@@ -195,18 +186,10 @@ function parseArgs(argv: string[]): ParsedArgs {
195
186
  parsed.displayName = ensureValue(flag, consumeValue());
196
187
  break;
197
188
  case "--category":
198
- parsed.category = parseEnum(
199
- "category",
200
- consumeValue(),
201
- CATEGORY_OPTIONS,
202
- );
189
+ parsed.category = parseEnum("category", consumeValue(), CATEGORY_OPTIONS);
203
190
  break;
204
191
  case "--auth-mode":
205
- parsed.authMode = parseEnum(
206
- "auth mode",
207
- consumeValue(),
208
- AUTH_MODE_OPTIONS,
209
- );
192
+ parsed.authMode = parseEnum("auth mode", consumeValue(), AUTH_MODE_OPTIONS);
210
193
  break;
211
194
  case "--runtime":
212
195
  parsed.runtime = parseEnum("runtime", consumeValue(), RUNTIME_OPTIONS);
@@ -253,9 +236,7 @@ function parseEnum<T extends readonly string[]>(
253
236
  const resolvedValue = ensureValue(`--${label}`, value);
254
237
  const matchedValue = options.find((option) => option === resolvedValue);
255
238
  if (!matchedValue) {
256
- throw new Error(
257
- `Invalid ${label}: ${resolvedValue}. Expected one of: ${options.join(", ")}`,
258
- );
239
+ throw new Error(`Invalid ${label}: ${resolvedValue}. Expected one of: ${options.join(", ")}`);
259
240
  }
260
241
  return matchedValue;
261
242
  }
@@ -282,9 +263,7 @@ async function resolveCreateOptions(
282
263
  authMode: parsed.authMode ?? config?.authMode,
283
264
  runtime: parsed.runtime ?? config?.runtime,
284
265
  sdkSpecifier:
285
- parsed.sdkSpecifier ??
286
- config?.sdkSpecifier ??
287
- process.env.APIFUSE__SDK__SPECIFIER,
266
+ parsed.sdkSpecifier ?? config?.sdkSpecifier ?? process.env.APIFUSE__SDK__SPECIFIER,
288
267
  dryRun: parsed.dryRun,
289
268
  json: parsed.json,
290
269
  yes: parsed.yes,
@@ -298,9 +277,7 @@ async function resolveCreateOptions(
298
277
 
299
278
  if (partial.yes) {
300
279
  if (!partial.name) {
301
- throw new Error(
302
- "--yes requires a provider name (positional or via config).",
303
- );
280
+ throw new Error("--yes requires a provider name (positional or via config).");
304
281
  }
305
282
 
306
283
  return {
@@ -410,9 +387,7 @@ export async function buildProviderCreatePlan(
410
387
  cwd: string,
411
388
  ): Promise<ProviderCreatePlan> {
412
389
  const resolvedWorkspaceRoot =
413
- options.preset === "monorepo"
414
- ? findApifuseInternalWorkspaceRoot(cwd)
415
- : undefined;
390
+ options.preset === "monorepo" ? findApifuseInternalWorkspaceRoot(cwd) : undefined;
416
391
  if (options.preset === "monorepo" && !resolvedWorkspaceRoot) {
417
392
  throw new Error(
418
393
  "Monorepo preset is internal to the APIFuse repository. External bounty workspaces are one-provider repositories; use the standalone default create flow.",
@@ -424,9 +399,7 @@ export async function buildProviderCreatePlan(
424
399
  if (options.outputDir) {
425
400
  providerRoot = resolve(cwd, options.outputDir);
426
401
  installCwd =
427
- options.preset === "monorepo" && resolvedWorkspaceRoot
428
- ? resolvedWorkspaceRoot
429
- : providerRoot;
402
+ options.preset === "monorepo" && resolvedWorkspaceRoot ? resolvedWorkspaceRoot : providerRoot;
430
403
  } else if (options.preset === "monorepo" && resolvedWorkspaceRoot) {
431
404
  providerRoot = resolve(resolvedWorkspaceRoot, "providers", options.name);
432
405
  installCwd = resolvedWorkspaceRoot;
@@ -439,20 +412,22 @@ export async function buildProviderCreatePlan(
439
412
  throw new Error(`Target directory already exists: ${providerRoot}`);
440
413
  }
441
414
 
442
- if (
443
- options.sdkSpecifier?.startsWith("workspace:") &&
444
- !resolvedWorkspaceRoot
445
- ) {
415
+ if (options.sdkSpecifier?.startsWith("workspace:") && !resolvedWorkspaceRoot) {
446
416
  throw new Error(
447
417
  "workspace:* is only valid inside the APIFuse monorepo because public Provider SDK scaffolds must install from npm or an explicit tarball/file specifier.",
448
418
  );
449
419
  }
450
420
 
421
+ // Exact pin, not a caret range: the prompt-asset freshness gate requires
422
+ // manifest.sdkVersion === installed SDK version, and the manifest records
423
+ // this CLI's own version. A caret range would let `bun install` resolve a
424
+ // newer SDK than the create binary, making a fresh scaffold fail its own
425
+ // gate. `sync-assets` after an explicit SDK upgrade re-records the version.
451
426
  const sdkSpecifier =
452
427
  options.sdkSpecifier ??
453
428
  (options.preset === "monorepo" && resolvedWorkspaceRoot
454
429
  ? "workspace:*"
455
- : `^${packageJson.version}`);
430
+ : packageJson.version);
456
431
  const relativeProviderRoot = relative(cwd, providerRoot) || options.name;
457
432
  const nextDevCommand = `cd ${relativeProviderRoot} && bun run dev`;
458
433
  const packageName =
@@ -578,6 +553,21 @@ export async function buildProviderCreatePlan(
578
553
  },
579
554
  ];
580
555
 
556
+ const promptAssetEntries = await buildPromptAssetPlanEntries(renderTemplate);
557
+ for (const entry of promptAssetEntries) {
558
+ files.push({
559
+ path: resolve(providerRoot, entry.path),
560
+ content: entry.content,
561
+ ...(entry.kind === "symlink" ? { kind: "symlink" as const } : {}),
562
+ });
563
+ }
564
+ // Manifest last: writePlan writes in order, so a crash mid-scaffold never
565
+ // leaves a manifest that claims assets which were not written.
566
+ files.push({
567
+ path: resolve(providerRoot, PROMPT_ASSET_MANIFEST_PATH),
568
+ content: buildPromptAssetManifest(promptAssetEntries, packageJson.version),
569
+ });
570
+
581
571
  return {
582
572
  displayName: options.displayName,
583
573
  files,
@@ -592,17 +582,14 @@ export async function buildProviderCreatePlan(
592
582
  validationCommands: [
593
583
  "bun run check",
594
584
  "bun run type-check",
595
- "bun run submit-check",
585
+ "bun run submit-check -- --smoke",
596
586
  "bun run test",
597
587
  ],
598
588
  workspaceRoot: resolvedWorkspaceRoot,
599
589
  };
600
590
  }
601
591
 
602
- async function renderTemplate(
603
- fileName: string,
604
- values: Record<string, string>,
605
- ): Promise<string> {
592
+ async function renderTemplate(fileName: string, values: Record<string, string>): Promise<string> {
606
593
  const templatePath = resolve(TEMPLATE_DIR, fileName);
607
594
  const template = await readFile(templatePath, "utf8");
608
595
  return template.replace(/\{\{([A-Z_]+)\}\}/g, (_match, key: string) => {
@@ -610,10 +597,7 @@ async function renderTemplate(
610
597
  });
611
598
  }
612
599
 
613
- function renderPackageJson(input: {
614
- packageName: string;
615
- sdkSpecifier: string;
616
- }): string {
600
+ function renderPackageJson(input: { packageName: string; sdkSpecifier: string }): string {
617
601
  return `${JSON.stringify(
618
602
  {
619
603
  name: input.packageName,
@@ -625,11 +609,10 @@ function renderPackageJson(input: {
625
609
  dev: "apifuse dev .",
626
610
  check: "apifuse check . && bun run type-check",
627
611
  "type-check": "tsc --noEmit",
628
- "submit-check":
629
- "apifuse submit-check . --markdown submission-report.md",
612
+ "submit-check": "apifuse submit-check . --markdown submission-report.md",
613
+ "sync-assets": "apifuse sync-assets .",
630
614
  test: "apifuse test .",
631
615
  record: "apifuse record .",
632
- "perf:sample": "apifuse perf . --operation ping --runs 3",
633
616
  start: "bun start.ts",
634
617
  },
635
618
  dependencies: {
@@ -666,10 +649,7 @@ function renderTsconfig(): string {
666
649
  )}\n`;
667
650
  }
668
651
 
669
- function renderStarterLocaleCatalog(
670
- displayName: string,
671
- locale: "en" | "ko",
672
- ): string {
652
+ function renderStarterLocaleCatalog(displayName: string, locale: "en" | "ko"): string {
673
653
  const catalog = {
674
654
  meta: {
675
655
  displayName,
@@ -734,8 +714,8 @@ function renderAuthBlock(authMode: CreateAuthMode): string {
734
714
  return `{
735
715
  mode: "credentials",
736
716
  flow: {
737
- start: async () => ({
738
- kind: "input",
717
+ start: async (_ctx) => ({
718
+ kind: "form",
739
719
  turnId: crypto.randomUUID(),
740
720
  expectedInput: {
741
721
  schema: {
@@ -778,7 +758,7 @@ function renderAuthBlock(authMode: CreateAuthMode): string {
778
758
  return `{
779
759
  mode: "oauth2",
780
760
  flow: {
781
- start: async () => ({
761
+ start: async (_ctx) => ({
782
762
  kind: "redirect",
783
763
  turnId: crypto.randomUUID(),
784
764
  data: {
@@ -871,9 +851,7 @@ function isApifuseInternalWorkspaceRoot(workspaceRoot: string): boolean {
871
851
  return false;
872
852
  }
873
853
  try {
874
- const packageJson = JSON.parse(
875
- readFileSync(providerSdkPackageJsonPath, "utf8"),
876
- );
854
+ const packageJson = JSON.parse(readFileSync(providerSdkPackageJsonPath, "utf8"));
877
855
  return (
878
856
  typeof packageJson === "object" &&
879
857
  packageJson !== null &&
@@ -888,31 +866,28 @@ function isApifuseInternalWorkspaceRoot(workspaceRoot: string): boolean {
888
866
  async function writePlan(plan: ProviderCreatePlan): Promise<void> {
889
867
  for (const file of plan.files) {
890
868
  await mkdir(dirname(file.path), { recursive: true });
869
+ if (file.kind === "symlink") {
870
+ // rm operates on the link itself (lstat semantics) and tolerates
871
+ // directories, so whatever occupies the path is replaced by the link.
872
+ await rm(file.path, { recursive: true, force: true });
873
+ await symlink(file.content, file.path);
874
+ continue;
875
+ }
891
876
  await writeFile(file.path, file.content);
892
877
  }
893
878
  }
894
879
 
895
- async function installDependencies(
896
- plan: ProviderCreatePlan,
897
- jsonMode: boolean,
898
- ): Promise<void> {
880
+ async function installDependencies(plan: ProviderCreatePlan, jsonMode: boolean): Promise<void> {
899
881
  await runCommand(plan.installCommand, plan.installCwd, jsonMode);
900
882
  }
901
883
 
902
- async function runBaselineValidation(
903
- plan: ProviderCreatePlan,
904
- jsonMode: boolean,
905
- ): Promise<void> {
884
+ async function runBaselineValidation(plan: ProviderCreatePlan, jsonMode: boolean): Promise<void> {
906
885
  for (const command of plan.validationCommands) {
907
886
  await runCommand(command, plan.providerRoot, jsonMode);
908
887
  }
909
888
  }
910
889
 
911
- async function runCommand(
912
- command: string,
913
- cwd: string,
914
- jsonMode: boolean,
915
- ): Promise<void> {
890
+ async function runCommand(command: string, cwd: string, jsonMode: boolean): Promise<void> {
916
891
  const [binary, ...args] = command.split(" ");
917
892
  if (!binary) {
918
893
  throw new Error(`Cannot run empty command in ${cwd}`);
@@ -944,9 +919,7 @@ async function runCommand(
944
919
  rejectPromise(
945
920
  new Error(
946
921
  `Command failed (${command}) in ${cwd}${
947
- stdout || stderr
948
- ? `\n${[stdout, stderr].filter(Boolean).join("\n")}`
949
- : ""
922
+ stdout || stderr ? `\n${[stdout, stderr].filter(Boolean).join("\n")}` : ""
950
923
  }`,
951
924
  ),
952
925
  );
@@ -954,11 +927,7 @@ async function runCommand(
954
927
  });
955
928
  }
956
929
 
957
- function printResult(
958
- plan: ProviderCreatePlan,
959
- jsonMode: boolean,
960
- dryRun: boolean,
961
- ) {
930
+ function printResult(plan: ProviderCreatePlan, jsonMode: boolean, dryRun: boolean) {
962
931
  const payload = {
963
932
  success: true,
964
933
  dryRun,
@@ -975,9 +944,10 @@ function printResult(
975
944
  },
976
945
  validationCommands: plan.validationCommands,
977
946
  nextDevCommand: plan.nextDevCommand,
978
- files: plan.files.map(
979
- (file) => relative(plan.providerRoot, file.path) || file.path,
980
- ),
947
+ files: plan.files.map((file) => {
948
+ const relativePath = relative(plan.providerRoot, file.path) || file.path;
949
+ return file.kind === "symlink" ? `${relativePath} -> ${file.content} (symlink)` : relativePath;
950
+ }),
981
951
  };
982
952
 
983
953
  if (jsonMode) {
@@ -993,7 +963,7 @@ function printResult(
993
963
  }
994
964
  console.log(`Next local dev: ${plan.nextDevCommand}`);
995
965
  console.log(
996
- "Submission evidence: run `bun run submit-check`, save the generated report, and note `/health` plus `POST /v1/{operation}` smoke results.",
966
+ "Submission evidence: run `bun run submit-check -- --smoke` to archive measured `/health` and `POST /v1/{operation}` results.",
997
967
  );
998
968
  if (plan.files.some((file) => file.content.includes('runtime: "browser"'))) {
999
969
  console.log(