@apifuse/provider-sdk 2.2.0-beta.1 → 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 (203) hide show
  1. package/AUTHORING.md +106 -0
  2. package/CHANGELOG.md +37 -0
  3. package/README.md +5 -1
  4. package/SUBMISSION.md +1 -1
  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 +14 -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 +510 -45
  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/index.d.ts +3 -3
  20. package/dist/auth.d.ts +2 -2
  21. package/dist/auth.js +9 -18
  22. package/dist/ceremonies/index.d.ts +1 -1
  23. package/dist/ceremonies/index.js +8 -18
  24. package/dist/cli/commands.d.ts +1 -1
  25. package/dist/cli/commands.js +8 -0
  26. package/dist/cli/create.d.ts +3 -0
  27. package/dist/cli/create.js +34 -35
  28. package/dist/cli/prompt-assets.d.ts +80 -0
  29. package/dist/cli/prompt-assets.js +743 -0
  30. package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
  31. package/dist/config/loader.d.ts +149 -8
  32. package/dist/config/loader.js +378 -89
  33. package/dist/contract-serialization.d.ts +2 -2
  34. package/dist/contract-serialization.js +3 -6
  35. package/dist/contract-types.d.ts +2 -2
  36. package/dist/contract.d.ts +3 -3
  37. package/dist/contract.js +4 -6
  38. package/dist/define.d.ts +8 -1
  39. package/dist/define.js +119 -122
  40. package/dist/dev.d.ts +1 -1
  41. package/dist/dev.js +1 -1
  42. package/dist/errors.d.ts +4 -1
  43. package/dist/errors.js +48 -0
  44. package/dist/i18n/catalog.d.ts +2 -2
  45. package/dist/i18n/catalog.js +4 -10
  46. package/dist/i18n/index.d.ts +2 -2
  47. package/dist/i18n/index.js +2 -2
  48. package/dist/i18n/keys.d.ts +2 -2
  49. package/dist/index.d.ts +44 -42
  50. package/dist/index.js +39 -37
  51. package/dist/lint.js +8 -15
  52. package/dist/provider.d.ts +11 -11
  53. package/dist/provider.js +9 -9
  54. package/dist/public-schema-field-lint.d.ts +1 -1
  55. package/dist/recipes/gov-api.js +1 -1
  56. package/dist/runtime/auth-flow.d.ts +1 -1
  57. package/dist/runtime/auth-flow.js +3 -3
  58. package/dist/runtime/browser.d.ts +1 -1
  59. package/dist/runtime/browser.js +15 -29
  60. package/dist/runtime/cache.d.ts +1 -1
  61. package/dist/runtime/cache.js +4 -8
  62. package/dist/runtime/choice.d.ts +1 -1
  63. package/dist/runtime/choice.js +31 -35
  64. package/dist/runtime/credential.d.ts +1 -1
  65. package/dist/runtime/credential.js +1 -1
  66. package/dist/runtime/env.d.ts +1 -1
  67. package/dist/runtime/executor.d.ts +1 -1
  68. package/dist/runtime/executor.js +15 -3
  69. package/dist/runtime/http.d.ts +2 -2
  70. package/dist/runtime/http.js +144 -348
  71. package/dist/runtime/insights.d.ts +1 -1
  72. package/dist/runtime/insights.js +6 -13
  73. package/dist/runtime/instrumentation.d.ts +2 -2
  74. package/dist/runtime/instrumentation.js +56 -19
  75. package/dist/runtime/keyring.js +1 -1
  76. package/dist/runtime/namespace.js +1 -1
  77. package/dist/runtime/otlp.d.ts +1 -1
  78. package/dist/runtime/perf.d.ts +1 -1
  79. package/dist/runtime/provider.d.ts +1 -1
  80. package/dist/runtime/provider.js +1 -2
  81. package/dist/runtime/proxy-errors.d.ts +1 -1
  82. package/dist/runtime/proxy-errors.js +9 -7
  83. package/dist/runtime/proxy-nodemaven.d.ts +35 -0
  84. package/dist/runtime/proxy-nodemaven.js +128 -0
  85. package/dist/runtime/proxy-retry-policy.d.ts +40 -0
  86. package/dist/runtime/proxy-retry-policy.js +326 -0
  87. package/dist/runtime/proxy-telemetry.d.ts +2 -1
  88. package/dist/runtime/proxy-telemetry.js +55 -52
  89. package/dist/runtime/redis.d.ts +1 -1
  90. package/dist/runtime/redis.js +2 -4
  91. package/dist/runtime/request-options.d.ts +1 -1
  92. package/dist/runtime/secrets.d.ts +27 -0
  93. package/dist/runtime/secrets.js +51 -0
  94. package/dist/runtime/state.d.ts +2 -2
  95. package/dist/runtime/state.js +15 -4
  96. package/dist/runtime/stealth.d.ts +2 -2
  97. package/dist/runtime/stealth.js +108 -212
  98. package/dist/runtime/stt.d.ts +1 -1
  99. package/dist/runtime/stt.js +11 -15
  100. package/dist/runtime/trace.d.ts +2 -2
  101. package/dist/runtime/trace.js +2 -4
  102. package/dist/runtime/waterfall.d.ts +1 -1
  103. package/dist/schema.d.ts +1 -1
  104. package/dist/schema.js +7 -15
  105. package/dist/serve.d.ts +1 -1
  106. package/dist/serve.js +1 -1
  107. package/dist/server/index.d.ts +7 -7
  108. package/dist/server/index.js +6 -6
  109. package/dist/server/self-test-redaction.d.ts +1 -1
  110. package/dist/server/self-test-redaction.js +1 -1
  111. package/dist/server/self-test.d.ts +104 -3
  112. package/dist/server/self-test.js +673 -115
  113. package/dist/server/serve.d.ts +8 -3
  114. package/dist/server/serve.js +105 -59
  115. package/dist/server/types.d.ts +10 -9
  116. package/dist/server/types.js +3 -7
  117. package/dist/stealth/profiles.d.ts +1 -1
  118. package/dist/stealth/profiles.js +5 -14
  119. package/dist/stream.d.ts +1 -1
  120. package/dist/testing/index.d.ts +2 -2
  121. package/dist/testing/index.js +2 -2
  122. package/dist/testing/run.d.ts +1 -1
  123. package/dist/testing/run.js +7 -14
  124. package/dist/types.d.ts +87 -1
  125. package/dist/user-input.d.ts +30 -0
  126. package/dist/user-input.js +66 -0
  127. package/package.json +9 -5
  128. package/src/auth-turn/index.ts +1 -1
  129. package/src/auth.ts +28 -86
  130. package/src/ceremonies/index.ts +24 -78
  131. package/src/cli/commands.ts +10 -0
  132. package/src/cli/create.ts +42 -35
  133. package/src/cli/prompt-assets.ts +865 -0
  134. package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
  135. package/src/config/loader.ts +543 -208
  136. package/src/contract-serialization.ts +5 -11
  137. package/src/contract-types.ts +2 -2
  138. package/src/contract.ts +12 -28
  139. package/src/define.ts +247 -505
  140. package/src/dev.ts +4 -9
  141. package/src/errors.ts +58 -5
  142. package/src/i18n/catalog.ts +10 -32
  143. package/src/i18n/index.ts +2 -2
  144. package/src/i18n/keys.ts +5 -11
  145. package/src/index.ts +49 -42
  146. package/src/lint.ts +89 -159
  147. package/src/provider.ts +15 -11
  148. package/src/public-schema-field-lint.ts +7 -33
  149. package/src/recipes/gov-api.ts +2 -5
  150. package/src/runtime/auth-flow.ts +5 -7
  151. package/src/runtime/browser.ts +64 -187
  152. package/src/runtime/cache.ts +20 -67
  153. package/src/runtime/choice.ts +79 -132
  154. package/src/runtime/credential.ts +2 -2
  155. package/src/runtime/env.ts +1 -1
  156. package/src/runtime/executor.ts +23 -20
  157. package/src/runtime/http.ts +195 -559
  158. package/src/runtime/insights.ts +15 -53
  159. package/src/runtime/instrumentation.ts +78 -65
  160. package/src/runtime/keyring.ts +7 -19
  161. package/src/runtime/namespace.ts +2 -7
  162. package/src/runtime/otlp.ts +12 -23
  163. package/src/runtime/perf.ts +1 -1
  164. package/src/runtime/provider.ts +4 -9
  165. package/src/runtime/proxy-errors.ts +29 -42
  166. package/src/runtime/proxy-nodemaven.ts +178 -0
  167. package/src/runtime/proxy-retry-policy.ts +469 -0
  168. package/src/runtime/proxy-telemetry.ts +79 -77
  169. package/src/runtime/redis.ts +4 -12
  170. package/src/runtime/request-options.ts +4 -13
  171. package/src/runtime/secrets.ts +64 -0
  172. package/src/runtime/state.ts +41 -110
  173. package/src/runtime/stealth.ts +162 -377
  174. package/src/runtime/stt.ts +38 -94
  175. package/src/runtime/trace.ts +14 -44
  176. package/src/runtime/waterfall.ts +5 -18
  177. package/src/schema.ts +23 -84
  178. package/src/serve.ts +1 -1
  179. package/src/server/index.ts +12 -7
  180. package/src/server/self-test-redaction.ts +2 -2
  181. package/src/server/self-test.ts +857 -132
  182. package/src/server/serve.ts +172 -245
  183. package/src/server/types.ts +7 -19
  184. package/src/stealth/profiles.ts +10 -26
  185. package/src/stream.ts +8 -19
  186. package/src/testing/index.ts +2 -2
  187. package/src/testing/run.ts +19 -67
  188. package/src/types.ts +88 -1
  189. package/src/user-input.ts +118 -0
  190. package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
  191. package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
  192. /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  193. /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  194. /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  195. /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  196. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  197. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
  198. /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  199. /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  200. /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  201. /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  202. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  203. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
@@ -2,19 +2,14 @@ import { createHash, randomBytes, randomUUID } from "node:crypto";
2
2
 
3
3
  import Ajv2020 from "ajv/dist/2020.js";
4
4
 
5
- import { AUTH_TURN_SCHEMA, type KnownAuthTurnKind } from "../auth-turn";
5
+ import { AUTH_TURN_SCHEMA, type KnownAuthTurnKind } from "../auth-turn/index.js";
6
6
  import {
7
7
  FlowExpiredError,
8
8
  ProviderSecretError,
9
9
  TurnValidationError,
10
10
  ValidationError,
11
- } from "../errors";
12
- import type {
13
- AuthFlowDefinition,
14
- AuthFlowInputHandler,
15
- AuthTurn,
16
- FlowContext,
17
- } from "../types";
11
+ } from "../errors.js";
12
+ import type { AuthFlowDefinition, AuthFlowInputHandler, AuthTurn, FlowContext } from "../types.js";
18
13
 
19
14
  type TurnKind = KnownAuthTurnKind;
20
15
 
@@ -45,10 +40,7 @@ function ensureRecord(value: unknown): JsonObject {
45
40
  return isRecord(value) ? value : {};
46
41
  }
47
42
 
48
- function createTurn(
49
- kind: TurnKind,
50
- options: Omit<AuthTurn, "kind" | "turnId"> = {},
51
- ): AuthTurn {
43
+ function createTurn(kind: TurnKind, options: Omit<AuthTurn, "kind" | "turnId"> = {}): AuthTurn {
52
44
  return {
53
45
  kind,
54
46
  turnId: randomUUID(),
@@ -69,11 +61,7 @@ function getRequiredEnv(ctx: FlowContext, key: string): string {
69
61
  }
70
62
 
71
63
  function toBase64Url(input: Buffer): string {
72
- return input
73
- .toString("base64")
74
- .replace(/\+/g, "-")
75
- .replace(/\//g, "_")
76
- .replace(/=+$/g, "");
64
+ return input.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
77
65
  }
78
66
 
79
67
  function createCodeVerifier(): string {
@@ -130,10 +118,7 @@ async function runCeremonyHandler(
130
118
  }
131
119
  }
132
120
 
133
- function getString(
134
- input: Record<string, unknown>,
135
- key: string,
136
- ): string | undefined {
121
+ function getString(input: Record<string, unknown>, key: string): string | undefined {
137
122
  const value = input[key];
138
123
  return typeof value === "string" ? value : undefined;
139
124
  }
@@ -142,10 +127,7 @@ function getNestedRecord(ctx: FlowContext, key: string): JsonObject {
142
127
  return ensureRecord(ctx.context.get(key));
143
128
  }
144
129
 
145
- function buildJsonSchemaForm(
146
- expectedInput: JsonObject,
147
- hint: string,
148
- ): AuthTurn {
130
+ function buildJsonSchemaForm(expectedInput: JsonObject, hint: string): AuthTurn {
149
131
  return createTurn("form", {
150
132
  hint,
151
133
  expectedInput: withDeclaredFormFieldOrder(expectedInput),
@@ -160,10 +142,7 @@ function withDeclaredFormFieldOrder(expectedInput: JsonObject): JsonObject {
160
142
  }
161
143
 
162
144
  const existingOrder = expectedInput[FORM_FIELD_ORDER_EXTENSION];
163
- if (
164
- Array.isArray(existingOrder) &&
165
- existingOrder.every((value) => typeof value === "string")
166
- ) {
145
+ if (Array.isArray(existingOrder) && existingOrder.every((value) => typeof value === "string")) {
167
146
  return expectedInput;
168
147
  }
169
148
 
@@ -176,9 +155,7 @@ function withDeclaredFormFieldOrder(expectedInput: JsonObject): JsonObject {
176
155
  export function validateCeremonyOutput(turn: unknown): AuthTurn {
177
156
  if (!validateAuthTurn(turn)) {
178
157
  const detail = validateAuthTurn.errors
179
- ?.map(
180
- (error) => `${error.instancePath || "$"} ${error.message ?? "invalid"}`,
181
- )
158
+ ?.map((error) => `${error.instancePath || "$"} ${error.message ?? "invalid"}`)
182
159
  .join("; ");
183
160
  throw new TurnValidationError(detail || "Invalid AuthTurn output");
184
161
  }
@@ -214,10 +191,7 @@ export function createOAuth2Ceremony(options: {
214
191
  if (options.usePKCE) {
215
192
  const verifier = createCodeVerifier();
216
193
  ctx.context.set(OAUTH2_PKCE_VERIFIER_KEY, verifier);
217
- authorizeUrl.searchParams.set(
218
- "code_challenge",
219
- createCodeChallenge(verifier),
220
- );
194
+ authorizeUrl.searchParams.set("code_challenge", createCodeChallenge(verifier));
221
195
  authorizeUrl.searchParams.set("code_challenge_method", "S256");
222
196
  }
223
197
 
@@ -270,10 +244,7 @@ export function createOAuth2Ceremony(options: {
270
244
  ctx,
271
245
  input,
272
246
  ),
273
- abort: async () =>
274
- validateCeremonyOutput(
275
- createTurn("abort", { hint: "OAuth flow aborted." }),
276
- ),
247
+ abort: async () => validateCeremonyOutput(createTurn("abort", { hint: "OAuth flow aborted." })),
277
248
  };
278
249
  }
279
250
 
@@ -329,26 +300,19 @@ export function createDeviceFlowCeremony(options: {
329
300
  client_id: getRequiredEnv(ctx, options.clientIdEnvKey),
330
301
  ...(options.clientSecretEnvKey
331
302
  ? {
332
- client_secret: getRequiredEnv(
333
- ctx,
334
- options.clientSecretEnvKey,
335
- ),
303
+ client_secret: getRequiredEnv(ctx, options.clientSecretEnvKey),
336
304
  }
337
305
  : {}),
338
306
  });
339
307
  const data = ensureRecord(response.data);
340
308
  const errorCode = getString(data, "error");
341
309
 
342
- if (
343
- errorCode === "authorization_pending" ||
344
- errorCode === "slow_down"
345
- ) {
310
+ if (errorCode === "authorization_pending" || errorCode === "slow_down") {
346
311
  return createTurn("poll", {
347
312
  data,
348
313
  hint: "Authorization pending.",
349
314
  timing: {
350
- suggestedPollIntervalMs:
351
- errorCode === "slow_down" ? 10_000 : 5_000,
315
+ suggestedPollIntervalMs: errorCode === "slow_down" ? 10_000 : 5_000,
352
316
  maxWaitMs: 120_000,
353
317
  },
354
318
  });
@@ -367,9 +331,7 @@ export function createDeviceFlowCeremony(options: {
367
331
  ctx,
368
332
  ),
369
333
  abort: async () =>
370
- validateCeremonyOutput(
371
- createTurn("abort", { hint: "Device flow aborted." }),
372
- ),
334
+ validateCeremonyOutput(createTurn("abort", { hint: "Device flow aborted." })),
373
335
  };
374
336
  }
375
337
 
@@ -437,9 +399,7 @@ export function createWebAuthnCeremony(options: {
437
399
  input,
438
400
  ),
439
401
  abort: async () =>
440
- validateCeremonyOutput(
441
- createTurn("abort", { hint: "WebAuthn ceremony aborted." }),
442
- ),
402
+ validateCeremonyOutput(createTurn("abort", { hint: "WebAuthn ceremony aborted." })),
443
403
  };
444
404
  }
445
405
 
@@ -526,9 +486,7 @@ export function createMagicLinkCeremony(options: {
526
486
  ctx,
527
487
  ),
528
488
  abort: async () =>
529
- validateCeremonyOutput(
530
- createTurn("abort", { hint: "Magic link flow aborted." }),
531
- ),
489
+ validateCeremonyOutput(createTurn("abort", { hint: "Magic link flow aborted." })),
532
490
  };
533
491
  }
534
492
 
@@ -548,7 +506,7 @@ export function createFormCeremony(options: {
548
506
  continue: (ctx, input = {}) =>
549
507
  runCeremonyHandler(
550
508
  async () => {
551
- const { prevalidate } = await import("../runtime/prevalidate");
509
+ const { prevalidate } = await import("../runtime/prevalidate.js");
552
510
  const result = prevalidate(options.schema, input);
553
511
  if (!result.valid) {
554
512
  throw new ValidationError("Form input failed validation.", {
@@ -558,9 +516,7 @@ export function createFormCeremony(options: {
558
516
 
559
517
  return createTurn("complete", {
560
518
  data: {
561
- credential: options.mapCredential
562
- ? options.mapCredential(input)
563
- : input,
519
+ credential: options.mapCredential ? options.mapCredential(input) : input,
564
520
  },
565
521
  hint: "Form completed.",
566
522
  });
@@ -570,15 +526,11 @@ export function createFormCeremony(options: {
570
526
  input,
571
527
  ),
572
528
  abort: async () =>
573
- validateCeremonyOutput(
574
- createTurn("abort", { hint: "Form ceremony aborted." }),
575
- ),
529
+ validateCeremonyOutput(createTurn("abort", { hint: "Form ceremony aborted." })),
576
530
  };
577
531
  }
578
532
 
579
- export function combineCeremonies(
580
- ...ceremonies: AuthFlowDefinition[]
581
- ): AuthFlowDefinition {
533
+ export function combineCeremonies(...ceremonies: AuthFlowDefinition[]): AuthFlowDefinition {
582
534
  function getStage(ctx: FlowContext): number {
583
535
  const rawStage = ctx.context.get(COMBINED_STAGE_KEY);
584
536
  return typeof rawStage === "number" ? rawStage : 0;
@@ -630,9 +582,7 @@ export function combineCeremonies(
630
582
  async () => {
631
583
  const current = ceremonies[getStage(ctx)];
632
584
  if (!current?.poll) {
633
- throw new ValidationError(
634
- "Current ceremony does not support polling.",
635
- );
585
+ throw new ValidationError("Current ceremony does not support polling.");
636
586
  }
637
587
  return await current.poll(ctx);
638
588
  },
@@ -680,9 +630,7 @@ export function createSwitchCeremony(options: {
680
630
  async () => {
681
631
  const storedChoice = ctx.context.get(SWITCH_SELECTION_KEY);
682
632
  const choice =
683
- typeof storedChoice === "string"
684
- ? storedChoice
685
- : getString(input, "choice");
633
+ typeof storedChoice === "string" ? storedChoice : getString(input, "choice");
686
634
 
687
635
  if (!choice || !options.choices[choice]) {
688
636
  throw new ValidationError("A valid choice is required.");
@@ -710,9 +658,7 @@ export function createSwitchCeremony(options: {
710
658
 
711
659
  const ceremony = options.choices[choice];
712
660
  if (!ceremony?.poll) {
713
- throw new ValidationError(
714
- "Selected ceremony does not support polling.",
715
- );
661
+ throw new ValidationError("Selected ceremony does not support polling.");
716
662
  }
717
663
 
718
664
  return await ceremony.poll(ctx);
@@ -2,6 +2,7 @@ export type ApifuseCommandName =
2
2
  | "create"
3
3
  | "dev"
4
4
  | "check"
5
+ | "sync-assets"
5
6
  | "submit-check"
6
7
  | "bounty-check"
7
8
  | "record"
@@ -46,6 +47,14 @@ export const COMMAND_MANIFEST: Record<
46
47
  examples: ["apifuse check .", "apifuse check providers/korea-air-quality"],
47
48
  modulePath: "./apifuse-check",
48
49
  },
50
+ "sync-assets": {
51
+ name: "sync-assets",
52
+ summary:
53
+ "Regenerate SDK-managed agent prompt assets (AGENTS.md, .agents/skills, symlinks, manifest) for the installed SDK version.",
54
+ usage: "apifuse sync-assets [path] [--check]",
55
+ examples: ["apifuse sync-assets .", "apifuse sync-assets . --check"],
56
+ modulePath: "./apifuse-sync-assets",
57
+ },
49
58
  "submit-check": {
50
59
  name: "submit-check",
51
60
  summary:
@@ -103,6 +112,7 @@ export const COMMAND_ORDER: ApifuseCommandName[] = [
103
112
  "create",
104
113
  "dev",
105
114
  "check",
115
+ "sync-assets",
106
116
  "submit-check",
107
117
  "record",
108
118
  "test",
package/src/cli/create.ts CHANGED
@@ -1,6 +1,6 @@
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
 
@@ -8,6 +8,11 @@ import { cancel, intro, isCancel, note, outro, select, text } from "@clack/promp
8
8
  import { z } from "zod";
9
9
 
10
10
  import packageJson from "../../package.json";
11
+ import {
12
+ buildPromptAssetManifest,
13
+ buildPromptAssetPlanEntries,
14
+ PROMPT_ASSET_MANIFEST_PATH,
15
+ } from "./prompt-assets.js";
11
16
 
12
17
  export const PROVIDER_NAME_REGEX = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
13
18
  export const CATEGORY_OPTIONS = [
@@ -64,7 +69,10 @@ export type CreateResolvedOptions = {
64
69
 
65
70
  export type ProviderPlanFile = {
66
71
  path: string;
72
+ /** File content, or the symlink target (relative path) for kind "symlink". */
67
73
  content: string;
74
+ /** Absent kind means "file" (backward compatible with older consumers). */
75
+ kind?: "file" | "symlink";
68
76
  };
69
77
 
70
78
  export type ProviderCreatePlan = {
@@ -410,11 +418,16 @@ export async function buildProviderCreatePlan(
410
418
  );
411
419
  }
412
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.
413
426
  const sdkSpecifier =
414
427
  options.sdkSpecifier ??
415
428
  (options.preset === "monorepo" && resolvedWorkspaceRoot
416
429
  ? "workspace:*"
417
- : `^${packageJson.version}`);
430
+ : packageJson.version);
418
431
  const relativeProviderRoot = relative(cwd, providerRoot) || options.name;
419
432
  const nextDevCommand = `cd ${relativeProviderRoot} && bun run dev`;
420
433
  const packageName =
@@ -538,40 +551,23 @@ export async function buildProviderCreatePlan(
538
551
  PROVIDER_ID: options.name,
539
552
  }),
540
553
  },
541
- {
542
- path: resolve(providerRoot, "AGENTS.md"),
543
- content: await renderTemplate("AGENTS.md.tpl", {}),
544
- },
545
- {
546
- path: resolve(providerRoot, "CLAUDE.md"),
547
- content: await renderTemplate("CLAUDE.md.tpl", {}),
548
- },
549
- {
550
- path: resolve(providerRoot, "skills", "normalization-standards", "SKILL.md"),
551
- content: await renderTemplate("skills/normalization-standards/SKILL.md.tpl", {}),
552
- },
553
- {
554
- path: resolve(providerRoot, "skills", "upstream-contract-verification", "SKILL.md"),
555
- content: await renderTemplate("skills/upstream-contract-verification/SKILL.md.tpl", {}),
556
- },
557
- {
558
- path: resolve(providerRoot, "skills", "fixtures-and-recording", "SKILL.md"),
559
- content: await renderTemplate("skills/fixtures-and-recording/SKILL.md.tpl", {}),
560
- },
561
- {
562
- path: resolve(providerRoot, "skills", "pagination-and-counts", "SKILL.md"),
563
- content: await renderTemplate("skills/pagination-and-counts/SKILL.md.tpl", {}),
564
- },
565
- {
566
- path: resolve(providerRoot, "skills", "health-checks-and-fail-closed", "SKILL.md"),
567
- content: await renderTemplate("skills/health-checks-and-fail-closed/SKILL.md.tpl", {}),
568
- },
569
- {
570
- path: resolve(providerRoot, "skills", "upstream-notes", "README.md"),
571
- content: await renderTemplate("skills/upstream-notes/README.md.tpl", {}),
572
- },
573
554
  ];
574
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
+
575
571
  return {
576
572
  displayName: options.displayName,
577
573
  files,
@@ -614,6 +610,7 @@ function renderPackageJson(input: { packageName: string; sdkSpecifier: string })
614
610
  check: "apifuse check . && bun run type-check",
615
611
  "type-check": "tsc --noEmit",
616
612
  "submit-check": "apifuse submit-check . --markdown submission-report.md",
613
+ "sync-assets": "apifuse sync-assets .",
617
614
  test: "apifuse test .",
618
615
  record: "apifuse record .",
619
616
  start: "bun start.ts",
@@ -869,6 +866,13 @@ function isApifuseInternalWorkspaceRoot(workspaceRoot: string): boolean {
869
866
  async function writePlan(plan: ProviderCreatePlan): Promise<void> {
870
867
  for (const file of plan.files) {
871
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
+ }
872
876
  await writeFile(file.path, file.content);
873
877
  }
874
878
  }
@@ -940,7 +944,10 @@ function printResult(plan: ProviderCreatePlan, jsonMode: boolean, dryRun: boolea
940
944
  },
941
945
  validationCommands: plan.validationCommands,
942
946
  nextDevCommand: plan.nextDevCommand,
943
- files: plan.files.map((file) => relative(plan.providerRoot, file.path) || file.path),
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
+ }),
944
951
  };
945
952
 
946
953
  if (jsonMode) {