@gobing-ai/knowledge-kit 0.0.12 → 0.0.14

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 (152) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/index.js +120 -26
  3. package/package.json +1 -1
  4. package/plugins/generations/content-gen/dist/index.js +22187 -0
  5. package/plugins/generations/content-gen/plugin.json +1 -1
  6. package/plugins/generations/core-facts-gen/dist/index.js +22068 -0
  7. package/plugins/generations/core-facts-gen/plugin.json +1 -1
  8. package/plugins/generations/daily-article-gen/dist/index.js +22050 -0
  9. package/plugins/generations/daily-article-gen/plugin.json +1 -1
  10. package/plugins/generations/daily-article-gen/src/index.ts +13 -1
  11. package/plugins/generations/dailynews-gen/dist/index.js +22344 -0
  12. package/plugins/generations/dailynews-gen/plugin.json +1 -1
  13. package/plugins/generations/episode-plan-gen/dist/index.js +22503 -0
  14. package/plugins/generations/episode-plan-gen/plugin.json +1 -1
  15. package/plugins/generations/episode-plan-gen/src/index.ts +11 -0
  16. package/plugins/generations/image-gen/config.example.yaml +75 -0
  17. package/plugins/generations/image-gen/dist/index.js +24862 -0
  18. package/plugins/generations/image-gen/package.json +17 -0
  19. package/plugins/generations/image-gen/plugin.json +7 -0
  20. package/plugins/generations/image-gen/presets/formats/cover.yaml +57 -0
  21. package/plugins/generations/image-gen/presets/formats/free.yaml +46 -0
  22. package/plugins/generations/image-gen/presets/formats/illustration.yaml +48 -0
  23. package/plugins/generations/image-gen/presets/styles/clean-webapp-ui.yaml +28 -0
  24. package/plugins/generations/image-gen/presets/styles/cute.yaml +3 -0
  25. package/plugins/generations/image-gen/presets/styles/editorial.yaml +3 -0
  26. package/plugins/generations/image-gen/presets/styles/fresh.yaml +3 -0
  27. package/plugins/generations/image-gen/presets/styles/minimalist.yaml +3 -0
  28. package/plugins/generations/image-gen/presets/styles/photorealistic.yaml +3 -0
  29. package/plugins/generations/image-gen/presets/styles/sketch.yaml +3 -0
  30. package/plugins/generations/image-gen/presets/styles/technical-diagram.yaml +3 -0
  31. package/plugins/generations/image-gen/presets/styles/vibrant.yaml +3 -0
  32. package/plugins/generations/image-gen/presets/styles/warm.yaml +3 -0
  33. package/plugins/generations/image-gen/src/bytes.ts +19 -0
  34. package/plugins/generations/image-gen/src/index.ts +319 -0
  35. package/plugins/generations/image-gen/src/job.ts +143 -0
  36. package/plugins/generations/image-gen/src/paths.ts +31 -0
  37. package/plugins/generations/image-gen/src/presets.ts +344 -0
  38. package/plugins/generations/image-gen/src/providers/agnes.ts +110 -0
  39. package/plugins/generations/image-gen/src/providers/azure.ts +153 -0
  40. package/plugins/generations/image-gen/src/providers/codex-cli.ts +170 -0
  41. package/plugins/generations/image-gen/src/providers/dashscope.ts +485 -0
  42. package/plugins/generations/image-gen/src/providers/google.ts +268 -0
  43. package/plugins/generations/image-gen/src/providers/huggingface.ts +59 -0
  44. package/plugins/generations/image-gen/src/providers/jimeng.ts +259 -0
  45. package/plugins/generations/image-gen/src/providers/minimax.ts +171 -0
  46. package/plugins/generations/image-gen/src/providers/openai.ts +319 -0
  47. package/plugins/generations/image-gen/src/providers/openrouter.ts +257 -0
  48. package/plugins/generations/image-gen/src/providers/refs.ts +24 -0
  49. package/plugins/generations/image-gen/src/providers/replicate.ts +279 -0
  50. package/plugins/generations/image-gen/src/providers/seedream.ts +128 -0
  51. package/plugins/generations/image-gen/src/providers/types.ts +286 -0
  52. package/plugins/generations/image-gen/src/providers/zai.ts +237 -0
  53. package/plugins/generations/image-gen/tsconfig.json +8 -0
  54. package/plugins/generations/news-report-gen/dist/index.js +22193 -0
  55. package/plugins/generations/news-report-gen/package.json +17 -0
  56. package/plugins/generations/news-report-gen/plugin.json +7 -0
  57. package/plugins/generations/news-report-gen/src/index.ts +308 -0
  58. package/plugins/generations/news-report-gen/tsconfig.json +4 -0
  59. package/plugins/generations/omni-voice-gen/Makefile +14 -0
  60. package/plugins/generations/omni-voice-gen/README.md +112 -0
  61. package/plugins/generations/omni-voice-gen/bin/omni-voice-gen +2 -0
  62. package/plugins/generations/omni-voice-gen/dist/omni-voice-gen-prr8skpb. +2 -0
  63. package/plugins/generations/omni-voice-gen/dist/omni-voice-gen.js +6 -0
  64. package/plugins/generations/omni-voice-gen/plugin.json +6 -0
  65. package/plugins/generations/omni-voice-gen/profiles.json +12 -0
  66. package/plugins/generations/omni-voice-gen/pyproject.toml +25 -0
  67. package/plugins/generations/omni-voice-gen/scripts/coverage_gate.py +74 -0
  68. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/__init__.py +1 -0
  69. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/__main__.py +39 -0
  70. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/audio.py +190 -0
  71. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/backend.py +150 -0
  72. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/contract.py +76 -0
  73. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/mp3.py +60 -0
  74. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/pipeline.py +289 -0
  75. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/profiles.py +100 -0
  76. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/qc.py +234 -0
  77. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/voicescript.py +352 -0
  78. package/plugins/generations/omni-voice-gen/uv.lock +3510 -0
  79. package/plugins/generations/voice-gen/dist/index.js +23055 -0
  80. package/plugins/generations/voice-gen/plugin.json +1 -1
  81. package/plugins/generations/voice-gen/src/index.ts +16 -1
  82. package/plugins/generations/voice-gen/src/voicebox-client.ts +3 -1
  83. package/plugins/ingestions/aihot-ingest/dist/index.js +22378 -0
  84. package/plugins/ingestions/aihot-ingest/plugin.json +1 -1
  85. package/plugins/ingestions/horizon-ingest/dist/index.js +22125 -0
  86. package/plugins/ingestions/horizon-ingest/plugin.json +1 -1
  87. package/plugins/ingestions/karakeep-local/dist/index.js +24204 -0
  88. package/plugins/ingestions/karakeep-local/plugin.json +1 -1
  89. package/plugins/ingestions/last30days-ingest/dist/index.js +22070 -0
  90. package/plugins/ingestions/last30days-ingest/plugin.json +1 -1
  91. package/plugins/ingestions/web-search/dist/index.js +24399 -0
  92. package/plugins/ingestions/web-search/plugin.json +1 -1
  93. package/plugins/kk/commands/image-extract.md +40 -0
  94. package/plugins/kk/commands/image-generate.md +32 -0
  95. package/plugins/kk/config.example.yaml +80 -0
  96. package/plugins/kk/plugin.json +1 -1
  97. package/plugins/kk/skills/image-authoring/SKILL.md +257 -0
  98. package/plugins/kk/skills/image-authoring/references/format-drafting.md +57 -0
  99. package/plugins/kk/skills/image-authoring/references/illustration-positions.md +87 -0
  100. package/plugins/kk/skills/image-authoring/references/migrating-from-wt.md +31 -0
  101. package/plugins/kk/skills/image-authoring/references/providers.md +52 -0
  102. package/plugins/kk/skills/image-authoring/references/style-extraction.md +139 -0
  103. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +130 -30
  104. package/plugins/publishings/emdash-pub/dist/index.js +22263 -0
  105. package/plugins/publishings/emdash-pub/plugin.json +1 -1
  106. package/plugins/publishings/podcast-pub/dist/index.js +22650 -0
  107. package/plugins/publishings/podcast-pub/plugin.json +8 -2
  108. package/plugins/publishings/podcast-pub/src/index.ts +18 -2
  109. package/plugins/publishings/podcast-pub/src/show-notes.ts +56 -9
  110. package/plugins/publishings/qiita-pub/dist/index.js +22101 -0
  111. package/plugins/publishings/qiita-pub/plugin.json +1 -1
  112. package/plugins/publishings/surfdash-pub/dist/index.js +22323 -0
  113. package/plugins/publishings/surfdash-pub/plugin.json +1 -1
  114. package/plugins/publishings/surfdash-pub/src/index.ts +109 -9
  115. package/plugins/publishings/zenn-pub/dist/index.js +22142 -0
  116. package/plugins/publishings/zenn-pub/plugin.json +1 -1
  117. package/plugins/sp/scripts/batch-preflight.mjs +346 -0
  118. package/plugins/sp/scripts/batch-preflight.ts +459 -0
  119. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +615 -0
  120. package/plugins/sp/scripts/daily-summary/daily-summary.ts +846 -0
  121. package/plugins/sp/scripts/daily-summary/logger.ts +28 -0
  122. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +223 -0
  123. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +367 -0
  124. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +132 -0
  125. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +169 -0
  126. package/plugins/sp/scripts/feature-dev-precheck.mjs +171 -0
  127. package/plugins/sp/scripts/feature-dev-precheck.ts +238 -0
  128. package/plugins/sp/scripts/feature-sync-bounded.mjs +285 -0
  129. package/plugins/sp/scripts/feature-sync-bounded.ts +478 -0
  130. package/plugins/sp/scripts/history-anatomy-cache.mjs +902 -0
  131. package/plugins/sp/scripts/history-anatomy-cache.ts +1028 -0
  132. package/plugins/sp/scripts/idea-handoff.mjs +22 -0
  133. package/plugins/sp/scripts/idea-handoff.ts +44 -0
  134. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +185 -0
  135. package/plugins/sp/scripts/inline-run-setup.ts +198 -0
  136. package/plugins/sp/scripts/pr-reviewing.mjs +769 -0
  137. package/plugins/sp/scripts/pr-reviewing.ts +925 -0
  138. package/plugins/sp/scripts/quality-gate.mjs +179 -0
  139. package/plugins/sp/scripts/quality-gate.ts +217 -0
  140. package/plugins/sp/scripts/script-contract-check.ts +319 -0
  141. package/plugins/sp/scripts/stage-registry-adapter.ts +1533 -0
  142. package/plugins/sp/scripts/surface-drift-inventory.ts +929 -0
  143. package/plugins/sp/scripts/task-evidence-precheck.ts +181 -0
  144. package/plugins/sp/scripts/task-size-precheck.ts +175 -0
  145. package/plugins/sp/scripts/transition-shim-check.ts +238 -0
  146. package/plugins/sp/scripts/validate-commands.ts +689 -0
  147. package/plugins/sp/scripts/validate-flag-contracts.ts +878 -0
  148. package/plugins/sp/scripts/verify-answer-lint.ts +530 -0
  149. package/plugins/sp/scripts/workflow-step-profile.mjs +316 -0
  150. package/plugins/sp/scripts/workflow-step-profile.ts +456 -0
  151. package/plugins/sp/scripts/wrapup-steps.mjs +373 -0
  152. package/plugins/sp/scripts/wrapup-steps.ts +466 -0
@@ -0,0 +1,153 @@
1
+ import { extname } from 'node:path';
2
+ import { readBytes } from '../bytes';
3
+ import { extractImageFromResponse, getOpenAISize, type OpenAIImageResponse } from './openai';
4
+ import type { ImageProvider, ImageRequest, ImageResult, ProviderFactory, ProviderOptions } from './types';
5
+
6
+ /**
7
+ * Azure OpenAI adapter (0115 R1) — port of
8
+ * vendors/baoyu-skills/skills/baoyu-image-gen/scripts/providers/azure.ts
9
+ * (MIT © 2026 Jim Liu). `CliArgs.size` becomes `ImageRequest.width/height`.
10
+ */
11
+
12
+ const DEFAULT_AZURE_API_VERSION = '2025-04-01-preview';
13
+ const AZURE_EDIT_IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg']);
14
+
15
+ interface AzureEndpoint {
16
+ resourceBaseURL: string;
17
+ deployment: string | null;
18
+ }
19
+
20
+ export function parseAzureBaseURL(url: string): AzureEndpoint {
21
+ const parsed = new URL(url);
22
+ const trimmedPath = parsed.pathname.replace(/\/+$/, '');
23
+ const deploymentMatch = trimmedPath.match(/^(.*?)(?:\/openai)?\/deployments\/([^/]+)$/);
24
+ if (deploymentMatch !== null) {
25
+ parsed.pathname = `${deploymentMatch[1] ?? ''}/openai`;
26
+ return {
27
+ resourceBaseURL: parsed.toString().replace(/\/+$/, ''),
28
+ deployment: decodeURIComponent(deploymentMatch[2] as string),
29
+ };
30
+ }
31
+ parsed.pathname = trimmedPath.endsWith('/openai') ? trimmedPath : `${trimmedPath}/openai`;
32
+ return { resourceBaseURL: parsed.toString().replace(/\/+$/, ''), deployment: null };
33
+ }
34
+
35
+ function azureEndpoint(env: Record<string, string | undefined>): AzureEndpoint {
36
+ const url = env.AZURE_OPENAI_BASE_URL ?? '';
37
+ if (url === '') {
38
+ throw new Error(
39
+ 'AZURE_OPENAI_BASE_URL is required. Set it to your Azure resource or deployment endpoint, e.g.: https://your-resource.openai.azure.com or https://your-resource.openai.azure.com/openai/deployments/your-deployment',
40
+ );
41
+ }
42
+ return parseAzureBaseURL(url);
43
+ }
44
+
45
+ function getApiKey(env: Record<string, string | undefined>): string {
46
+ const key = env.AZURE_OPENAI_API_KEY ?? '';
47
+ if (key === '') {
48
+ throw new Error(
49
+ 'AZURE_OPENAI_API_KEY is required. Get it from Azure Portal → your OpenAI resource → Keys and Endpoint.',
50
+ );
51
+ }
52
+ return key;
53
+ }
54
+
55
+ function buildURL(
56
+ endpoint: AzureEndpoint,
57
+ deployment: string,
58
+ pathSuffix: string,
59
+ env: Record<string, string | undefined>,
60
+ ): string {
61
+ const apiVersion = env.AZURE_API_VERSION ?? DEFAULT_AZURE_API_VERSION;
62
+ return `${endpoint.resourceBaseURL}/deployments/${encodeURIComponent(deployment)}${pathSuffix}?api-version=${apiVersion}`;
63
+ }
64
+
65
+ function getAzureQuality(quality: 'normal' | '2k' | undefined): 'medium' | 'high' {
66
+ return quality === '2k' ? 'high' : 'medium';
67
+ }
68
+
69
+ /** Azure reference images must be PNG or JPG/JPEG before the API call is spent. */
70
+ export function validateReferenceImages(referenceImages: readonly string[]): void {
71
+ for (const refPath of referenceImages) {
72
+ if (!AZURE_EDIT_IMAGE_EXTENSIONS.has(extname(refPath).toLowerCase())) {
73
+ throw new Error(`Azure OpenAI reference images must be PNG or JPG/JPEG. Unsupported file: ${refPath}`);
74
+ }
75
+ }
76
+ }
77
+
78
+ export const createAzure: ProviderFactory = (options?: ProviderOptions): ImageProvider => {
79
+ const fetchFn = options?.fetch ?? fetch;
80
+ const env = options?.env ?? process.env;
81
+ // Azure's own env-precedence for a default model: explicit deployment, then the base URL,
82
+ // then the frozen 'gpt-image-2' (matches the baoyu getDefaultModel chain; spec facts unchanged).
83
+ function defaultModel(): string {
84
+ const explicit = env.AZURE_OPENAI_DEPLOYMENT?.trim();
85
+ if (explicit !== undefined && explicit !== '') return explicit;
86
+ const baseURL = env.AZURE_OPENAI_BASE_URL;
87
+ if (baseURL !== undefined) {
88
+ try {
89
+ const { deployment } = parseAzureBaseURL(baseURL);
90
+ if (deployment !== null) return deployment;
91
+ } catch {
92
+ // A malformed base URL surfaces later through the required-env check.
93
+ }
94
+ }
95
+ return 'gpt-image-2';
96
+ }
97
+
98
+ const supportsRef = (): boolean => true;
99
+
100
+ return {
101
+ name: 'azure',
102
+ defaultModel,
103
+ isAvailable: () => (env.AZURE_OPENAI_API_KEY ?? '') !== '',
104
+ supportsReferenceImages: supportsRef,
105
+ validate(req: ImageRequest): void {
106
+ validateReferenceImages(req.referenceImages ?? []);
107
+ },
108
+ async generate(req: ImageRequest): Promise<ImageResult> {
109
+ const apiKey = getApiKey(env);
110
+ const endpoint = azureEndpoint(env);
111
+ const deployment = req.model.trim();
112
+ if (deployment === '') {
113
+ throw new Error(
114
+ 'Azure deployment name is required. Use --model <deployment>, AZURE_OPENAI_DEPLOYMENT, AZURE_OPENAI_IMAGE_MODEL, or embed the deployment in AZURE_OPENAI_BASE_URL.',
115
+ );
116
+ }
117
+ const size =
118
+ (req.width !== undefined && req.height !== undefined ? `${req.width}x${req.height}` : undefined) ??
119
+ getOpenAISize(req.model, req.aspectRatio ?? null, req.quality);
120
+ const refs = req.referenceImages ?? [];
121
+ const bytes = await (async (): Promise<Uint8Array> => {
122
+ if (refs.length > 0) {
123
+ const form = new FormData();
124
+ form.append('prompt', req.prompt);
125
+ form.append('size', size);
126
+ form.append('n', '1');
127
+ form.append('quality', getAzureQuality(req.quality));
128
+ for (const refPath of refs) {
129
+ const fileBytes = await readBytes(refPath);
130
+ const filename = refPath.split('/').pop() ?? refPath;
131
+ const mimeType = extname(filename).toLowerCase() === '.png' ? 'image/png' : 'image/jpeg';
132
+ form.append('image[]', new Blob([fileBytes], { type: mimeType }), filename);
133
+ }
134
+ const res = await fetchFn(buildURL(endpoint, deployment, '/images/edits', env), {
135
+ method: 'POST',
136
+ headers: { 'api-key': apiKey },
137
+ body: form,
138
+ });
139
+ if (!res.ok) throw new Error(`Azure OpenAI edits API error: ${await res.text()}`);
140
+ return extractImageFromResponse((await res.json()) as OpenAIImageResponse, fetchFn);
141
+ }
142
+ const res = await fetchFn(buildURL(endpoint, deployment, '/images/generations', env), {
143
+ method: 'POST',
144
+ headers: { 'content-type': 'application/json', 'api-key': apiKey },
145
+ body: JSON.stringify({ prompt: req.prompt, size, n: 1, quality: getAzureQuality(req.quality) }),
146
+ });
147
+ if (!res.ok) throw new Error(`Azure OpenAI API error: ${await res.text()}`);
148
+ return extractImageFromResponse((await res.json()) as OpenAIImageResponse, fetchFn);
149
+ })();
150
+ return { bytes, extension: 'png', provider: 'azure', model: req.model };
151
+ },
152
+ };
153
+ };
@@ -0,0 +1,170 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { randomBytes } from 'node:crypto';
3
+ import { access, mkdir, rm, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { resolve } from 'node:path';
6
+ import { readBytes } from '../bytes';
7
+ import type { ImageProvider, ImageRequest, ImageResult, ProviderFactory, ProviderOptions } from './types';
8
+
9
+ /**
10
+ * no-direct-fs-io / no-direct-process-spawn exemption is recorded as per-file
11
+ * exclusions in .spur/rules/strict/runtime-boundaries.yaml: this provider's
12
+ * transport IS spawning the local `codex` CLI and staging temp files for it
13
+ * (same vendor-CLI-boundary class as the file-cli publish transports).
14
+
15
+ /**
16
+ * codex-cli adapter (0115 R1) — port of
17
+ * vendors/baoyu-skills/skills/baoyu-image-gen/scripts/providers/codex-cli.ts
18
+ * (MIT © 2026 Jim Liu). Never auto-selected (frozen NEVER_AUTO rule): readiness is
19
+ * the 'codex' CLI login, not an env fact, so isAvailable() stays false.
20
+ */
21
+
22
+ const DEFAULT_MODEL = 'codex-image-gen';
23
+
24
+ type WrapperOkResult = {
25
+ status: 'ok';
26
+ path?: string;
27
+ };
28
+
29
+ type WrapperErrorResult = {
30
+ status: 'error';
31
+ error: string;
32
+ error_kind: string;
33
+ };
34
+
35
+ type WrapperResult = WrapperOkResult | WrapperErrorResult;
36
+
37
+ async function exists(filePath: string): Promise<boolean> {
38
+ try {
39
+ await access(filePath);
40
+ return true;
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Wrapper resolution keeps the baoyu env-override channel verbatim, including the
48
+ * inherited variable names (0115 straight port; kk has no bundled codex wrapper,
49
+ * so the override is the only supported location).
50
+ */
51
+ export async function resolveWrapperPath(readEnv: (key: string) => string): Promise<string> {
52
+ const override = readEnv('BAOYU_CODEX_IMAGEGEN_BIN');
53
+ if (override.length > 0) {
54
+ if (!(await exists(override))) {
55
+ throw new Error(`Invalid BAOYU_CODEX_IMAGEGEN_BIN: ${override} does not exist.`);
56
+ }
57
+ return override;
58
+ }
59
+ throw new Error(
60
+ 'codex-cli wrapper not found. Set BAOYU_CODEX_IMAGEGEN_BIN to a codex-imagegen main.ts (or .sh) path.',
61
+ );
62
+ }
63
+
64
+ type SpawnResult = { stdout: string; stderr: string; code: number };
65
+
66
+ function spawnWrapper(wrapperPath: string, cliArgs: string[]): Promise<SpawnResult> {
67
+ return new Promise((resolveSpawn, rejectSpawn) => {
68
+ const isTs = wrapperPath.endsWith('.ts');
69
+ const command = isTs ? 'bun' : wrapperPath;
70
+ const args = isTs ? [wrapperPath, ...cliArgs] : cliArgs;
71
+ const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'] });
72
+ let stdout = '';
73
+ let stderr = '';
74
+ child.stdout.on('data', (chunk: Buffer) => {
75
+ stdout += chunk.toString('utf8');
76
+ });
77
+ child.stderr.on('data', (chunk: Buffer) => {
78
+ const text = chunk.toString('utf8');
79
+ stderr += text;
80
+ });
81
+ child.on('error', (err) => rejectSpawn(err));
82
+ child.on('close', (code) => resolveSpawn({ stdout, stderr, code: code ?? 1 }));
83
+ });
84
+ }
85
+
86
+ function parseWrapperJson(stdout: string): WrapperResult {
87
+ const trimmed = stdout.trim();
88
+ if (trimmed === '') throw new Error('Invalid codex-cli response: empty stdout from wrapper.');
89
+ const lastLine = trimmed.split(/\r?\n/).pop() ?? trimmed;
90
+ try {
91
+ return JSON.parse(lastLine) as WrapperResult;
92
+ } catch (parseErr: unknown) {
93
+ throw new Error(
94
+ `Invalid codex-cli response: could not parse JSON from wrapper stdout (${parseErr instanceof Error ? parseErr.message : String(parseErr)}).`,
95
+ );
96
+ }
97
+ }
98
+
99
+ function parsePositiveInt(value: string): number | null {
100
+ if (value === '') return null;
101
+ const parsed = Number.parseInt(value, 10);
102
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
103
+ }
104
+
105
+ export const createCodexCli: ProviderFactory = (options?: ProviderOptions): ImageProvider => {
106
+ const env = options?.env ?? process.env;
107
+ const readEnv = (key: string): string => env[key] ?? '';
108
+
109
+ return {
110
+ name: 'codex-cli',
111
+ defaultModel: () => DEFAULT_MODEL,
112
+ isAvailable: () => false,
113
+ supportsReferenceImages: () => true,
114
+ validate(req: ImageRequest): void {
115
+ if ((req.n ?? 1) > 1) {
116
+ throw new Error(
117
+ 'codex-cli provider supports only n=1 (Codex image_gen returns a single image per call).',
118
+ );
119
+ }
120
+ },
121
+ async generate(req: ImageRequest): Promise<ImageResult> {
122
+ const wrapperPath = await resolveWrapperPath(readEnv);
123
+
124
+ const sessionDir = resolve(tmpdir(), 'kk-image-gen-codex-cli');
125
+ await mkdir(sessionDir, { recursive: true });
126
+ const token = randomBytes(8).toString('hex');
127
+ const tmpOutput = resolve(sessionDir, `out-${token}.png`);
128
+ const tmpPrompt = resolve(sessionDir, `prompt-${token}.md`);
129
+ await writeFile(tmpPrompt, req.prompt, 'utf8');
130
+
131
+ const cliArgs: string[] = [
132
+ '--image',
133
+ tmpOutput,
134
+ '--prompt-file',
135
+ tmpPrompt,
136
+ '--aspect',
137
+ req.aspectRatio ?? '1:1',
138
+ ];
139
+ for (const ref of req.referenceImages ?? []) {
140
+ cliArgs.push('--ref', resolve(ref));
141
+ }
142
+
143
+ const cacheDir = readEnv('BAOYU_CODEX_IMAGEGEN_CACHE_DIR');
144
+ if (cacheDir !== '') cliArgs.push('--cache-dir', cacheDir);
145
+ const timeoutMs = parsePositiveInt(readEnv('BAOYU_CODEX_IMAGEGEN_TIMEOUT_MS'));
146
+ if (timeoutMs !== null) cliArgs.push('--timeout', String(timeoutMs));
147
+ const retries = parsePositiveInt(readEnv('BAOYU_CODEX_IMAGEGEN_RETRIES'));
148
+ if (retries !== null) cliArgs.push('--retries', String(retries));
149
+ const logFile = readEnv('BAOYU_CODEX_IMAGEGEN_LOG_FILE');
150
+ if (logFile !== '') cliArgs.push('--log-file', logFile);
151
+
152
+ try {
153
+ const spawnResult = await spawnWrapper(wrapperPath, cliArgs);
154
+ const parsed = parseWrapperJson(spawnResult.stdout);
155
+ if (parsed.status === 'error') {
156
+ throw new Error(`Invalid codex-cli result (${parsed.error_kind}): ${parsed.error}`);
157
+ }
158
+ if (spawnResult.code !== 0) {
159
+ throw new Error(
160
+ `Invalid codex-cli result: wrapper exited with code ${spawnResult.code} despite reporting status=ok.`,
161
+ );
162
+ }
163
+ const bytes = await readBytes(parsed.path ?? tmpOutput);
164
+ return { bytes, extension: 'png', provider: 'codex-cli', model: req.model };
165
+ } finally {
166
+ await Promise.allSettled([rm(tmpOutput, { force: true }), rm(tmpPrompt, { force: true })]);
167
+ }
168
+ },
169
+ };
170
+ };