whitesmith 0.0.2 → 0.0.4
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.
- package/README.md +286 -88
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +90 -2
- package/dist/cli.js.map +1 -1
- package/dist/comment.d.ts.map +1 -1
- package/dist/comment.js +18 -11
- package/dist/comment.js.map +1 -1
- package/dist/git.d.ts +5 -3
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +20 -29
- package/dist/git.js.map +1 -1
- package/dist/harnesses/pi.d.ts.map +1 -1
- package/dist/harnesses/pi.js +22 -6
- package/dist/harnesses/pi.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestrator.d.ts +31 -3
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +214 -10
- package/dist/orchestrator.js.map +1 -1
- package/dist/prompts.d.ts +52 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +197 -0
- package/dist/prompts.js.map +1 -1
- package/dist/providers/github-ci.d.ts +40 -0
- package/dist/providers/github-ci.d.ts.map +1 -1
- package/dist/providers/github-ci.js +463 -213
- package/dist/providers/github-ci.js.map +1 -1
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/review.d.ts +48 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/review.js +221 -0
- package/dist/review.js.map +1 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +116 -3
- package/src/comment.ts +20 -14
- package/src/git.ts +23 -30
- package/src/harnesses/pi.ts +27 -6
- package/src/index.ts +9 -1
- package/src/orchestrator.ts +253 -14
- package/src/prompts.ts +239 -0
- package/src/providers/github-ci.ts +513 -217
- package/src/providers/index.ts +1 -1
- package/src/review.ts +290 -0
- package/src/types.ts +4 -0
|
@@ -6,7 +6,7 @@ import * as path from 'node:path';
|
|
|
6
6
|
|
|
7
7
|
export type AuthMode = 'auth-json' | 'models-json';
|
|
8
8
|
|
|
9
|
-
interface ProviderEntry {
|
|
9
|
+
export interface ProviderEntry {
|
|
10
10
|
name: string;
|
|
11
11
|
baseUrl?: string;
|
|
12
12
|
api?: string;
|
|
@@ -16,11 +16,33 @@ interface ProviderEntry {
|
|
|
16
16
|
builtin: boolean;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Serializable CI configuration.
|
|
21
|
+
* Can be saved to JSON with --export-config and loaded with --config.
|
|
22
|
+
* When --include-secrets is used, the `secrets` field maps env var names to
|
|
23
|
+
* their actual API key values. These are set via `gh secret set` on install.
|
|
24
|
+
*/
|
|
25
|
+
export interface CIConfigFile {
|
|
26
|
+
providers: ProviderEntry[];
|
|
27
|
+
defaultProvider: string;
|
|
28
|
+
defaultModel: string;
|
|
29
|
+
/** API key values keyed by env var name. Only present with --include-secrets. */
|
|
30
|
+
secrets?: Record<string, string>;
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
interface CIConfig {
|
|
20
34
|
authMode: AuthMode;
|
|
21
35
|
providers: ProviderEntry[];
|
|
22
36
|
defaultProvider: string;
|
|
23
37
|
defaultModel: string;
|
|
38
|
+
/** When true, install whitesmith from source (pnpm i + pnpm link --global) instead of npm. */
|
|
39
|
+
dev: boolean;
|
|
40
|
+
/** When true, generate the review workflow. */
|
|
41
|
+
reviewWorkflow: boolean;
|
|
42
|
+
/** When true, the review step is enabled in the main loop (whitesmith PRs are already reviewed inline). */
|
|
43
|
+
reviewStepEnabled: boolean;
|
|
44
|
+
/** whitesmith package version to pin in npm install. */
|
|
45
|
+
version: string;
|
|
24
46
|
}
|
|
25
47
|
|
|
26
48
|
/**
|
|
@@ -180,12 +202,14 @@ async function promptDefaults(
|
|
|
180
202
|
}
|
|
181
203
|
|
|
182
204
|
/**
|
|
183
|
-
*
|
|
184
|
-
*
|
|
205
|
+
* Set API key secrets on GitHub. If `knownSecrets` contains a value for an
|
|
206
|
+
* env var, it is used directly; otherwise the user is prompted interactively.
|
|
207
|
+
* Returns the list of secret names that were successfully set.
|
|
185
208
|
*/
|
|
186
|
-
async function
|
|
209
|
+
async function setOrPromptSecrets(
|
|
187
210
|
ctx: GitHubCIContext,
|
|
188
211
|
providers: ProviderEntry[],
|
|
212
|
+
knownSecrets?: Record<string, string>,
|
|
189
213
|
): Promise<string[]> {
|
|
190
214
|
const setSecrets: string[] = [];
|
|
191
215
|
const seen = new Set<string>();
|
|
@@ -194,9 +218,12 @@ async function promptAndSetSecrets(
|
|
|
194
218
|
if (seen.has(p.apiKeyEnvVar)) continue;
|
|
195
219
|
seen.add(p.apiKeyEnvVar);
|
|
196
220
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
221
|
+
let apiKey = knownSecrets?.[p.apiKeyEnvVar];
|
|
222
|
+
if (!apiKey) {
|
|
223
|
+
apiKey = await password({
|
|
224
|
+
message: `Enter API key for ${p.name} (secret: ${p.apiKeyEnvVar}):`,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
200
227
|
|
|
201
228
|
if (!apiKey) {
|
|
202
229
|
console.log(` ⚠ Skipped ${p.apiKeyEnvVar} (empty)`);
|
|
@@ -252,94 +279,164 @@ function indent(text: string, spaces: number): string {
|
|
|
252
279
|
.join('\n');
|
|
253
280
|
}
|
|
254
281
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
MODELS_EOF`;
|
|
266
|
-
}
|
|
282
|
+
/**
|
|
283
|
+
* Top-level env block shared by whitesmith.yml and whitesmith-comment.yml.
|
|
284
|
+
* Includes defaults, GH_TOKEN, and API key secrets.
|
|
285
|
+
*/
|
|
286
|
+
function generateTopLevelEnv(config: CIConfig): string {
|
|
287
|
+
const lines: string[] = [
|
|
288
|
+
` WHITESMITH_PROVIDER: ${config.defaultProvider}`,
|
|
289
|
+
` WHITESMITH_MODEL: ${config.defaultModel}`,
|
|
290
|
+
` GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}`,
|
|
291
|
+
];
|
|
267
292
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
echo "Set it to the contents of ~/.pi/agent/auth.json"
|
|
277
|
-
exit 1
|
|
278
|
-
fi
|
|
279
|
-
mkdir -p ~/.pi/agent
|
|
280
|
-
echo "$PI_AUTH_JSON" > ~/.pi/agent/auth.json
|
|
281
|
-
chmod 600 ~/.pi/agent/auth.json
|
|
293
|
+
if (config.authMode === 'models-json') {
|
|
294
|
+
const seen = new Set<string>();
|
|
295
|
+
for (const p of config.providers) {
|
|
296
|
+
if (seen.has(p.apiKeyEnvVar)) continue;
|
|
297
|
+
seen.add(p.apiKeyEnvVar);
|
|
298
|
+
lines.push(` ${p.apiKeyEnvVar}: \${{ secrets.${p.apiKeyEnvVar} }}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
282
301
|
|
|
283
|
-
|
|
284
|
-
- name: Refresh OAuth token
|
|
285
|
-
env:
|
|
286
|
-
GH_PAT: \${{ secrets.GH_PAT }}
|
|
287
|
-
run: node .github/scripts/refresh-oauth-token.mjs`;
|
|
302
|
+
return lines.join('\n');
|
|
288
303
|
}
|
|
289
304
|
|
|
290
|
-
|
|
305
|
+
/**
|
|
306
|
+
* Composite action: node setup, git config, npm cache, install, auth config.
|
|
307
|
+
* This is written to .github/actions/setup-whitesmith/action.yml so workflows
|
|
308
|
+
* can just do `uses: ./.github/actions/setup-whitesmith`.
|
|
309
|
+
*/
|
|
310
|
+
function generateSetupAction(config: CIConfig): string {
|
|
311
|
+
let authStep: string;
|
|
312
|
+
|
|
291
313
|
if (config.authMode === 'auth-json') {
|
|
292
|
-
|
|
314
|
+
authStep = `\
|
|
315
|
+
- name: Configure pi auth
|
|
316
|
+
shell: bash
|
|
317
|
+
run: |
|
|
318
|
+
if [ -z "$PI_AUTH_JSON" ]; then
|
|
319
|
+
echo "ERROR: PI_AUTH_JSON secret is not set" >&2; exit 1
|
|
320
|
+
fi
|
|
321
|
+
mkdir -p ~/.pi/agent
|
|
322
|
+
echo "$PI_AUTH_JSON" > ~/.pi/agent/auth.json
|
|
323
|
+
chmod 600 ~/.pi/agent/auth.json
|
|
324
|
+
|
|
325
|
+
# Workaround for https://github.com/badlogic/pi-mono/issues/2743
|
|
326
|
+
- name: Refresh OAuth token
|
|
327
|
+
shell: bash
|
|
328
|
+
run: node .github/scripts/refresh-oauth-token.mjs`;
|
|
329
|
+
} else {
|
|
330
|
+
const modelsJson = buildModelsJson(config.providers);
|
|
331
|
+
const modelsJsonStr = JSON.stringify(modelsJson, null, 2);
|
|
332
|
+
|
|
333
|
+
authStep = `\
|
|
334
|
+
- name: Configure pi models
|
|
335
|
+
shell: bash
|
|
336
|
+
run: |
|
|
337
|
+
mkdir -p ~/.pi/agent
|
|
338
|
+
cat > ~/.pi/agent/models.json << 'MODELS_EOF'
|
|
339
|
+
${indent(modelsJsonStr, 8)}
|
|
340
|
+
MODELS_EOF`;
|
|
293
341
|
}
|
|
294
|
-
return generateModelsJsonStep(config);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function generateRunEnvBlock(config: CIConfig): string {
|
|
298
|
-
const envs: Record<string, string> = {
|
|
299
|
-
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}',
|
|
300
|
-
};
|
|
301
342
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
343
|
+
let installSteps: string;
|
|
344
|
+
|
|
345
|
+
if (config.dev) {
|
|
346
|
+
// Dev mode: build whitesmith from source using pnpm.
|
|
347
|
+
// We add pnpm's global bin to $GITHUB_PATH so that `whitesmith` and `pi`
|
|
348
|
+
// are available in all subsequent steps (persists across composite action
|
|
349
|
+
// steps and the calling workflow).
|
|
350
|
+
// We always rebuild (even on cache hit) because source changes per commit.
|
|
351
|
+
installSteps = `\
|
|
352
|
+
- name: Setup pnpm
|
|
353
|
+
uses: pnpm/action-setup@v4
|
|
354
|
+
|
|
355
|
+
- name: Add pnpm global bin to PATH
|
|
356
|
+
shell: bash
|
|
357
|
+
run: |
|
|
358
|
+
pnpm setup
|
|
359
|
+
echo "$HOME/.local/share/pnpm" >> "$GITHUB_PATH"
|
|
360
|
+
|
|
361
|
+
- name: Install dependencies and build whitesmith
|
|
362
|
+
shell: bash
|
|
363
|
+
run: |
|
|
364
|
+
pnpm install
|
|
365
|
+
pnpm run build
|
|
366
|
+
pnpm link --global
|
|
367
|
+
|
|
368
|
+
- name: Install pi
|
|
369
|
+
shell: bash
|
|
370
|
+
run: pnpm add -g @mariozechner/pi-coding-agent`;
|
|
371
|
+
} else {
|
|
372
|
+
installSteps = `\
|
|
373
|
+
- name: Get npm global prefix
|
|
374
|
+
id: npm-prefix
|
|
375
|
+
shell: bash
|
|
376
|
+
run: echo "dir=$(npm prefix -g)" >> "$GITHUB_OUTPUT"
|
|
377
|
+
|
|
378
|
+
- name: Cache npm packages
|
|
379
|
+
id: npm-cache
|
|
380
|
+
uses: actions/cache@v4
|
|
381
|
+
with:
|
|
382
|
+
path: \${{ steps.npm-prefix.outputs.dir }}
|
|
383
|
+
key: whitesmith-\${{ runner.os }}-${config.version}
|
|
384
|
+
|
|
385
|
+
- name: Install whitesmith and pi
|
|
386
|
+
if: steps.npm-cache.outputs.cache-hit != 'true'
|
|
387
|
+
shell: bash
|
|
388
|
+
run: npm install -g whitesmith@${config.version} @mariozechner/pi-coding-agent`;
|
|
306
389
|
}
|
|
307
390
|
|
|
308
|
-
return
|
|
309
|
-
|
|
310
|
-
|
|
391
|
+
return `\
|
|
392
|
+
name: Setup whitesmith
|
|
393
|
+
description: Install Node.js, whitesmith, pi, and configure AI provider auth
|
|
394
|
+
|
|
395
|
+
runs:
|
|
396
|
+
using: composite
|
|
397
|
+
steps:
|
|
398
|
+
- name: Setup Node.js
|
|
399
|
+
uses: actions/setup-node@v4
|
|
400
|
+
with:
|
|
401
|
+
node-version: '22'
|
|
402
|
+
|
|
403
|
+
- name: Configure git
|
|
404
|
+
shell: bash
|
|
405
|
+
run: |
|
|
406
|
+
git config user.name "whitesmith[bot]"
|
|
407
|
+
git config user.email "whitesmith[bot]@users.noreply.github.com"
|
|
408
|
+
|
|
409
|
+
${installSteps}
|
|
410
|
+
|
|
411
|
+
${authStep}
|
|
412
|
+
`;
|
|
311
413
|
}
|
|
312
414
|
|
|
313
415
|
function generateMainWorkflow(config: CIConfig): string {
|
|
314
|
-
const
|
|
315
|
-
const envBlock = generateRunEnvBlock(config);
|
|
416
|
+
const envBlock = generateTopLevelEnv(config);
|
|
316
417
|
|
|
317
418
|
return `\
|
|
318
|
-
#
|
|
319
|
-
# Settings → Actions → General → "Allow GitHub Actions to create and approve pull requests"
|
|
320
|
-
# Without this, PR creation will fail with a permissions error.
|
|
419
|
+
# Requires: Settings → Actions → General → "Allow GitHub Actions to create and approve pull requests"
|
|
321
420
|
name: whitesmith
|
|
322
421
|
|
|
323
422
|
on:
|
|
324
|
-
schedule:
|
|
325
|
-
- cron: '*/15 * * * *'
|
|
326
423
|
workflow_dispatch:
|
|
327
424
|
inputs:
|
|
425
|
+
issue:
|
|
426
|
+
description: 'Issue number to target (leave empty for global scan)'
|
|
328
427
|
max_iterations:
|
|
329
428
|
description: 'Maximum iterations'
|
|
330
429
|
default: '3'
|
|
331
|
-
type: string
|
|
332
430
|
provider:
|
|
333
|
-
description: 'AI provider (
|
|
334
|
-
required: false
|
|
335
|
-
type: string
|
|
431
|
+
description: 'AI provider (overrides WHITESMITH_PROVIDER)'
|
|
336
432
|
model:
|
|
337
|
-
description: 'AI model
|
|
338
|
-
|
|
339
|
-
|
|
433
|
+
description: 'AI model (overrides WHITESMITH_MODEL)'
|
|
434
|
+
|
|
435
|
+
env:
|
|
436
|
+
${envBlock}
|
|
340
437
|
|
|
341
438
|
concurrency:
|
|
342
|
-
group: whitesmith-
|
|
439
|
+
group: \${{ inputs.issue && format('whitesmith-issue-{0}', inputs.issue) || 'whitesmith-global' }}
|
|
343
440
|
cancel-in-progress: false
|
|
344
441
|
|
|
345
442
|
permissions:
|
|
@@ -354,64 +451,36 @@ jobs:
|
|
|
354
451
|
- uses: actions/checkout@v4
|
|
355
452
|
with:
|
|
356
453
|
fetch-depth: 0
|
|
357
|
-
token: \${{ secrets.GITHUB_TOKEN }}
|
|
358
454
|
|
|
359
|
-
-
|
|
360
|
-
uses: actions/setup-node@v4
|
|
361
|
-
with:
|
|
362
|
-
node-version: '22'
|
|
455
|
+
- uses: ./.github/actions/setup-whitesmith
|
|
363
456
|
|
|
364
|
-
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
- name: Get npm global prefix
|
|
370
|
-
id: npm-prefix
|
|
371
|
-
run: echo "dir=$(npm prefix -g)" >> "$GITHUB_OUTPUT"
|
|
372
|
-
|
|
373
|
-
- name: Cache global npm packages
|
|
374
|
-
id: npm-cache
|
|
375
|
-
uses: actions/cache@v4
|
|
376
|
-
with:
|
|
377
|
-
path: \${{ steps.npm-prefix.outputs.dir }}
|
|
378
|
-
key: npm-global-\${{ runner.os }}-pi-v1
|
|
379
|
-
|
|
380
|
-
- name: Install whitesmith and pi
|
|
381
|
-
if: steps.npm-cache.outputs.cache-hit != 'true'
|
|
382
|
-
run: |
|
|
383
|
-
npm install -g whitesmith
|
|
384
|
-
npm install -g @mariozechner/pi-coding-agent
|
|
385
|
-
|
|
386
|
-
${authSteps}
|
|
387
|
-
|
|
388
|
-
- name: Run whitesmith
|
|
389
|
-
env:
|
|
390
|
-
${envBlock}
|
|
391
|
-
run: |
|
|
392
|
-
PROVIDER="\${{ inputs.provider || '${config.defaultProvider}' }}"
|
|
393
|
-
MODEL="\${{ inputs.model || '${config.defaultModel}' }}"
|
|
457
|
+
- run: |
|
|
458
|
+
ISSUE_FLAG=""
|
|
459
|
+
if [ -n "\${{ inputs.issue }}" ]; then
|
|
460
|
+
ISSUE_FLAG="--issue \${{ inputs.issue }}"
|
|
461
|
+
fi
|
|
394
462
|
whitesmith run . \\
|
|
395
|
-
|
|
396
|
-
--provider "
|
|
397
|
-
--model "
|
|
463
|
+
\$ISSUE_FLAG \\
|
|
464
|
+
--provider "\${{ inputs.provider || env.WHITESMITH_PROVIDER }}" \\
|
|
465
|
+
--model "\${{ inputs.model || env.WHITESMITH_MODEL }}" \\
|
|
398
466
|
--max-iterations \${{ inputs.max_iterations || '3' }}
|
|
399
467
|
`;
|
|
400
468
|
}
|
|
401
469
|
|
|
402
470
|
function generateCommentWorkflow(config: CIConfig): string {
|
|
403
|
-
const
|
|
404
|
-
const envBlock = generateRunEnvBlock(config);
|
|
471
|
+
const envBlock = generateTopLevelEnv(config);
|
|
405
472
|
|
|
406
473
|
return `\
|
|
407
|
-
#
|
|
408
|
-
# Settings → Actions → General → "Allow GitHub Actions to create and approve pull requests"
|
|
474
|
+
# Requires: Settings → Actions → General → "Allow GitHub Actions to create and approve pull requests"
|
|
409
475
|
name: whitesmith-comment
|
|
410
476
|
|
|
411
477
|
on:
|
|
412
478
|
issue_comment:
|
|
413
479
|
types: [created]
|
|
414
480
|
|
|
481
|
+
env:
|
|
482
|
+
${envBlock}
|
|
483
|
+
|
|
415
484
|
concurrency:
|
|
416
485
|
group: whitesmith-comment-\${{ github.event.issue.number }}
|
|
417
486
|
cancel-in-progress: false
|
|
@@ -427,120 +496,203 @@ jobs:
|
|
|
427
496
|
outputs:
|
|
428
497
|
should_run: \${{ steps.check.outputs.should_run }}
|
|
429
498
|
steps:
|
|
430
|
-
-
|
|
431
|
-
id: check
|
|
499
|
+
- id: check
|
|
432
500
|
env:
|
|
433
|
-
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
434
501
|
COMMENT_BODY: \${{ github.event.comment.body }}
|
|
435
502
|
run: |
|
|
436
|
-
# Always run if comment contains /whitesmith
|
|
437
503
|
if echo "$COMMENT_BODY" | grep -q '/whitesmith'; then
|
|
438
504
|
echo "should_run=true" >> "$GITHUB_OUTPUT"
|
|
439
|
-
echo "Triggered by /whitesmith keyword"
|
|
440
505
|
exit 0
|
|
441
506
|
fi
|
|
442
|
-
|
|
443
|
-
# For PR comments, auto-trigger if the PR is on a whitesmith branch
|
|
444
507
|
if [ -n "\${{ github.event.issue.pull_request.url }}" ]; then
|
|
445
508
|
BRANCH=$(gh pr view \${{ github.event.issue.number }} \\
|
|
446
|
-
--repo \${{ github.repository }}
|
|
447
|
-
|
|
448
|
-
echo "PR branch: $BRANCH"
|
|
449
|
-
if echo "$BRANCH" | grep -qE '^(investigate|task)/'; then
|
|
509
|
+
--repo \${{ github.repository }} --json headRefName -q .headRefName)
|
|
510
|
+
if echo "$BRANCH" | grep -qE '^(investigate|issue)/'; then
|
|
450
511
|
echo "should_run=true" >> "$GITHUB_OUTPUT"
|
|
451
|
-
echo "Triggered by comment on whitesmith PR branch"
|
|
452
512
|
exit 0
|
|
453
513
|
fi
|
|
454
514
|
fi
|
|
455
|
-
|
|
456
515
|
echo "should_run=false" >> "$GITHUB_OUTPUT"
|
|
457
|
-
echo "Skipping: not a /whitesmith command and not a whitesmith PR"
|
|
458
516
|
|
|
459
517
|
run:
|
|
460
518
|
needs: check
|
|
461
|
-
runs-on: ubuntu-latest
|
|
462
519
|
if: needs.check.outputs.should_run == 'true'
|
|
520
|
+
runs-on: ubuntu-latest
|
|
463
521
|
steps:
|
|
464
|
-
-
|
|
465
|
-
env:
|
|
466
|
-
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
467
|
-
run: |
|
|
522
|
+
- run: |
|
|
468
523
|
gh api repos/\${{ github.repository }}/issues/comments/\${{ github.event.comment.id }}/reactions \\
|
|
469
524
|
-f content=eyes
|
|
470
525
|
|
|
471
526
|
- uses: actions/checkout@v4
|
|
472
527
|
with:
|
|
473
528
|
fetch-depth: 0
|
|
474
|
-
token: \${{ secrets.GITHUB_TOKEN }}
|
|
475
|
-
|
|
476
|
-
- name: Setup Node.js
|
|
477
|
-
uses: actions/setup-node@v4
|
|
478
|
-
with:
|
|
479
|
-
node-version: '22'
|
|
480
|
-
|
|
481
|
-
- name: Configure git
|
|
482
|
-
run: |
|
|
483
|
-
git config user.name "whitesmith[bot]"
|
|
484
|
-
git config user.email "whitesmith[bot]@users.noreply.github.com"
|
|
485
529
|
|
|
486
|
-
-
|
|
487
|
-
id: npm-prefix
|
|
488
|
-
run: echo "dir=$(npm prefix -g)" >> "$GITHUB_OUTPUT"
|
|
530
|
+
- uses: ./.github/actions/setup-whitesmith
|
|
489
531
|
|
|
490
|
-
-
|
|
491
|
-
id: npm-cache
|
|
492
|
-
uses: actions/cache@v4
|
|
493
|
-
with:
|
|
494
|
-
path: \${{ steps.npm-prefix.outputs.dir }}
|
|
495
|
-
key: npm-global-\${{ runner.os }}-pi-v1
|
|
496
|
-
|
|
497
|
-
- name: Install whitesmith and pi
|
|
498
|
-
if: steps.npm-cache.outputs.cache-hit != 'true'
|
|
499
|
-
run: |
|
|
500
|
-
npm install -g whitesmith
|
|
501
|
-
npm install -g @mariozechner/pi-coding-agent
|
|
502
|
-
|
|
503
|
-
${authSteps}
|
|
504
|
-
|
|
505
|
-
- name: Save comment body to file
|
|
506
|
-
env:
|
|
532
|
+
- env:
|
|
507
533
|
COMMENT_BODY: \${{ github.event.comment.body }}
|
|
508
534
|
run: |
|
|
509
535
|
printf '%s' "$COMMENT_BODY" > .whitesmith-comment-body.txt
|
|
510
|
-
|
|
511
|
-
- name: Run whitesmith comment
|
|
512
|
-
env:
|
|
513
|
-
${envBlock}
|
|
514
|
-
run: |
|
|
515
536
|
whitesmith comment . \\
|
|
516
537
|
--number "\${{ github.event.issue.number }}" \\
|
|
517
538
|
--body-file .whitesmith-comment-body.txt \\
|
|
518
|
-
--provider "$
|
|
519
|
-
--model "$
|
|
539
|
+
--provider "$WHITESMITH_PROVIDER" \\
|
|
540
|
+
--model "$WHITESMITH_MODEL" \\
|
|
520
541
|
--post
|
|
521
542
|
|
|
522
|
-
-
|
|
523
|
-
if: success()
|
|
524
|
-
env:
|
|
525
|
-
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
543
|
+
- if: success()
|
|
526
544
|
run: |
|
|
527
545
|
gh api repos/\${{ github.repository }}/issues/comments/\${{ github.event.comment.id }}/reactions \\
|
|
528
546
|
-f content="+1"
|
|
529
547
|
|
|
530
|
-
-
|
|
531
|
-
if: failure()
|
|
532
|
-
env:
|
|
533
|
-
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
548
|
+
- if: failure()
|
|
534
549
|
run: |
|
|
535
550
|
gh api repos/\${{ github.repository }}/issues/comments/\${{ github.event.comment.id }}/reactions \\
|
|
536
551
|
-f content="-1"
|
|
537
|
-
gh issue comment \${{ github.event.issue.number }} \\
|
|
538
|
-
--
|
|
539
|
-
--body "❌ Agent run failed for [this comment](\${{ github.event.comment.html_url }}). Check the [workflow run](\${{ github.server_url }}/\${{ github.repository }}/actions/runs/\${{ github.run_id }}) for details."
|
|
552
|
+
gh issue comment \${{ github.event.issue.number }} --repo \${{ github.repository }} \\
|
|
553
|
+
--body "❌ Agent run failed. See [workflow run](\${{ github.server_url }}/\${{ github.repository }}/actions/runs/\${{ github.run_id }})."
|
|
540
554
|
`;
|
|
541
555
|
}
|
|
542
556
|
|
|
543
|
-
function
|
|
557
|
+
function generateReviewWorkflow(config: CIConfig): string {
|
|
558
|
+
const envBlock = generateTopLevelEnv(config);
|
|
559
|
+
|
|
560
|
+
// When the review step is enabled in the main loop, whitesmith PRs are
|
|
561
|
+
// already reviewed inline. The workflow should only review non-whitesmith PRs.
|
|
562
|
+
// When the review step is disabled, the workflow reviews ALL PRs.
|
|
563
|
+
const skipWhitesmithCheck = config.reviewStepEnabled
|
|
564
|
+
? `\
|
|
565
|
+
check:
|
|
566
|
+
if: github.event_name == 'pull_request'
|
|
567
|
+
runs-on: ubuntu-latest
|
|
568
|
+
outputs:
|
|
569
|
+
should_run: \${{ steps.check.outputs.should_run }}
|
|
570
|
+
steps:
|
|
571
|
+
- id: check
|
|
572
|
+
run: |
|
|
573
|
+
BRANCH="\${{ github.event.pull_request.head.ref }}"
|
|
574
|
+
if echo "$BRANCH" | grep -qE '^(investigate|issue)/'; then
|
|
575
|
+
echo "Skipping review for whitesmith-managed branch: $BRANCH"
|
|
576
|
+
echo "should_run=false" >> "$GITHUB_OUTPUT"
|
|
577
|
+
else
|
|
578
|
+
echo "should_run=true" >> "$GITHUB_OUTPUT"
|
|
579
|
+
fi
|
|
580
|
+
|
|
581
|
+
review:
|
|
582
|
+
needs: check
|
|
583
|
+
if: >-
|
|
584
|
+
(github.event_name == 'workflow_dispatch') ||
|
|
585
|
+
(needs.check.outputs.should_run == 'true')`
|
|
586
|
+
: `\
|
|
587
|
+
review:`;
|
|
588
|
+
|
|
589
|
+
return `\
|
|
590
|
+
name: whitesmith-review
|
|
591
|
+
|
|
592
|
+
on:
|
|
593
|
+
pull_request:
|
|
594
|
+
types: [opened, synchronize]
|
|
595
|
+
workflow_dispatch:
|
|
596
|
+
inputs:
|
|
597
|
+
number:
|
|
598
|
+
description: 'PR or issue number to review'
|
|
599
|
+
required: true
|
|
600
|
+
type:
|
|
601
|
+
description: 'Review type (auto-detected if empty): pr, issue-tasks, issue-tasks-completed'
|
|
602
|
+
provider:
|
|
603
|
+
description: 'AI provider (overrides WHITESMITH_PROVIDER)'
|
|
604
|
+
model:
|
|
605
|
+
description: 'AI model (overrides WHITESMITH_MODEL)'
|
|
606
|
+
|
|
607
|
+
env:
|
|
608
|
+
${envBlock}
|
|
609
|
+
|
|
610
|
+
concurrency:
|
|
611
|
+
group: whitesmith-review-\${{ github.event.pull_request.number || inputs.number }}
|
|
612
|
+
cancel-in-progress: true
|
|
613
|
+
|
|
614
|
+
permissions:
|
|
615
|
+
contents: read
|
|
616
|
+
issues: write
|
|
617
|
+
pull-requests: write
|
|
618
|
+
|
|
619
|
+
jobs:
|
|
620
|
+
${skipWhitesmithCheck}
|
|
621
|
+
runs-on: ubuntu-latest
|
|
622
|
+
steps:
|
|
623
|
+
- uses: actions/checkout@v4
|
|
624
|
+
with:
|
|
625
|
+
fetch-depth: 0
|
|
626
|
+
|
|
627
|
+
- uses: ./.github/actions/setup-whitesmith
|
|
628
|
+
|
|
629
|
+
- if: github.event_name == 'pull_request'
|
|
630
|
+
run: |
|
|
631
|
+
whitesmith review . \\
|
|
632
|
+
--number "\${{ github.event.pull_request.number }}" \\
|
|
633
|
+
--provider "\${{ env.WHITESMITH_PROVIDER }}" \\
|
|
634
|
+
--model "\${{ env.WHITESMITH_MODEL }}" \\
|
|
635
|
+
--post
|
|
636
|
+
|
|
637
|
+
- if: github.event_name == 'workflow_dispatch'
|
|
638
|
+
run: |
|
|
639
|
+
TYPE_FLAG=""
|
|
640
|
+
if [ -n "\${{ inputs.type }}" ]; then
|
|
641
|
+
TYPE_FLAG="--type \${{ inputs.type }}"
|
|
642
|
+
fi
|
|
643
|
+
whitesmith review . \\
|
|
644
|
+
--number "\${{ inputs.number }}" \\
|
|
645
|
+
\$TYPE_FLAG \\
|
|
646
|
+
--provider "\${{ inputs.provider || env.WHITESMITH_PROVIDER }}" \\
|
|
647
|
+
--model "\${{ inputs.model || env.WHITESMITH_MODEL }}" \\
|
|
648
|
+
--post
|
|
649
|
+
`;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function generateIssueWorkflow(config: CIConfig): string {
|
|
653
|
+
const envBlock = generateTopLevelEnv(config);
|
|
654
|
+
|
|
655
|
+
return `\
|
|
656
|
+
name: whitesmith-issue
|
|
657
|
+
|
|
658
|
+
on:
|
|
659
|
+
issues:
|
|
660
|
+
types: [opened]
|
|
661
|
+
|
|
662
|
+
env:
|
|
663
|
+
${envBlock}
|
|
664
|
+
|
|
665
|
+
concurrency:
|
|
666
|
+
group: whitesmith-issue-\${{ github.event.issue.number }}
|
|
667
|
+
cancel-in-progress: false
|
|
668
|
+
|
|
669
|
+
permissions:
|
|
670
|
+
contents: write
|
|
671
|
+
issues: write
|
|
672
|
+
pull-requests: write
|
|
673
|
+
|
|
674
|
+
jobs:
|
|
675
|
+
run:
|
|
676
|
+
runs-on: ubuntu-latest
|
|
677
|
+
steps:
|
|
678
|
+
- uses: actions/checkout@v4
|
|
679
|
+
with:
|
|
680
|
+
fetch-depth: 0
|
|
681
|
+
|
|
682
|
+
- uses: ./.github/actions/setup-whitesmith
|
|
683
|
+
|
|
684
|
+
- run: |
|
|
685
|
+
whitesmith run . \\
|
|
686
|
+
--issue "\${{ github.event.issue.number }}" \\
|
|
687
|
+
--provider "$WHITESMITH_PROVIDER" \\
|
|
688
|
+
--model "$WHITESMITH_MODEL" \\
|
|
689
|
+
--max-iterations 10
|
|
690
|
+
`;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function generateReconcileWorkflow(config: CIConfig): string {
|
|
694
|
+
const envBlock = generateTopLevelEnv(config);
|
|
695
|
+
|
|
544
696
|
return `\
|
|
545
697
|
name: whitesmith-reconcile
|
|
546
698
|
|
|
@@ -549,42 +701,71 @@ on:
|
|
|
549
701
|
types: [closed]
|
|
550
702
|
branches: [main]
|
|
551
703
|
|
|
704
|
+
env:
|
|
705
|
+
${envBlock}
|
|
706
|
+
|
|
552
707
|
permissions:
|
|
553
|
-
contents:
|
|
708
|
+
contents: write
|
|
554
709
|
issues: write
|
|
555
|
-
pull-requests:
|
|
710
|
+
pull-requests: write
|
|
556
711
|
|
|
557
712
|
jobs:
|
|
558
|
-
|
|
713
|
+
parse:
|
|
559
714
|
if: github.event.pull_request.merged == true
|
|
560
715
|
runs-on: ubuntu-latest
|
|
716
|
+
outputs:
|
|
717
|
+
issue_number: \${{ steps.parse.outputs.issue_number }}
|
|
718
|
+
branch_type: \${{ steps.parse.outputs.branch_type }}
|
|
561
719
|
steps:
|
|
562
|
-
-
|
|
720
|
+
- id: parse
|
|
721
|
+
run: |
|
|
722
|
+
BRANCH="\${{ github.event.pull_request.head.ref }}"
|
|
723
|
+
INVESTIGATE_NUM=$(echo "$BRANCH" | sed -n 's|^investigate/\\([0-9]*\\)$|\\1|p')
|
|
724
|
+
ISSUE_NUM=$(echo "$BRANCH" | sed -n 's|^issue/\\([0-9]*\\)$|\\1|p')
|
|
725
|
+
if [ -n "$INVESTIGATE_NUM" ]; then
|
|
726
|
+
echo "issue_number=$INVESTIGATE_NUM" >> "$GITHUB_OUTPUT"
|
|
727
|
+
echo "branch_type=investigate" >> "$GITHUB_OUTPUT"
|
|
728
|
+
elif [ -n "$ISSUE_NUM" ]; then
|
|
729
|
+
echo "issue_number=$ISSUE_NUM" >> "$GITHUB_OUTPUT"
|
|
730
|
+
echo "branch_type=issue" >> "$GITHUB_OUTPUT"
|
|
731
|
+
else
|
|
732
|
+
echo "branch_type=other" >> "$GITHUB_OUTPUT"
|
|
733
|
+
fi
|
|
563
734
|
|
|
564
|
-
|
|
565
|
-
|
|
735
|
+
implement:
|
|
736
|
+
needs: parse
|
|
737
|
+
if: needs.parse.outputs.branch_type == 'investigate'
|
|
738
|
+
runs-on: ubuntu-latest
|
|
739
|
+
concurrency:
|
|
740
|
+
group: whitesmith-issue-\${{ needs.parse.outputs.issue_number }}
|
|
741
|
+
cancel-in-progress: false
|
|
742
|
+
steps:
|
|
743
|
+
- uses: actions/checkout@v4
|
|
566
744
|
with:
|
|
567
|
-
|
|
745
|
+
fetch-depth: 0
|
|
568
746
|
|
|
569
|
-
-
|
|
570
|
-
id: npm-prefix
|
|
571
|
-
run: echo "dir=$(npm prefix -g)" >> "$GITHUB_OUTPUT"
|
|
747
|
+
- uses: ./.github/actions/setup-whitesmith
|
|
572
748
|
|
|
573
|
-
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
749
|
+
- run: |
|
|
750
|
+
whitesmith run . \\
|
|
751
|
+
--issue "\${{ needs.parse.outputs.issue_number }}" \\
|
|
752
|
+
--provider "$WHITESMITH_PROVIDER" \\
|
|
753
|
+
--model "$WHITESMITH_MODEL" \\
|
|
754
|
+
--max-iterations 10
|
|
579
755
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
756
|
+
reconcile:
|
|
757
|
+
needs: parse
|
|
758
|
+
if: needs.parse.outputs.branch_type != 'investigate'
|
|
759
|
+
runs-on: ubuntu-latest
|
|
760
|
+
concurrency:
|
|
761
|
+
group: \${{ (needs.parse.outputs.issue_number && format('whitesmith-issue-{0}', needs.parse.outputs.issue_number)) || 'whitesmith-reconcile-other' }}
|
|
762
|
+
cancel-in-progress: false
|
|
763
|
+
steps:
|
|
764
|
+
- uses: actions/checkout@v4
|
|
583
765
|
|
|
584
|
-
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
run: whitesmith reconcile .
|
|
766
|
+
- uses: ./.github/actions/setup-whitesmith
|
|
767
|
+
|
|
768
|
+
- run: whitesmith reconcile .
|
|
588
769
|
`;
|
|
589
770
|
}
|
|
590
771
|
|
|
@@ -690,6 +871,42 @@ if (repo && token) {
|
|
|
690
871
|
export interface InstallCIOptions {
|
|
691
872
|
authMode: AuthMode;
|
|
692
873
|
fake?: boolean;
|
|
874
|
+
/** Path to a JSON config file — skips interactive prompts. */
|
|
875
|
+
configFile?: string;
|
|
876
|
+
/** Write the provider config as JSON to this file path instead of generating workflows. */
|
|
877
|
+
exportConfig?: string;
|
|
878
|
+
/** When used with --export-config, prompt for API keys and include them in the output. */
|
|
879
|
+
includeSecrets?: boolean;
|
|
880
|
+
/** Build whitesmith from source (pnpm i + link --global) instead of installing from npm. Auto-detected when inside the whitesmith repo. */
|
|
881
|
+
dev?: boolean;
|
|
882
|
+
/** Generate the review workflow for PR reviews. Off by default. */
|
|
883
|
+
reviewWorkflow?: boolean;
|
|
884
|
+
/** Whether the review step is enabled in the main loop (affects review workflow filtering). */
|
|
885
|
+
reviewStepEnabled?: boolean;
|
|
886
|
+
/** Skip setting GitHub secrets (useful when reconfiguring workflows only). */
|
|
887
|
+
skipSecrets?: boolean;
|
|
888
|
+
/** whitesmith package version to pin in the install command. */
|
|
889
|
+
version: string;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Load config from a JSON file, skipping interactive prompts.
|
|
894
|
+
*/
|
|
895
|
+
function loadConfigFile(filePath: string): CIConfigFile {
|
|
896
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
897
|
+
const data = JSON.parse(raw) as CIConfigFile;
|
|
898
|
+
|
|
899
|
+
if (!data.providers || !Array.isArray(data.providers) || data.providers.length === 0) {
|
|
900
|
+
throw new Error(`Config file must contain a non-empty "providers" array`);
|
|
901
|
+
}
|
|
902
|
+
if (!data.defaultProvider) {
|
|
903
|
+
throw new Error(`Config file must contain "defaultProvider"`);
|
|
904
|
+
}
|
|
905
|
+
if (!data.defaultModel) {
|
|
906
|
+
throw new Error(`Config file must contain "defaultModel"`);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
return data;
|
|
693
910
|
}
|
|
694
911
|
|
|
695
912
|
export async function installGitHubCI(
|
|
@@ -697,33 +914,42 @@ export async function installGitHubCI(
|
|
|
697
914
|
options: InstallCIOptions,
|
|
698
915
|
): Promise<void> {
|
|
699
916
|
const {authMode} = options;
|
|
917
|
+
const fake = options.fake ?? false;
|
|
918
|
+
const exportConfig = options.exportConfig ?? undefined;
|
|
700
919
|
|
|
701
920
|
console.log('=== whitesmith install-ci (GitHub) ===\n');
|
|
702
921
|
console.log(`Auth mode: ${authMode}\n`);
|
|
703
922
|
|
|
704
923
|
let repo = ctx.repo;
|
|
705
924
|
|
|
706
|
-
if (!repo && authMode === 'models-json' && ctx.ghAvailable) {
|
|
925
|
+
if (!exportConfig && !fake && !repo && authMode === 'models-json' && ctx.ghAvailable) {
|
|
707
926
|
repo = await input({
|
|
708
927
|
message: 'GitHub repository (owner/repo) — needed to set secrets:',
|
|
709
928
|
});
|
|
710
929
|
ctx.repo = repo;
|
|
711
930
|
}
|
|
712
931
|
|
|
713
|
-
let providers: ProviderEntry[]
|
|
932
|
+
let providers: ProviderEntry[];
|
|
714
933
|
let defaultProvider: string;
|
|
715
934
|
let defaultModel: string;
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
935
|
+
let loadedSecrets: Record<string, string> | undefined;
|
|
936
|
+
|
|
937
|
+
if (options.configFile) {
|
|
938
|
+
// Load from file — no prompts
|
|
939
|
+
const loaded = loadConfigFile(options.configFile);
|
|
940
|
+
providers = loaded.providers;
|
|
941
|
+
defaultProvider = loaded.defaultProvider;
|
|
942
|
+
defaultModel = loaded.defaultModel;
|
|
943
|
+
loadedSecrets = loaded.secrets;
|
|
944
|
+
} else if (authMode === 'models-json') {
|
|
945
|
+
// Interactive prompts
|
|
719
946
|
providers = await promptProviders();
|
|
720
|
-
|
|
721
|
-
// Pick defaults
|
|
722
947
|
const defaults = await promptDefaults(providers);
|
|
723
948
|
defaultProvider = defaults.provider;
|
|
724
949
|
defaultModel = defaults.model;
|
|
725
950
|
} else {
|
|
726
951
|
// auth.json mode — still need provider/model for whitesmith commands
|
|
952
|
+
providers = [];
|
|
727
953
|
defaultProvider = await input({
|
|
728
954
|
message: 'Default AI provider:',
|
|
729
955
|
default: 'anthropic',
|
|
@@ -734,24 +960,77 @@ export async function installGitHubCI(
|
|
|
734
960
|
});
|
|
735
961
|
}
|
|
736
962
|
|
|
963
|
+
// ── Export config mode — write JSON to stdout and exit ───────────────
|
|
964
|
+
|
|
965
|
+
if (exportConfig) {
|
|
966
|
+
const configFile: CIConfigFile = {providers, defaultProvider, defaultModel};
|
|
967
|
+
if (options.includeSecrets && providers.length > 0) {
|
|
968
|
+
const secrets: Record<string, string> = {};
|
|
969
|
+
const seen = new Set<string>();
|
|
970
|
+
for (const p of providers) {
|
|
971
|
+
if (seen.has(p.apiKeyEnvVar)) continue;
|
|
972
|
+
seen.add(p.apiKeyEnvVar);
|
|
973
|
+
const key = await password({
|
|
974
|
+
message: `Enter API key for ${p.name} (${p.apiKeyEnvVar}):`,
|
|
975
|
+
});
|
|
976
|
+
if (key) secrets[p.apiKeyEnvVar] = key;
|
|
977
|
+
}
|
|
978
|
+
if (Object.keys(secrets).length > 0) {
|
|
979
|
+
configFile.secrets = secrets;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
const json = JSON.stringify(configFile, null, 2) + '\n';
|
|
983
|
+
fs.writeFileSync(exportConfig, json, 'utf-8');
|
|
984
|
+
console.log(`\n✅ Config written to ${exportConfig}`);
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Auto-detect dev mode: check if we're inside the whitesmith repo itself
|
|
989
|
+
let dev = options.dev ?? false;
|
|
990
|
+
if (!dev) {
|
|
991
|
+
try {
|
|
992
|
+
const pkgPath = path.join(ctx.workDir, 'package.json');
|
|
993
|
+
if (fs.existsSync(pkgPath)) {
|
|
994
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
995
|
+
if (pkg.name === 'whitesmith') {
|
|
996
|
+
dev = true;
|
|
997
|
+
console.log('📦 Detected whitesmith repo — using dev mode (build from source)\n');
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
} catch {
|
|
1001
|
+
// Ignore — not in whitesmith repo
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
const reviewWorkflow = options.reviewWorkflow ?? false;
|
|
1006
|
+
const reviewStepEnabled = options.reviewStepEnabled ?? true;
|
|
1007
|
+
|
|
737
1008
|
const config: CIConfig = {
|
|
738
1009
|
authMode,
|
|
739
1010
|
providers,
|
|
740
1011
|
defaultProvider,
|
|
741
1012
|
defaultModel,
|
|
1013
|
+
dev,
|
|
1014
|
+
reviewWorkflow,
|
|
1015
|
+
reviewStepEnabled,
|
|
1016
|
+
version: options.version,
|
|
742
1017
|
};
|
|
743
1018
|
|
|
744
1019
|
// ── Set GitHub secrets via gh CLI ─────────────────────────────────────
|
|
745
1020
|
|
|
746
|
-
const
|
|
1021
|
+
const skipSecrets = options.skipSecrets ?? false;
|
|
747
1022
|
|
|
748
|
-
if (
|
|
1023
|
+
if (skipSecrets) {
|
|
1024
|
+
console.log('\n🔑 Skipping secret setup (--no-secrets)');
|
|
1025
|
+
} else if (!fake && authMode === 'models-json' && repo) {
|
|
749
1026
|
if (!ctx.ghAvailable) {
|
|
750
1027
|
console.log('\n⚠ GitHub CLI (gh) is not available or not authenticated.');
|
|
751
1028
|
console.log(' You will need to set the following secrets manually.\n');
|
|
752
1029
|
} else {
|
|
1030
|
+
// If config file included secrets, set them directly without prompting
|
|
1031
|
+
const configSecrets = options.configFile ? loadedSecrets : undefined;
|
|
753
1032
|
console.log('\n🔑 Setting API key secrets on GitHub...\n');
|
|
754
|
-
const setSecrets = await
|
|
1033
|
+
const setSecrets = await setOrPromptSecrets(ctx, providers, configSecrets);
|
|
755
1034
|
|
|
756
1035
|
const allEnvVars = [...new Set(providers.map((p) => p.apiKeyEnvVar))];
|
|
757
1036
|
const missing = allEnvVars.filter((v) => !setSecrets.includes(v));
|
|
@@ -769,11 +1048,17 @@ export async function installGitHubCI(
|
|
|
769
1048
|
// ── Generate and write workflow files ─────────────────────────────────
|
|
770
1049
|
|
|
771
1050
|
const outputBase = fake ? '.fake' : '.github';
|
|
772
|
-
const
|
|
773
|
-
const workflowsDir = path.join(
|
|
1051
|
+
const baseDir = path.join(ctx.workDir, outputBase);
|
|
1052
|
+
const workflowsDir = path.join(baseDir, 'workflows');
|
|
1053
|
+
const actionsDir = path.join(baseDir, 'actions', 'setup-whitesmith');
|
|
774
1054
|
fs.mkdirSync(workflowsDir, {recursive: true});
|
|
1055
|
+
fs.mkdirSync(actionsDir, {recursive: true});
|
|
775
1056
|
|
|
776
1057
|
const files: {path: string; content: string}[] = [
|
|
1058
|
+
{
|
|
1059
|
+
path: path.join(actionsDir, 'action.yml'),
|
|
1060
|
+
content: generateSetupAction(config),
|
|
1061
|
+
},
|
|
777
1062
|
{
|
|
778
1063
|
path: path.join(workflowsDir, 'whitesmith.yml'),
|
|
779
1064
|
content: generateMainWorkflow(config),
|
|
@@ -782,14 +1067,25 @@ export async function installGitHubCI(
|
|
|
782
1067
|
path: path.join(workflowsDir, 'whitesmith-comment.yml'),
|
|
783
1068
|
content: generateCommentWorkflow(config),
|
|
784
1069
|
},
|
|
1070
|
+
{
|
|
1071
|
+
path: path.join(workflowsDir, 'whitesmith-issue.yml'),
|
|
1072
|
+
content: generateIssueWorkflow(config),
|
|
1073
|
+
},
|
|
785
1074
|
{
|
|
786
1075
|
path: path.join(workflowsDir, 'whitesmith-reconcile.yml'),
|
|
787
|
-
content: generateReconcileWorkflow(),
|
|
1076
|
+
content: generateReconcileWorkflow(config),
|
|
788
1077
|
},
|
|
789
1078
|
];
|
|
790
1079
|
|
|
1080
|
+
if (config.reviewWorkflow) {
|
|
1081
|
+
files.push({
|
|
1082
|
+
path: path.join(workflowsDir, 'whitesmith-review.yml'),
|
|
1083
|
+
content: generateReviewWorkflow(config),
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
|
|
791
1087
|
if (authMode === 'auth-json') {
|
|
792
|
-
const scriptsDir = path.join(
|
|
1088
|
+
const scriptsDir = path.join(baseDir, 'scripts');
|
|
793
1089
|
fs.mkdirSync(scriptsDir, {recursive: true});
|
|
794
1090
|
files.push({
|
|
795
1091
|
path: path.join(scriptsDir, 'refresh-oauth-token.mjs'),
|