@agent-native/core 0.94.3 → 0.95.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 (50) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/docs/content/pr-visual-recap.mdx +20 -12
  4. package/corpus/core/package.json +1 -1
  5. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +3 -0
  6. package/corpus/core/src/agent/engine/registry.ts +6 -1
  7. package/corpus/core/src/agent/engine/types.ts +2 -0
  8. package/corpus/core/src/cli/code-agent-executor.ts +61 -1
  9. package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
  10. package/corpus/core/src/cli/recap.ts +262 -25
  11. package/corpus/core/src/client/settings/UsageSection.tsx +26 -5
  12. package/corpus/core/src/index.ts +2 -0
  13. package/corpus/core/src/usage/store.ts +82 -20
  14. package/corpus/templates/plan/actions/record-recap-usage-cost.ts +49 -0
  15. package/corpus/templates/plan/actions/record-recap-usage.ts +15 -22
  16. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  17. package/dist/agent/engine/ai-sdk-engine.js +3 -0
  18. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  19. package/dist/agent/engine/registry.d.ts +1 -1
  20. package/dist/agent/engine/registry.d.ts.map +1 -1
  21. package/dist/agent/engine/registry.js +2 -0
  22. package/dist/agent/engine/registry.js.map +1 -1
  23. package/dist/agent/engine/types.d.ts +2 -0
  24. package/dist/agent/engine/types.d.ts.map +1 -1
  25. package/dist/agent/engine/types.js.map +1 -1
  26. package/dist/cli/code-agent-executor.d.ts +2 -0
  27. package/dist/cli/code-agent-executor.d.ts.map +1 -1
  28. package/dist/cli/code-agent-executor.js +52 -3
  29. package/dist/cli/code-agent-executor.js.map +1 -1
  30. package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
  31. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  32. package/dist/cli/pr-visual-recap-workflow.js +1 -1
  33. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  34. package/dist/cli/recap.d.ts +22 -2
  35. package/dist/cli/recap.d.ts.map +1 -1
  36. package/dist/cli/recap.js +199 -22
  37. package/dist/cli/recap.js.map +1 -1
  38. package/dist/client/settings/UsageSection.d.ts.map +1 -1
  39. package/dist/client/settings/UsageSection.js +12 -3
  40. package/dist/client/settings/UsageSection.js.map +1 -1
  41. package/dist/collab/struct-routes.d.ts +1 -1
  42. package/dist/index.d.ts +1 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js.map +1 -1
  45. package/dist/usage/store.d.ts +21 -0
  46. package/dist/usage/store.d.ts.map +1 -1
  47. package/dist/usage/store.js +54 -19
  48. package/dist/usage/store.js.map +1 -1
  49. package/docs/content/pr-visual-recap.mdx +20 -12
  50. package/package.json +1 -1
package/dist/cli/recap.js CHANGED
@@ -37,6 +37,7 @@ import { execFileSync } from "node:child_process";
37
37
  import { createHash } from "node:crypto";
38
38
  import fs from "node:fs";
39
39
  import path from "node:path";
40
+ import { normalizeOpenAiBaseUrl } from "../agent/engine/openai-compatible-endpoint.js";
40
41
  import { DEFAULT_PLAN_APP_URL, fetchPlanBlockCatalog, planActionEndpoint, } from "./plan-blocks.js";
41
42
  import { readPlanPublishAuth } from "./plan-publish-store.js";
42
43
  import { PR_VISUAL_RECAP_WORKFLOW_YML } from "./pr-visual-recap-workflow.js";
@@ -82,7 +83,9 @@ export const PR_VISUAL_RECAP_SETUP = [
82
83
  " ANTHROPIC_API_KEY — the LLM key for the default Claude Code backend",
83
84
  "Optional (only if you change defaults):",
84
85
  " OPENAI_API_KEY (secret) + VISUAL_RECAP_AGENT=codex (variable) — use Codex instead of Claude",
85
- " VISUAL_RECAP_MODEL / VISUAL_RECAP_REASONING (variables) pin the model (e.g. gpt-5.6-sol) and reasoning depth (none|minimal|low|medium|high|xhigh; Codex only)",
86
+ " VISUAL_RECAP_API_KEY (secret) + VISUAL_RECAP_AGENT=openai-compatible + VISUAL_RECAP_BASE_URL (variable) use DeepSeek, Kimi, or any OpenAI-compatible API",
87
+ " VISUAL_RECAP_MODEL (variable, required for openai-compatible) — provider model id; optional override for Claude/Codex",
88
+ " VISUAL_RECAP_REASONING (variable) — reasoning depth (none|minimal|low|medium|high|xhigh; Codex only)",
86
89
  " VISUAL_RECAP_SKILL_SOURCE=repo (variable) — pin CI to the repo-local visual-recap skill instead of latest bundled guidance",
87
90
  " VISUAL_RECAP_SECRET_SCAN=off|high-confidence|strict (variable) — default high-confidence; strict restores generic TOKEN/SECRET assignment suppression",
88
91
  " PLAN_RECAP_APP_URL (secret) — only when self-hosting the plan app (defaults to https://plan.agent-native.com)",
@@ -154,10 +157,12 @@ export function buildReusableCallerWorkflow(options = {}) {
154
157
  ` PLAN_RECAP_TOKEN: \${{ secrets.PLAN_RECAP_TOKEN }}\n` +
155
158
  ` ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}\n` +
156
159
  ` OPENAI_API_KEY: \${{ secrets.OPENAI_API_KEY }}\n` +
160
+ ` VISUAL_RECAP_API_KEY: \${{ secrets.VISUAL_RECAP_API_KEY }}\n` +
157
161
  ` PLAN_RECAP_APP_URL: \${{ secrets.PLAN_RECAP_APP_URL }}\n` +
158
162
  ` with:\n` +
159
163
  ` agent: ${agentValue}\n` +
160
164
  ` model: ${modelValue}\n` +
165
+ ` base-url: \${{ vars.VISUAL_RECAP_BASE_URL || '' }}\n` +
161
166
  ` reasoning: \${{ vars.VISUAL_RECAP_REASONING || '' }}\n` +
162
167
  ` skill-source: \${{ vars.VISUAL_RECAP_SKILL_SOURCE || 'auto' }}\n` +
163
168
  ` secret-scan: \${{ vars.VISUAL_RECAP_SECRET_SCAN || 'high-confidence' }}\n` +
@@ -202,12 +207,19 @@ export function normalizeRecapAgent(value) {
202
207
  return "codex";
203
208
  if (agent === "claude")
204
209
  return "claude";
205
- throw new Error(`Unsupported recap agent "${value}" (expected "claude" or "codex").`);
210
+ if (["openai-compatible", "deepseek", "kimi", "moonshot", "custom"].includes(agent)) {
211
+ return "openai-compatible";
212
+ }
213
+ throw new Error(`Unsupported recap agent "${value}" (expected "claude", "codex", or "openai-compatible").`);
206
214
  }
207
215
  export function recapRequiredSecrets(agent) {
208
216
  return [
209
217
  "PLAN_RECAP_TOKEN",
210
- agent === "codex" ? "OPENAI_API_KEY" : "ANTHROPIC_API_KEY",
218
+ agent === "codex"
219
+ ? "OPENAI_API_KEY"
220
+ : agent === "openai-compatible"
221
+ ? "VISUAL_RECAP_API_KEY"
222
+ : "ANTHROPIC_API_KEY",
211
223
  ];
212
224
  }
213
225
  function recapWorkflowFile(baseDir) {
@@ -338,13 +350,60 @@ function setGithubVariable(name, value, repo, dryRun) {
338
350
  args.push("--repo", repo);
339
351
  return gh(args).ok ? "set" : "failed";
340
352
  }
353
+ const OPENAI_COMPATIBLE_VARIABLE_REQUIREMENTS = [
354
+ {
355
+ name: "VISUAL_RECAP_BASE_URL",
356
+ example: "https://provider.example/v1",
357
+ },
358
+ { name: "VISUAL_RECAP_MODEL", example: "provider-model-id" },
359
+ ];
360
+ const RECAP_MODEL_PATTERN = /^[a-zA-Z0-9._-]{1,80}$/;
361
+ const OPENAI_COMPATIBLE_RECAP_MODEL_PATTERN = /^[^\p{C}\p{Z}]{1,200}$/u;
362
+ export function validateOpenAiCompatibleRecapVariables(input) {
363
+ const [baseUrlRequirement, modelRequirement] = OPENAI_COMPATIBLE_VARIABLE_REQUIREMENTS;
364
+ const problems = [];
365
+ const baseUrl = input.baseUrl?.trim() || "";
366
+ const rawModel = input.model || "";
367
+ const model = rawModel.trim();
368
+ try {
369
+ const parsed = normalizeOpenAiBaseUrl(baseUrl);
370
+ if (!parsed)
371
+ throw new Error("empty");
372
+ }
373
+ catch {
374
+ problems.push({
375
+ requirement: baseUrlRequirement,
376
+ reason: "VISUAL_RECAP_BASE_URL must be a valid http(s) URL without credentials",
377
+ });
378
+ }
379
+ if (!model) {
380
+ problems.push({
381
+ requirement: modelRequirement,
382
+ reason: "VISUAL_RECAP_MODEL is required (openai-compatible backend)",
383
+ });
384
+ }
385
+ else if (!OPENAI_COMPATIBLE_RECAP_MODEL_PATTERN.test(rawModel)) {
386
+ problems.push({
387
+ requirement: modelRequirement,
388
+ reason: "invalid VISUAL_RECAP_MODEL value (must be 1-200 characters without whitespace or controls)",
389
+ });
390
+ }
391
+ return problems;
392
+ }
341
393
  export function buildRecapSetupPlan(input) {
342
394
  const env = input.env ?? process.env;
343
395
  const appUrl = stripTrailingSlash(input.appUrl || env.PLAN_RECAP_APP_URL || DEFAULT_RECAP_APP_URL);
344
396
  const agent = normalizeRecapAgent(input.agent || env.VISUAL_RECAP_AGENT);
345
397
  const requiredSecrets = recapRequiredSecrets(agent);
398
+ const requiredVariables = agent === "openai-compatible"
399
+ ? OPENAI_COMPATIBLE_VARIABLE_REQUIREMENTS
400
+ : [];
346
401
  const planToken = envValue(env, "PLAN_RECAP_TOKEN") ?? planTokenFromLocalStore(appUrl);
347
- const llmSecretName = agent === "codex" ? "OPENAI_API_KEY" : "ANTHROPIC_API_KEY";
402
+ const llmSecretName = agent === "codex"
403
+ ? "OPENAI_API_KEY"
404
+ : agent === "openai-compatible"
405
+ ? "VISUAL_RECAP_API_KEY"
406
+ : "ANTHROPIC_API_KEY";
348
407
  const variableValues = {};
349
408
  if (agent !== "claude")
350
409
  variableValues.VISUAL_RECAP_AGENT = agent;
@@ -357,6 +416,17 @@ export function buildRecapSetupPlan(input) {
357
416
  if (value)
358
417
  variableValues[key] = value;
359
418
  }
419
+ if (agent === "openai-compatible") {
420
+ const baseUrl = envValue(env, "VISUAL_RECAP_BASE_URL");
421
+ if (baseUrl)
422
+ variableValues.VISUAL_RECAP_BASE_URL = baseUrl;
423
+ }
424
+ const variableProblems = agent === "openai-compatible"
425
+ ? validateOpenAiCompatibleRecapVariables({
426
+ baseUrl: variableValues.VISUAL_RECAP_BASE_URL,
427
+ model: variableValues.VISUAL_RECAP_MODEL,
428
+ })
429
+ : [];
360
430
  return {
361
431
  agent,
362
432
  appUrl,
@@ -364,6 +434,8 @@ export function buildRecapSetupPlan(input) {
364
434
  workflowPath: path.relative(input.baseDir, recapWorkflowFile(input.baseDir)),
365
435
  workflowExists: fs.existsSync(recapWorkflowFile(input.baseDir)),
366
436
  requiredSecrets,
437
+ requiredVariables,
438
+ variableProblems,
367
439
  variableValues,
368
440
  secretValues: {
369
441
  PLAN_RECAP_TOKEN: planToken,
@@ -469,7 +541,10 @@ function runSetup(args) {
469
541
  lines.push(` Set manually: ${commandForMissingSecret(name, repo)}`);
470
542
  }
471
543
  }
544
+ const invalidVariableNames = new Set(plan.variableProblems.map((problem) => problem.requirement.name));
472
545
  for (const [name, value] of Object.entries(plan.variableValues)) {
546
+ if (invalidVariableNames.has(name))
547
+ continue;
473
548
  const status = setGithubVariable(name, value, repo, dryRun);
474
549
  if (status === "set") {
475
550
  lines.push(` ${name}: set to ${value}.`);
@@ -482,6 +557,13 @@ function runSetup(args) {
482
557
  lines.push(` Set manually: ${commandForMissingVariable(name, value, repo)}`);
483
558
  }
484
559
  }
560
+ for (const problem of plan.variableProblems) {
561
+ const { requirement } = problem;
562
+ const hasValue = Boolean(plan.variableValues[requirement.name]);
563
+ lines.push(` ${requirement.name}: ${hasValue ? "invalid value" : "missing value"}.`);
564
+ lines.push(` ${problem.reason}.`);
565
+ lines.push(` Set manually: ${commandForMissingVariable(requirement.name, requirement.example, repo)}`);
566
+ }
485
567
  }
486
568
  lines.push("");
487
569
  lines.push(`Next: commit ${plan.workflowPath}, then run npx @agent-native/core@latest recap doctor.`);
@@ -558,6 +640,28 @@ function runDoctor(args) {
558
640
  else {
559
641
  const configuredAgent = variables.get("VISUAL_RECAP_AGENT") || "claude";
560
642
  lines.push(`[ok] Recap backend variable: ${configuredAgent}.`);
643
+ const remoteVariableProblems = plan.agent === "openai-compatible"
644
+ ? validateOpenAiCompatibleRecapVariables({
645
+ baseUrl: variables.get("VISUAL_RECAP_BASE_URL"),
646
+ model: variables.get("VISUAL_RECAP_MODEL"),
647
+ })
648
+ : [];
649
+ const problemsByName = new Map(remoteVariableProblems.map((problem) => [
650
+ problem.requirement.name,
651
+ problem,
652
+ ]));
653
+ for (const requirement of plan.requiredVariables) {
654
+ const problem = problemsByName.get(requirement.name);
655
+ if (!problem) {
656
+ lines.push(`[ok] GitHub variable configured: ${requirement.name}.`);
657
+ }
658
+ else {
659
+ ok = false;
660
+ const hasValue = Boolean(variables.get(requirement.name)?.trim());
661
+ lines.push(`[${hasValue ? "invalid" : "missing"}] ${problem.reason}.`);
662
+ lines.push(` Set it with: ${commandForMissingVariable(requirement.name, requirement.example, repo)}`);
663
+ }
664
+ }
561
665
  }
562
666
  process.stdout.write(`${lines.join("\n")}\n`);
563
667
  if (!ok)
@@ -810,6 +914,8 @@ function agentLabel(agent) {
810
914
  return "Codex";
811
915
  if (normalized === "claude")
812
916
  return "Claude";
917
+ if (normalized === "openai-compatible")
918
+ return "OpenAI-compatible";
813
919
  return agent || "Agent";
814
920
  }
815
921
  export function summarizeAgentRun(input) {
@@ -850,10 +956,18 @@ function localAgentResultCandidates(agent) {
850
956
  stderrFile: "codex-stderr.log",
851
957
  exitCodeFile: "codex-exit-code.txt",
852
958
  },
959
+ {
960
+ agent: "openai-compatible",
961
+ resultFile: "openai-compatible-result.txt",
962
+ stderrFile: "openai-compatible-stderr.log",
963
+ exitCodeFile: "openai-compatible-exit-code.txt",
964
+ },
853
965
  ];
854
966
  const normalized = agent.toLowerCase();
855
967
  if (normalized === "codex")
856
- return [all[1], all[0]];
968
+ return [all[1], all[0], all[2]];
969
+ if (normalized === "openai-compatible")
970
+ return [all[2], all[0], all[1]];
857
971
  return all;
858
972
  }
859
973
  export function summarizeLocalAgentFailure(input = {}) {
@@ -2599,22 +2713,35 @@ export function evaluateRecapGate(input) {
2599
2713
  // The chosen backend's API key must be present. Normalize the agent value once
2600
2714
  // here and validate it: an unknown or mis-cased value (e.g. "Claude", "gpt")
2601
2715
  // must NOT silently pass the gate and then match neither agent step.
2602
- const agent = (input.agentRaw || "claude").toLowerCase();
2603
- if (agent !== "claude" && agent !== "codex") {
2604
- reasons.push(`unsupported VISUAL_RECAP_AGENT "${input.agentRaw}" (expected "claude" or "codex")`);
2716
+ const rawAgent = (input.agentRaw || "claude").toLowerCase();
2717
+ const agent = ["deepseek", "kimi", "moonshot", "custom"].includes(rawAgent)
2718
+ ? "openai-compatible"
2719
+ : rawAgent;
2720
+ if (!["claude", "codex", "openai-compatible"].includes(agent)) {
2721
+ reasons.push(`unsupported VISUAL_RECAP_AGENT "${input.agentRaw}" (expected "claude", "codex", or "openai-compatible")`);
2605
2722
  }
2606
2723
  else if (agent === "codex") {
2607
2724
  if (!input.hasOpenai)
2608
2725
  reasons.push("OPENAI_API_KEY not configured (codex backend)");
2609
2726
  }
2610
- else {
2727
+ else if (agent === "claude") {
2611
2728
  if (!input.hasAnthropic)
2612
2729
  reasons.push("ANTHROPIC_API_KEY not configured (claude backend)");
2613
2730
  }
2731
+ else {
2732
+ if (!input.hasOpenaiCompatible)
2733
+ reasons.push("VISUAL_RECAP_API_KEY not configured (openai-compatible backend)");
2734
+ reasons.push(...validateOpenAiCompatibleRecapVariables({
2735
+ baseUrl: input.baseUrl,
2736
+ model: input.model,
2737
+ }).map((problem) => problem.reason));
2738
+ }
2614
2739
  // Validate VISUAL_RECAP_MODEL if set — an unchecked value could be injected by
2615
2740
  // a repo settings writer and passed straight to the agent CLI.
2616
2741
  const model = input.model || "";
2617
- if (model && !/^[a-zA-Z0-9._-]{1,80}$/.test(model)) {
2742
+ if (agent !== "openai-compatible" &&
2743
+ model &&
2744
+ !RECAP_MODEL_PATTERN.test(model)) {
2618
2745
  reasons.push("invalid VISUAL_RECAP_MODEL value (must match [a-zA-Z0-9._-]{1,80})");
2619
2746
  }
2620
2747
  const skillSource = normalizeRecapSkillSourceMode(input.skillSource);
@@ -2729,8 +2856,10 @@ async function runGate() {
2729
2856
  hasPlan: process.env.HAS_PLAN === "true",
2730
2857
  hasAnthropic: process.env.HAS_ANTHROPIC === "true",
2731
2858
  hasOpenai: process.env.HAS_OPENAI === "true",
2859
+ hasOpenaiCompatible: process.env.HAS_COMPATIBLE === "true",
2732
2860
  agentRaw: process.env.AGENT,
2733
2861
  model: process.env.VISUAL_RECAP_MODEL,
2862
+ baseUrl: process.env.VISUAL_RECAP_BASE_URL,
2734
2863
  skillSource: process.env.VISUAL_RECAP_SKILL_SOURCE,
2735
2864
  changedFiles,
2736
2865
  });
@@ -3190,6 +3319,41 @@ export function parseCodexUsage(jsonl) {
3190
3319
  model: typeof u.model === "string" ? u.model : undefined,
3191
3320
  };
3192
3321
  }
3322
+ /** Parse the usage sidecar emitted by an Agent-Native Code run. */
3323
+ export function parseOpenAiCompatibleUsage(json) {
3324
+ const obj = parseLastJsonObject(json);
3325
+ const usage = obj?.usage ?? obj;
3326
+ if (!usage || typeof usage !== "object")
3327
+ return null;
3328
+ const input = usage.inputTokens ?? usage.input_tokens ?? usage.prompt_tokens ?? undefined;
3329
+ const output = usage.outputTokens ??
3330
+ usage.output_tokens ??
3331
+ usage.completion_tokens ??
3332
+ undefined;
3333
+ if (input == null && output == null)
3334
+ return null;
3335
+ const asCount = (value) => {
3336
+ const parsed = Number(value ?? 0);
3337
+ return Number.isFinite(parsed) ? Math.max(0, parsed) : 0;
3338
+ };
3339
+ const inputDetails = usage.inputTokenDetails;
3340
+ return {
3341
+ inputTokens: asCount(input),
3342
+ outputTokens: asCount(output),
3343
+ cacheReadTokens: asCount(usage.cacheReadTokens ??
3344
+ usage.cachedInputTokens ??
3345
+ usage.cache_read_input_tokens ??
3346
+ inputDetails?.cacheReadTokens),
3347
+ cacheWriteTokens: asCount(usage.cacheWriteTokens ??
3348
+ usage.cache_write_tokens ??
3349
+ inputDetails?.cacheWriteTokens),
3350
+ model: typeof obj?.model === "string"
3351
+ ? obj.model
3352
+ : typeof usage.model === "string"
3353
+ ? usage.model
3354
+ : undefined,
3355
+ };
3356
+ }
3193
3357
  /**
3194
3358
  * `recap usage` — parse the agent's run output for token usage and POST it to
3195
3359
  * the plan app's record-recap-usage action so the recap row carries cost. The
@@ -3214,7 +3378,12 @@ async function runUsage(args) {
3214
3378
  let parsed = null;
3215
3379
  try {
3216
3380
  const raw = fs.readFileSync(path.resolve(stringArg(args, "result-file")), "utf8");
3217
- parsed = agent === "codex" ? parseCodexUsage(raw) : parseClaudeUsage(raw);
3381
+ parsed =
3382
+ agent === "codex"
3383
+ ? parseCodexUsage(raw)
3384
+ : agent === "openai-compatible"
3385
+ ? parseOpenAiCompatibleUsage(raw)
3386
+ : parseClaudeUsage(raw);
3218
3387
  }
3219
3388
  catch (err) {
3220
3389
  done({ ok: false, reason: `could not read/parse usage: ${String(err)}` });
@@ -3228,10 +3397,17 @@ async function runUsage(args) {
3228
3397
  // the pinned --model (VISUAL_RECAP_MODEL) and finally the documented default.
3229
3398
  const model = parsed.model ??
3230
3399
  optionalArg(args, "model") ??
3231
- (agent === "codex" ? "gpt-5.6-sol" : "claude");
3400
+ (agent === "codex"
3401
+ ? "gpt-5.6-sol"
3402
+ : agent === "openai-compatible"
3403
+ ? "openai-compatible"
3404
+ : "claude");
3405
+ const usageAgent = agent === "claude" || agent === "codex" || agent === "openai-compatible"
3406
+ ? agent
3407
+ : undefined;
3232
3408
  const body = {
3233
3409
  planId,
3234
- ...(agent === "codex" || agent === "claude" ? { agent } : {}),
3410
+ ...(usageAgent ? { agent: usageAgent } : {}),
3235
3411
  model,
3236
3412
  inputTokens: parsed.inputTokens,
3237
3413
  outputTokens: parsed.outputTokens,
@@ -3302,16 +3478,16 @@ function runAgentSummary(args) {
3302
3478
  const HELP = `npx @agent-native/core@latest recap — PR visual recap helpers (used by the GitHub Action)
3303
3479
 
3304
3480
  Usage:
3305
- npx @agent-native/core@latest recap setup [--repo owner/name] [--agent claude|codex] [--app-url <url>] [--skip-secrets] [--dry-run] [--force]
3306
- npx @agent-native/core@latest recap doctor [--repo owner/name] [--agent claude|codex] [--app-url <url>]
3481
+ npx @agent-native/core@latest recap setup [--repo owner/name] [--agent claude|codex|openai-compatible] [--app-url <url>] [--skip-secrets] [--dry-run] [--force]
3482
+ npx @agent-native/core@latest recap doctor [--repo owner/name] [--agent claude|codex|openai-compatible] [--app-url <url>]
3307
3483
  npx @agent-native/core@latest recap collect-diff --base <baseSha> --head <headSha> [--out recap.diff] [--stat recap.stat]
3308
3484
  npx @agent-native/core@latest recap block-reference [--app-url <url>] [--out recap-blocks.md]
3309
3485
  npx @agent-native/core@latest recap scan --diff <path> [--mode off|high-confidence|strict]
3310
3486
  npx @agent-native/core@latest recap build-prompt --pr <n> [--repo owner/name] [--head <sha>] [--app-url <url>] [--diff <path>] [--stat <path>] [--block-reference recap-blocks.md] [--prev-plan-id <id>] [--huge] [--local-files] [--local-dir <folder>] [--skill-source auto|latest|repo] [--out <path>]
3311
3487
  npx @agent-native/core@latest recap publish [--source recap-source.json] [--out recap-url.txt] [--repo owner/name] [--pr <n>] [--prev-plan-id <id>] [--source-pr-state open|closed|merged] [--source-pr-merged-at <iso>] [--source-author-email <email>] [--source-author-name <name>] [--source-author-login <login>] [--app-url <url>] [--token <planToken>] [--github-token <ghToken>]
3312
3488
  npx @agent-native/core@latest recap shot --url <planUrl> [--token <planToken>] [--app-url <url>] [--out recap.png] [--theme light|dark] [--image-cache-key <key>]
3313
- npx @agent-native/core@latest recap usage --plan-url <planUrl> --result-file <path> --app-url <url> --token <planToken> [--agent claude|codex] [--model <id>]
3314
- npx @agent-native/core@latest recap agent-summary --result-file <path> [--stderr-file <path>] [--exit-code-file <path>] [--agent claude|codex]
3489
+ npx @agent-native/core@latest recap usage --plan-url <planUrl> --result-file <path> --app-url <url> --token <planToken> [--agent claude|codex|openai-compatible] [--model <id>]
3490
+ npx @agent-native/core@latest recap agent-summary --result-file <path> [--stderr-file <path>] [--exit-code-file <path>] [--agent claude|codex|openai-compatible]
3315
3491
  npx @agent-native/core@latest recap comment <find-plan-id|upsert> --repo owner/name --issue <n> --token <github-token>
3316
3492
  npx @agent-native/core@latest recap check start [--repo owner/name] [--sha <headSha>] [--token <github-token>] [--workflow-url <url>]
3317
3493
  Create the in-progress "Visual Recap" GitHub check run and write its id to
@@ -3328,16 +3504,16 @@ Usage:
3328
3504
  The PR Visual Recap security gate. Decides whether to run the recap at all
3329
3505
  and which (normalized) backend agent to use. Reads the pull_request payload
3330
3506
  from $GITHUB_EVENT_PATH, the secret-presence/agent/model signals from the
3331
- environment (HAS_PLAN / HAS_ANTHROPIC / HAS_OPENAI === 'true', AGENT,
3332
- VISUAL_RECAP_MODEL), the repo from $GITHUB_REPOSITORY, and the PR's changed
3507
+ environment (HAS_PLAN / HAS_ANTHROPIC / HAS_OPENAI / HAS_COMPATIBLE === 'true', AGENT,
3508
+ VISUAL_RECAP_MODEL / VISUAL_RECAP_BASE_URL), the repo from $GITHUB_REPOSITORY, and the PR's changed
3333
3509
  files from the GitHub REST API (paged, with GH_TOKEN/GITHUB_TOKEN). Skips
3334
3510
  drafts, forks without secret access, bot authors, the missing-secret case, an
3335
3511
  invalid agent/model, and any untrusted PR that touches recap-control files
3336
3512
  (repo-pinned skill instructions, .claude/**, root CLAUDE.md, root AGENTS.md,
3337
3513
  root .mcp.json) — failing CLOSED on any file-list error. Writes
3338
- run=<true|false> and agent=<claude|codex> to $GITHUB_OUTPUT.
3514
+ run=<true|false> and agent=<claude|codex|openai-compatible> to $GITHUB_OUTPUT.
3339
3515
  npx @agent-native/core@latest recap agent-summary
3340
- Read the captured Claude/Codex result file and write a sanitized one-line
3516
+ Read the captured agent result file and write a sanitized one-line
3341
3517
  summary to stdout and $GITHUB_OUTPUT (summary). Used only when no plan URL
3342
3518
  was produced, so PR comments/checks explain the actual failure.
3343
3519
  npx @agent-native/core@latest recap scan
@@ -3358,7 +3534,8 @@ Usage:
3358
3534
  commands; secret values are sent to gh through stdin, never argv.
3359
3535
  npx @agent-native/core@latest recap doctor
3360
3536
  Check workflow presence/drift, local Plans publish-token availability, gh
3361
- repo access, and required GitHub Actions secrets for the selected backend.
3537
+ repo access, and required GitHub Actions secrets and variables for the
3538
+ selected backend, including provider-variable validity.
3362
3539
  `;
3363
3540
  export async function runRecap(argv) {
3364
3541
  const [sub, ...rest] = argv;