pullfrog 0.1.59 → 0.1.61
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/dist/agents/shared.d.ts +17 -0
- package/dist/cli.mjs +218 -551
- package/dist/external.d.ts +7 -0
- package/dist/index.js +163 -45
- package/dist/internal.js +13 -3
- package/dist/toolState.d.ts +3 -1
- package/dist/utils/activity.d.ts +12 -0
- package/dist/utils/credentialCheck.d.ts +7 -1
- package/dist/utils/patchWorkflowRunFields.d.ts +1 -1
- package/dist/utils/payload.d.ts +2 -0
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -106587,6 +106587,11 @@ var DEFAULT_ACTIVITY_TIMEOUT_MS = 3e5;
|
|
|
106587
106587
|
var AGENT_ACTIVITY_TIMEOUT_MS = 9e5;
|
|
106588
106588
|
var AGENT_FIRST_EVENT_TIMEOUT_MS = 12e4;
|
|
106589
106589
|
var DEFAULT_ACTIVITY_CHECK_INTERVAL_MS = 5e3;
|
|
106590
|
+
function watchdogBudgetMs(compiled, envVar) {
|
|
106591
|
+
const raw2 = Number(process.env[envVar]);
|
|
106592
|
+
if (!Number.isFinite(raw2) || raw2 <= 0) return compiled;
|
|
106593
|
+
return Math.min(compiled, raw2);
|
|
106594
|
+
}
|
|
106590
106595
|
var DEBUG_TS_PREFIX = /^(?:\[\d{4}-\d{2}-\d{2}T[^\]]+\]\s+)?/.source;
|
|
106591
106596
|
var ACTIVITY_NOISE_PATTERNS = [
|
|
106592
106597
|
new RegExp(`${DEBUG_TS_PREFIX}\\[mcp-proxy\\]`),
|
|
@@ -107350,7 +107355,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
107350
107355
|
// package.json
|
|
107351
107356
|
var package_default = {
|
|
107352
107357
|
name: "pullfrog",
|
|
107353
|
-
version: "0.1.
|
|
107358
|
+
version: "0.1.61",
|
|
107354
107359
|
type: "module",
|
|
107355
107360
|
bin: {
|
|
107356
107361
|
pullfrog: "dist/cli.mjs",
|
|
@@ -109459,7 +109464,7 @@ var claude = agent({
|
|
|
109459
109464
|
if ((isBedrockRoute || isVertexRoute2) && specifier && effort.alias?.effort) {
|
|
109460
109465
|
applyHostedEffortCapabilities({ env: env2, modelId: specifier, levels: effort.alias.effort });
|
|
109461
109466
|
}
|
|
109462
|
-
if (env2.CLAUDE_CODE_OAUTH_TOKEN && !isBedrockRoute && env2.ANTHROPIC_API_KEY) {
|
|
109467
|
+
if (env2.CLAUDE_CODE_OAUTH_TOKEN && !isBedrockRoute && !isVertexRoute2 && !env2.ANTHROPIC_BASE_URL && !env2.ANTHROPIC_AUTH_TOKEN && env2.ANTHROPIC_API_KEY) {
|
|
109463
109468
|
const preflight = await preflightClaudeSubscription({
|
|
109464
109469
|
token: env2.CLAUDE_CODE_OAUTH_TOKEN,
|
|
109465
109470
|
model
|
|
@@ -109476,6 +109481,19 @@ var claude = agent({
|
|
|
109476
109481
|
delete env2.CLAUDE_CODE_OAUTH_TOKEN;
|
|
109477
109482
|
}
|
|
109478
109483
|
}
|
|
109484
|
+
if (env2.CLAUDE_CODE_USE_BEDROCK) {
|
|
109485
|
+
ctx.toolState.credential = "bedrock";
|
|
109486
|
+
} else if (env2.CLAUDE_CODE_USE_VERTEX) {
|
|
109487
|
+
ctx.toolState.credential = "vertex";
|
|
109488
|
+
} else if (env2.CLAUDE_CODE_USE_FOUNDRY) {
|
|
109489
|
+
ctx.toolState.credential = "foundry";
|
|
109490
|
+
} else if (env2.ANTHROPIC_AUTH_TOKEN) {
|
|
109491
|
+
ctx.toolState.credential = "gateway";
|
|
109492
|
+
} else if (env2.ANTHROPIC_API_KEY) {
|
|
109493
|
+
ctx.toolState.credential = "api_key";
|
|
109494
|
+
} else if (env2.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
109495
|
+
ctx.toolState.credential = "subscription";
|
|
109496
|
+
}
|
|
109479
109497
|
const effortEnvOverride = env2[CLAUDE_EFFORT_ENV]?.trim();
|
|
109480
109498
|
if (effortEnvOverride) {
|
|
109481
109499
|
log2.warning(
|
|
@@ -109938,6 +109956,18 @@ function toTodoWriteInput(item) {
|
|
|
109938
109956
|
}))
|
|
109939
109957
|
};
|
|
109940
109958
|
}
|
|
109959
|
+
var CODEX_MODEL_PRICING = {
|
|
109960
|
+
"gpt-5.6-sol": { input: 5, cacheRead: 0.5, cacheWrite: 6.25, output: 30 },
|
|
109961
|
+
"gpt-5.6-luna": { input: 0.2, cacheRead: 0.02, cacheWrite: 0.25, output: 1.2 },
|
|
109962
|
+
"gpt-5.6-terra": { input: 2, cacheRead: 0.2, cacheWrite: 2.5, output: 12 }
|
|
109963
|
+
};
|
|
109964
|
+
function codexCostUsd(params) {
|
|
109965
|
+
const price = params.model ? CODEX_MODEL_PRICING[params.model] : void 0;
|
|
109966
|
+
if (!price) return void 0;
|
|
109967
|
+
const fresh = Math.max(0, params.input - params.cacheRead - params.cacheWrite);
|
|
109968
|
+
const usd = (fresh * price.input + params.cacheRead * price.cacheRead + params.cacheWrite * price.cacheWrite + params.output * price.output) / 1e6;
|
|
109969
|
+
return usd > 0 ? usd : void 0;
|
|
109970
|
+
}
|
|
109941
109971
|
async function runCodex2(params) {
|
|
109942
109972
|
const startTime = performance6.now();
|
|
109943
109973
|
const thinkingTimer = new ThinkingTimer();
|
|
@@ -109949,14 +109979,15 @@ async function runCodex2(params) {
|
|
|
109949
109979
|
let turnError = null;
|
|
109950
109980
|
let lastProviderError = null;
|
|
109951
109981
|
let stdoutBuffer = "";
|
|
109952
|
-
const tokens = { input: 0, cacheRead: 0, output: 0 };
|
|
109982
|
+
const tokens = { input: 0, cacheRead: 0, cacheWrite: 0, output: 0 };
|
|
109953
109983
|
function buildUsage2() {
|
|
109954
109984
|
if (tokens.input === 0 && tokens.output === 0) return void 0;
|
|
109955
109985
|
return {
|
|
109956
109986
|
agent: "codex",
|
|
109957
109987
|
inputTokens: tokens.input,
|
|
109958
109988
|
outputTokens: tokens.output,
|
|
109959
|
-
cacheReadTokens: tokens.cacheRead || void 0
|
|
109989
|
+
cacheReadTokens: tokens.cacheRead || void 0,
|
|
109990
|
+
costUsd: codexCostUsd({ model: params.model, ...tokens })
|
|
109960
109991
|
};
|
|
109961
109992
|
}
|
|
109962
109993
|
function onItem(item, phase) {
|
|
@@ -110021,6 +110052,7 @@ async function runCodex2(params) {
|
|
|
110021
110052
|
case "turn.completed":
|
|
110022
110053
|
tokens.input += event.usage.input_tokens ?? 0;
|
|
110023
110054
|
tokens.cacheRead += event.usage.cached_input_tokens ?? 0;
|
|
110055
|
+
tokens.cacheWrite += event.usage.cache_write_input_tokens ?? 0;
|
|
110024
110056
|
tokens.output += event.usage.output_tokens ?? 0;
|
|
110025
110057
|
return;
|
|
110026
110058
|
case "turn.failed":
|
|
@@ -110181,6 +110213,11 @@ var codex = agent({
|
|
|
110181
110213
|
} else if (process.env.OPENAI_API_KEY) {
|
|
110182
110214
|
env2.CODEX_API_KEY = process.env.OPENAI_API_KEY;
|
|
110183
110215
|
}
|
|
110216
|
+
if (codexHomeAuth) {
|
|
110217
|
+
ctx.toolState.credential = "subscription";
|
|
110218
|
+
} else if (env2.CODEX_API_KEY) {
|
|
110219
|
+
ctx.toolState.credential = "api_key";
|
|
110220
|
+
}
|
|
110184
110221
|
const securityFlags = securityOverrideFlags({
|
|
110185
110222
|
ctx,
|
|
110186
110223
|
sandboxMode: ctx.payload.push === "disabled" ? "read-only" : "workspace-write",
|
|
@@ -110189,7 +110226,13 @@ var codex = agent({
|
|
|
110189
110226
|
model: resolveCodexModel(ctx),
|
|
110190
110227
|
effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0
|
|
110191
110228
|
});
|
|
110192
|
-
const runnerArgs = {
|
|
110229
|
+
const runnerArgs = {
|
|
110230
|
+
cliPath,
|
|
110231
|
+
cwd: process.cwd(),
|
|
110232
|
+
env: env2,
|
|
110233
|
+
todoTracker: ctx.todoTracker,
|
|
110234
|
+
model: resolveCodexModel(ctx)
|
|
110235
|
+
};
|
|
110193
110236
|
const initial = await runCodex2({
|
|
110194
110237
|
...runnerArgs,
|
|
110195
110238
|
args: [...securityFlags, "exec", "--json", ctx.instructions.full],
|
|
@@ -116277,6 +116320,7 @@ function processTerminalToolPart(ctx, part, label, isOrchestrator) {
|
|
|
116277
116320
|
const callLine = inputFormatted !== "{}" ? `\xBB ${toolName}(${inputFormatted})` : `\xBB ${toolName}()`;
|
|
116278
116321
|
log2.info(withLabel(label, callLine));
|
|
116279
116322
|
if (isOrchestrator) ctx.loggedToolCallIDs.add(toolId);
|
|
116323
|
+
if (isOrchestrator && toolName.startsWith("pullfrog_")) ctx.mcpToolCalls++;
|
|
116280
116324
|
if (state.status === "completed") {
|
|
116281
116325
|
log2.debug(withLabel(label, ` output: ${state.output}`));
|
|
116282
116326
|
} else {
|
|
@@ -116515,21 +116559,28 @@ function formatConfigError(name, data) {
|
|
|
116515
116559
|
).join("");
|
|
116516
116560
|
return `${name}${detail}${bullets}`;
|
|
116517
116561
|
}
|
|
116562
|
+
var WATCHDOG_SALVAGE_PROMPT = "Your previous turn was cut off mid-response by a provider stall. Nothing you had not already submitted through a tool was saved. Continue from where you stopped and submit your work now \u2014 do not restart your analysis.";
|
|
116518
116563
|
function startInnerActivityWatchdog(params) {
|
|
116519
116564
|
let fired = false;
|
|
116565
|
+
let everFired = false;
|
|
116520
116566
|
const id = setInterval(() => {
|
|
116521
116567
|
if (fired) return;
|
|
116522
116568
|
const idleMs = performance7.now() - params.ctx.lastEventAt;
|
|
116523
|
-
const
|
|
116569
|
+
const compiledMs = params.ctx.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
|
|
116570
|
+
const budgetMs = everFired ? compiledMs : watchdogBudgetMs(
|
|
116571
|
+
compiledMs,
|
|
116572
|
+
params.ctx.sawModelOutput ? "PULLFROG_E2E_ACTIVITY_TIMEOUT_MS" : "PULLFROG_E2E_FIRST_EVENT_TIMEOUT_MS"
|
|
116573
|
+
);
|
|
116524
116574
|
if (idleMs <= budgetMs) return;
|
|
116525
116575
|
fired = true;
|
|
116576
|
+
everFired = true;
|
|
116526
116577
|
const idleSec = Math.round(idleMs / 1e3);
|
|
116527
116578
|
params.ctx.diagnostic.idleSec = idleSec;
|
|
116528
116579
|
params.ctx.diagnostic.sawModelOutput = params.ctx.sawModelOutput;
|
|
116529
116580
|
log2.info(
|
|
116530
116581
|
params.ctx.sawModelOutput ? `\xBB no opencode events for ${idleSec}s \u2014 aborting in-flight prompt and notifying harness` : `\xBB no opencode events for ${idleSec}s \u2014 the provider never returned a first token; aborting in-flight prompt and notifying harness`
|
|
116531
116582
|
);
|
|
116532
|
-
params.
|
|
116583
|
+
params.abortTurn();
|
|
116533
116584
|
try {
|
|
116534
116585
|
params.ctx.onActivityTimeout?.();
|
|
116535
116586
|
} catch (err) {
|
|
@@ -116539,7 +116590,22 @@ function startInnerActivityWatchdog(params) {
|
|
|
116539
116590
|
}
|
|
116540
116591
|
}, 5e3);
|
|
116541
116592
|
id.unref?.();
|
|
116542
|
-
return {
|
|
116593
|
+
return {
|
|
116594
|
+
stop: () => clearInterval(id),
|
|
116595
|
+
/**
|
|
116596
|
+
* Start a turn's idle budget from now. The clock must be reset with the
|
|
116597
|
+
* latch: `lastEventAt` only advances on model output, so a turn following a
|
|
116598
|
+
* stall would inherit the full stalled interval and be aborted on the very
|
|
116599
|
+
* next 5s tick — killing the salvage before the provider could answer. It
|
|
116600
|
+
* is the right semantics for an ordinary resume too, whose budget should
|
|
116601
|
+
* not be pre-spent by the post-run gate checks that ran between turns.
|
|
116602
|
+
*/
|
|
116603
|
+
armForTurn: () => {
|
|
116604
|
+
fired = false;
|
|
116605
|
+
params.ctx.lastEventAt = performance7.now();
|
|
116606
|
+
},
|
|
116607
|
+
firedThisTurn: () => fired
|
|
116608
|
+
};
|
|
116543
116609
|
}
|
|
116544
116610
|
var opencode = agent({
|
|
116545
116611
|
name: "opencode",
|
|
@@ -116651,6 +116717,7 @@ var opencode = agent({
|
|
|
116651
116717
|
variant: effort.rung,
|
|
116652
116718
|
todoTracker: ctx.todoTracker,
|
|
116653
116719
|
onActivityTimeout: ctx.onActivityTimeout,
|
|
116720
|
+
onTurnRecovered: ctx.onTurnRecovered,
|
|
116654
116721
|
onToolUse: ctx.onToolUse,
|
|
116655
116722
|
currentTurn: null,
|
|
116656
116723
|
eventCount: 0,
|
|
@@ -116659,6 +116726,7 @@ var opencode = agent({
|
|
|
116659
116726
|
promptMessageID: void 0,
|
|
116660
116727
|
taskDispatchByCallID: /* @__PURE__ */ new Map(),
|
|
116661
116728
|
loggedToolCallIDs: /* @__PURE__ */ new Set(),
|
|
116729
|
+
mcpToolCalls: 0,
|
|
116662
116730
|
recentStderr: server.recentStderr,
|
|
116663
116731
|
diagnostic: {
|
|
116664
116732
|
label: "Pullfrog",
|
|
@@ -116682,9 +116750,14 @@ var opencode = agent({
|
|
|
116682
116750
|
}
|
|
116683
116751
|
}
|
|
116684
116752
|
});
|
|
116685
|
-
const
|
|
116686
|
-
|
|
116687
|
-
|
|
116753
|
+
const runController = new AbortController();
|
|
116754
|
+
let turnController = new AbortController();
|
|
116755
|
+
const nextTurnSignal = () => {
|
|
116756
|
+
turnController = new AbortController();
|
|
116757
|
+
return AbortSignal.any([runController.signal, turnController.signal]);
|
|
116758
|
+
};
|
|
116759
|
+
const eventLoopPromise = consumeEvents(runnerCtx, runController.signal).catch((err) => {
|
|
116760
|
+
if (!runController.signal.aborted) {
|
|
116688
116761
|
log2.warning(
|
|
116689
116762
|
`\xBB opencode event subscription ended: ${err instanceof Error ? err.message : String(err)}`
|
|
116690
116763
|
);
|
|
@@ -116700,31 +116773,48 @@ var opencode = agent({
|
|
|
116700
116773
|
// a long synchronous tool call (no part.updated while it runs) can't
|
|
116701
116774
|
// false-positive it.
|
|
116702
116775
|
timeoutMs: AGENT_ACTIVITY_TIMEOUT_MS,
|
|
116703
|
-
|
|
116776
|
+
abortTurn: () => turnController.abort()
|
|
116704
116777
|
});
|
|
116705
116778
|
const sdkModel = parseModel2(model);
|
|
116779
|
+
let salvagesLeft = 1;
|
|
116780
|
+
const runTurn = async (text) => {
|
|
116781
|
+
const attempt = (prompt) => {
|
|
116782
|
+
watchdog.armForTurn();
|
|
116783
|
+
return runTurnGuarded(
|
|
116784
|
+
runnerCtx,
|
|
116785
|
+
() => runPromptTurn(runnerCtx, { text: prompt, model: sdkModel, signal: nextTurnSignal() })
|
|
116786
|
+
);
|
|
116787
|
+
};
|
|
116788
|
+
const standDownIfRecovered = (turn) => {
|
|
116789
|
+
if (watchdog.firedThisTurn() && turn.success) ctx.onTurnRecovered?.();
|
|
116790
|
+
return turn;
|
|
116791
|
+
};
|
|
116792
|
+
const result = await attempt(text);
|
|
116793
|
+
if (!watchdog.firedThisTurn()) return result;
|
|
116794
|
+
if (result.success) return standDownIfRecovered(result);
|
|
116795
|
+
if (salvagesLeft <= 0) return result;
|
|
116796
|
+
salvagesLeft--;
|
|
116797
|
+
ctx.onTurnRecovered?.();
|
|
116798
|
+
log2.info("\xBB activity watchdog cut the turn off \u2014 re-prompting once on the same session");
|
|
116799
|
+
const mcpCallsBefore = runnerCtx.mcpToolCalls;
|
|
116800
|
+
const salvaged = await attempt(WATCHDOG_SALVAGE_PROMPT);
|
|
116801
|
+
const usage = mergeAgentUsage(result.usage, salvaged.usage);
|
|
116802
|
+
if (runnerCtx.mcpToolCalls === mcpCallsBefore) {
|
|
116803
|
+
log2.info(
|
|
116804
|
+
"\xBB salvage turn reached no Pullfrog tool \u2014 keeping the activity-timeout failure"
|
|
116805
|
+
);
|
|
116806
|
+
return { ...result, usage };
|
|
116807
|
+
}
|
|
116808
|
+
return standDownIfRecovered({ ...salvaged, usage });
|
|
116809
|
+
};
|
|
116706
116810
|
try {
|
|
116707
|
-
const initial = await
|
|
116708
|
-
runnerCtx,
|
|
116709
|
-
() => runPromptTurn(runnerCtx, {
|
|
116710
|
-
text: ctx.instructions.full,
|
|
116711
|
-
model: sdkModel,
|
|
116712
|
-
signal: abortController.signal
|
|
116713
|
-
})
|
|
116714
|
-
);
|
|
116811
|
+
const initial = await runTurn(ctx.instructions.full);
|
|
116715
116812
|
const result = await runPostRunRetryLoop({
|
|
116716
116813
|
ctx,
|
|
116717
116814
|
initialResult: initial,
|
|
116718
116815
|
initialUsage: initial.usage,
|
|
116719
116816
|
reflectionPrompt: buildReflectionPrompt(ctx.toolState),
|
|
116720
|
-
resume: async (c2) =>
|
|
116721
|
-
runnerCtx,
|
|
116722
|
-
() => runPromptTurn(runnerCtx, {
|
|
116723
|
-
text: c2.prompt,
|
|
116724
|
-
model: sdkModel,
|
|
116725
|
-
signal: abortController.signal
|
|
116726
|
-
})
|
|
116727
|
-
)
|
|
116817
|
+
resume: async (c2) => runTurn(c2.prompt)
|
|
116728
116818
|
});
|
|
116729
116819
|
if (result.success) {
|
|
116730
116820
|
await ctx.todoTracker?.flush();
|
|
@@ -116734,7 +116824,7 @@ var opencode = agent({
|
|
|
116734
116824
|
return result;
|
|
116735
116825
|
} finally {
|
|
116736
116826
|
watchdog.stop();
|
|
116737
|
-
|
|
116827
|
+
runController.abort();
|
|
116738
116828
|
await eventLoopPromise.catch(() => {
|
|
116739
116829
|
});
|
|
116740
116830
|
}
|
|
@@ -189364,7 +189454,9 @@ var STRING_KEYS = [
|
|
|
189364
189454
|
"reviewNodeId",
|
|
189365
189455
|
"planCommentNodeId",
|
|
189366
189456
|
"summarySnapshot",
|
|
189367
|
-
"model"
|
|
189457
|
+
"model",
|
|
189458
|
+
"agent",
|
|
189459
|
+
"credential"
|
|
189368
189460
|
];
|
|
189369
189461
|
var NUMBER_KEYS = [
|
|
189370
189462
|
"inputTokens",
|
|
@@ -194525,7 +194617,7 @@ function hasEnvVar2(name) {
|
|
|
194525
194617
|
return typeof val === "string" && val.length > 0;
|
|
194526
194618
|
}
|
|
194527
194619
|
function hasClaudeCodeAuth() {
|
|
194528
|
-
return hasEnvVar2("CLAUDE_CODE_OAUTH_TOKEN") || hasEnvVar2("ANTHROPIC_API_KEY");
|
|
194620
|
+
return hasEnvVar2("CLAUDE_CODE_OAUTH_TOKEN") || hasEnvVar2("ANTHROPIC_API_KEY") || hasEnvVar2("ANTHROPIC_AUTH_TOKEN");
|
|
194529
194621
|
}
|
|
194530
194622
|
function hasCodexAuth() {
|
|
194531
194623
|
return hasEnvVar2("CODEX_AUTH_JSON") || hasEnvVar2("OPENAI_API_KEY");
|
|
@@ -194618,8 +194710,11 @@ function resolveAgent(ctx) {
|
|
|
194618
194710
|
} catch {
|
|
194619
194711
|
}
|
|
194620
194712
|
}
|
|
194621
|
-
if (!ctx.model
|
|
194622
|
-
|
|
194713
|
+
if (!ctx.model) {
|
|
194714
|
+
if (hasEnvVar2("ANTHROPIC_AUTH_TOKEN") && !hasEnvVar2("ANTHROPIC_API_KEY") && !hasEnvVar2("CLAUDE_CODE_OAUTH_TOKEN")) {
|
|
194715
|
+
return agents.claude;
|
|
194716
|
+
}
|
|
194717
|
+
if (ctx.codexAgent && hasCodexAuth() && !hasClaudeCodeAuth()) return agents.codex;
|
|
194623
194718
|
}
|
|
194624
194719
|
return agents.opencode;
|
|
194625
194720
|
}
|
|
@@ -194805,7 +194900,7 @@ function hasSingleProviderAuth(agentName) {
|
|
|
194805
194900
|
if (agentName === "codex") {
|
|
194806
194901
|
return hasEnvVar3("OPENAI_API_KEY") || hasEnvVar3("CODEX_AUTH_JSON");
|
|
194807
194902
|
}
|
|
194808
|
-
return hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN");
|
|
194903
|
+
return hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("ANTHROPIC_AUTH_TOKEN") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN");
|
|
194809
194904
|
}
|
|
194810
194905
|
function validateAgentApiKey(params) {
|
|
194811
194906
|
if (params.model) {
|
|
@@ -195137,7 +195232,8 @@ var PROBES = {
|
|
|
195137
195232
|
request: (value2) => ({
|
|
195138
195233
|
url: "https://api.anthropic.com/v1/models",
|
|
195139
195234
|
headers: { "x-api-key": value2, "anthropic-version": "2023-06-01" }
|
|
195140
|
-
})
|
|
195235
|
+
}),
|
|
195236
|
+
hostConfigurable: true
|
|
195141
195237
|
},
|
|
195142
195238
|
OPENROUTER_API_KEY: {
|
|
195143
195239
|
request: (value2) => ({
|
|
@@ -195186,12 +195282,18 @@ function hasEnvVar4(name) {
|
|
|
195186
195282
|
const value2 = process.env[name];
|
|
195187
195283
|
return typeof value2 === "string" && value2.length > 0;
|
|
195188
195284
|
}
|
|
195285
|
+
var HOST_OVERRIDES = {
|
|
195286
|
+
ANTHROPIC_API_KEY: "ANTHROPIC_BASE_URL",
|
|
195287
|
+
CLAUDE_CODE_OAUTH_TOKEN: "ANTHROPIC_BASE_URL"
|
|
195288
|
+
};
|
|
195189
195289
|
function envVarsFor(model) {
|
|
195190
195290
|
return model.includes("/") ? getModelEnvVars(model) : [];
|
|
195191
195291
|
}
|
|
195192
195292
|
async function checkOne(params) {
|
|
195193
195293
|
const value2 = process.env[params.envVar];
|
|
195194
195294
|
if (!value2) return null;
|
|
195295
|
+
const hostOverride = HOST_OVERRIDES[params.envVar];
|
|
195296
|
+
if (hostOverride && hasEnvVar4(hostOverride)) return null;
|
|
195195
195297
|
if (params.envVar === "CLAUDE_CODE_OAUTH_TOKEN") {
|
|
195196
195298
|
const preflight = await preflightClaudeSubscription({
|
|
195197
195299
|
token: value2,
|
|
@@ -196133,7 +196235,10 @@ var JsonPayload = type({
|
|
|
196133
196235
|
// optional so a payload from an older server build (pre-`checkRun`) still parses
|
|
196134
196236
|
// against a newer action across a rolling deploy.
|
|
196135
196237
|
"checkRun?": type({ id: "string" }).or("undefined"),
|
|
196136
|
-
"generateSummary?": "boolean | undefined"
|
|
196238
|
+
"generateSummary?": "boolean | undefined",
|
|
196239
|
+
// optional so a payload from a pre-canary server build still parses against a
|
|
196240
|
+
// newer action across a rolling deploy.
|
|
196241
|
+
"codexArm?": "boolean | undefined"
|
|
196137
196242
|
});
|
|
196138
196243
|
var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
|
|
196139
196244
|
function isCollaborator(event) {
|
|
@@ -196262,6 +196367,7 @@ function resolvePayload(resolvedPromptInput, repoSettings) {
|
|
|
196262
196367
|
progressComment: jsonPayload?.progressComment,
|
|
196263
196368
|
checkRun: jsonPayload?.checkRun,
|
|
196264
196369
|
generateSummary: jsonPayload?.generateSummary,
|
|
196370
|
+
codexArm: jsonPayload?.codexArm,
|
|
196265
196371
|
// permissions: inputs > repoSettings > fallbacks
|
|
196266
196372
|
push: inputs.push ?? repoSettings.push ?? "restricted",
|
|
196267
196373
|
shell: resolvedShell,
|
|
@@ -198206,8 +198312,12 @@ async function main() {
|
|
|
198206
198312
|
const agent2 = resolveAgent({
|
|
198207
198313
|
model: resolvedModel,
|
|
198208
198314
|
proxyModel: payload.proxyModel,
|
|
198209
|
-
|
|
198315
|
+
// the account opt-in and the canary arm are both admissions to codex, so
|
|
198316
|
+
// they OR: an account that opted in explicitly always gets it, and the
|
|
198317
|
+
// canary widens the pool without ever demoting a run that already had it.
|
|
198318
|
+
codexAgent: runContext.repoSettings.codexAgent || payload.codexArm === true
|
|
198210
198319
|
});
|
|
198320
|
+
toolState.agent = agent2.name;
|
|
198211
198321
|
const effectiveModel = payload.proxyModel ?? resolvedModel ?? payload.model;
|
|
198212
198322
|
toolState.model = effectiveModel;
|
|
198213
198323
|
if (!payload.proxyModel) {
|
|
@@ -198424,16 +198534,14 @@ ${instructions.user}` : null,
|
|
|
198424
198534
|
const onInnerActivityTimeout = () => {
|
|
198425
198535
|
if (innerTimeoutFired) return;
|
|
198426
198536
|
innerTimeoutFired = true;
|
|
198427
|
-
log2.info(
|
|
198428
|
-
"\xBB inner activity timeout fired \u2014 stopping MCP server and starting 5min safety-net timer"
|
|
198429
|
-
);
|
|
198430
|
-
mcpHttpServer[Symbol.asyncDispose]().catch((err) => {
|
|
198431
|
-
log2.debug(
|
|
198432
|
-
`mcp server stop after inner kill failed: ${err instanceof Error ? err.message : String(err)}`
|
|
198433
|
-
);
|
|
198434
|
-
});
|
|
198537
|
+
log2.info("\xBB inner activity timeout fired \u2014 starting 5min safety-net timer");
|
|
198435
198538
|
safetyNetTimer = setTimeout(
|
|
198436
198539
|
() => {
|
|
198540
|
+
mcpHttpServer[Symbol.asyncDispose]().catch((err) => {
|
|
198541
|
+
log2.debug(
|
|
198542
|
+
`mcp server stop after inner kill failed: ${err instanceof Error ? err.message : String(err)}`
|
|
198543
|
+
);
|
|
198544
|
+
});
|
|
198437
198545
|
activityTimeout?.forceReject(
|
|
198438
198546
|
"agent still pending 5min after inner activity kill \u2014 forcing exit"
|
|
198439
198547
|
);
|
|
@@ -198442,6 +198550,13 @@ ${instructions.user}` : null,
|
|
|
198442
198550
|
);
|
|
198443
198551
|
safetyNetTimer.unref?.();
|
|
198444
198552
|
};
|
|
198553
|
+
const onTurnRecovered = () => {
|
|
198554
|
+
if (!innerTimeoutFired) return;
|
|
198555
|
+
innerTimeoutFired = false;
|
|
198556
|
+
if (safetyNetTimer) clearTimeout(safetyNetTimer);
|
|
198557
|
+
safetyNetTimer = void 0;
|
|
198558
|
+
log2.info("\xBB inner activity safety net stood down \u2014 turn recovered");
|
|
198559
|
+
};
|
|
198445
198560
|
const agentPromise = agent2.run({
|
|
198446
198561
|
payload,
|
|
198447
198562
|
resolvedModel,
|
|
@@ -198462,6 +198577,7 @@ ${instructions.user}` : null,
|
|
|
198462
198577
|
toolState,
|
|
198463
198578
|
apiToken: runContext.apiToken,
|
|
198464
198579
|
onActivityTimeout: onInnerActivityTimeout,
|
|
198580
|
+
onTurnRecovered,
|
|
198465
198581
|
onToolUse: (event) => {
|
|
198466
198582
|
const wasTracked = recordDiffReadFromToolUse({
|
|
198467
198583
|
state: primaryRepoState(toolState).diffCoverage,
|
|
@@ -198559,6 +198675,8 @@ ${instructions.user}` : null,
|
|
|
198559
198675
|
if (toolContext) {
|
|
198560
198676
|
const patch = aggregateUsage(toolState.usageEntries);
|
|
198561
198677
|
if (toolState.model) patch.model = toolState.model;
|
|
198678
|
+
if (toolState.agent) patch.agent = toolState.agent;
|
|
198679
|
+
if (toolState.credential) patch.credential = toolState.credential;
|
|
198562
198680
|
if (Object.keys(patch).length > 0) {
|
|
198563
198681
|
await patchWorkflowRunFields(toolContext, patch);
|
|
198564
198682
|
}
|
|
@@ -198735,34 +198853,6 @@ var PULLFROG_API_URL2 = (process.env.PULLFROG_API_URL || "https://pullfrog.com")
|
|
|
198735
198853
|
function link(text, url4) {
|
|
198736
198854
|
return `\x1B]8;;${url4}\x07${text}\x1B]8;;\x07`;
|
|
198737
198855
|
}
|
|
198738
|
-
function buildProviders() {
|
|
198739
|
-
return Object.entries(providers).filter(([key]) => key !== "opencode" && key !== "openrouter").map(([key, config3]) => {
|
|
198740
|
-
const aliases = modelAliases.filter(
|
|
198741
|
-
(a2) => a2.provider === key && !a2.fallback && !a2.routing && !a2.hidden
|
|
198742
|
-
);
|
|
198743
|
-
const recommended = aliases.find((a2) => a2.preferred);
|
|
198744
|
-
const sorted = [...aliases].sort((a2, b) => {
|
|
198745
|
-
if (a2.preferred && !b.preferred) return -1;
|
|
198746
|
-
if (!a2.preferred && b.preferred) return 1;
|
|
198747
|
-
return 0;
|
|
198748
|
-
});
|
|
198749
|
-
return {
|
|
198750
|
-
id: key,
|
|
198751
|
-
name: config3.displayName,
|
|
198752
|
-
envVars: config3.envVars,
|
|
198753
|
-
models: sorted.map((a2) => ({
|
|
198754
|
-
value: a2.slug,
|
|
198755
|
-
label: a2.displayName,
|
|
198756
|
-
hint: a2 === recommended ? "recommended" : void 0
|
|
198757
|
-
}))
|
|
198758
|
-
};
|
|
198759
|
-
}).filter((p) => p.models.length > 0);
|
|
198760
|
-
}
|
|
198761
|
-
var CLI_PROVIDERS = buildProviders();
|
|
198762
|
-
function resolveModelProvider(slug2) {
|
|
198763
|
-
const providerId = slug2.split("/")[0];
|
|
198764
|
-
return CLI_PROVIDERS.find((p) => p.id === providerId) ?? null;
|
|
198765
|
-
}
|
|
198766
198856
|
var activeSpin2 = null;
|
|
198767
198857
|
function bail2(msg) {
|
|
198768
198858
|
if (activeSpin2) {
|
|
@@ -198847,15 +198937,11 @@ function openBrowser(url4) {
|
|
|
198847
198937
|
}
|
|
198848
198938
|
}
|
|
198849
198939
|
async function pullfrogApi2(ctx) {
|
|
198850
|
-
const headers = { authorization: `Bearer ${ctx.token}` };
|
|
198851
|
-
if (ctx.body) headers["content-type"] = "application/json";
|
|
198852
198940
|
const controller = new AbortController();
|
|
198853
198941
|
const timeout = setTimeout(() => controller.abort(), 3e4);
|
|
198854
198942
|
try {
|
|
198855
198943
|
const response = await fetch(`${PULLFROG_API_URL2}${ctx.path}`, {
|
|
198856
|
-
|
|
198857
|
-
headers,
|
|
198858
|
-
body: ctx.body ? JSON.stringify(ctx.body) : null,
|
|
198944
|
+
headers: { authorization: `Bearer ${ctx.token}` },
|
|
198859
198945
|
signal: controller.signal
|
|
198860
198946
|
});
|
|
198861
198947
|
const data = await response.json().catch(() => ({}));
|
|
@@ -198883,406 +198969,27 @@ async function fetchStatus2(ctx) {
|
|
|
198883
198969
|
isOrg: result.data.isOrg === true
|
|
198884
198970
|
};
|
|
198885
198971
|
}
|
|
198886
|
-
bail2(errorMsg || `
|
|
198972
|
+
bail2(errorMsg || `installation check failed (${result.status})`);
|
|
198887
198973
|
}
|
|
198888
198974
|
return {
|
|
198889
198975
|
installed: true,
|
|
198890
198976
|
isOrg: result.data.isOrg === true,
|
|
198891
|
-
installationId: typeof result.data.installationId === "number" ? result.data.installationId : null
|
|
198892
|
-
secretsAccessible: result.data.accessible !== false,
|
|
198893
|
-
repoSecrets: result.data.repoSecrets || [],
|
|
198894
|
-
orgSecrets: result.data.orgSecrets || [],
|
|
198895
|
-
pullfrogSecrets: result.data.pullfrogSecrets || [],
|
|
198896
|
-
model: result.data.repoModel ?? null,
|
|
198897
|
-
hasRuns: result.data.hasRuns === true
|
|
198898
|
-
};
|
|
198899
|
-
}
|
|
198900
|
-
async function createSession(ctx) {
|
|
198901
|
-
try {
|
|
198902
|
-
const result = await pullfrogApi2({
|
|
198903
|
-
path: "/api/cli/session",
|
|
198904
|
-
token: ctx.token,
|
|
198905
|
-
method: "POST",
|
|
198906
|
-
body: { owner: ctx.owner.toLowerCase(), repo: ctx.repo.toLowerCase() }
|
|
198907
|
-
});
|
|
198908
|
-
if (!result.ok || !result.data.id) return null;
|
|
198909
|
-
return result.data.id;
|
|
198910
|
-
} catch {
|
|
198911
|
-
return null;
|
|
198912
|
-
}
|
|
198913
|
-
}
|
|
198914
|
-
async function pollSession(ctx) {
|
|
198915
|
-
const result = await pullfrogApi2({
|
|
198916
|
-
path: `/api/cli/session/${ctx.sessionId}`,
|
|
198917
|
-
token: ctx.token
|
|
198918
|
-
});
|
|
198919
|
-
if (result.status === 410) return "expired";
|
|
198920
|
-
if (!result.ok) return "pending";
|
|
198921
|
-
return result.data.installed === true ? "installed" : "pending";
|
|
198922
|
-
}
|
|
198923
|
-
function cleanupSession(ctx) {
|
|
198924
|
-
void pullfrogApi2({
|
|
198925
|
-
path: `/api/cli/session/${ctx.sessionId}`,
|
|
198926
|
-
token: ctx.token,
|
|
198927
|
-
method: "DELETE"
|
|
198928
|
-
}).catch(() => {
|
|
198929
|
-
});
|
|
198930
|
-
}
|
|
198931
|
-
var SESSION_POLL_MS = 750;
|
|
198932
|
-
var FALLBACK_POLL_MS = 5e3;
|
|
198933
|
-
var HINT_AFTER_MS = 1e4;
|
|
198934
|
-
var TIMEOUT_MS = 3 * 60 * 1e3;
|
|
198935
|
-
function listenForKey(key) {
|
|
198936
|
-
let triggered = false;
|
|
198937
|
-
const onData = (data) => {
|
|
198938
|
-
if (data.toString().toLowerCase() === key) triggered = true;
|
|
198939
|
-
};
|
|
198940
|
-
process.stdin.setRawMode?.(true);
|
|
198941
|
-
process.stdin.resume();
|
|
198942
|
-
process.stdin.on("data", onData);
|
|
198943
|
-
return {
|
|
198944
|
-
consume() {
|
|
198945
|
-
if (!triggered) return false;
|
|
198946
|
-
triggered = false;
|
|
198947
|
-
return true;
|
|
198948
|
-
},
|
|
198949
|
-
stop() {
|
|
198950
|
-
process.stdin.removeListener("data", onData);
|
|
198951
|
-
process.stdin.setRawMode?.(false);
|
|
198952
|
-
process.stdin.pause();
|
|
198953
|
-
}
|
|
198977
|
+
installationId: typeof result.data.installationId === "number" ? result.data.installationId : null
|
|
198954
198978
|
};
|
|
198955
198979
|
}
|
|
198956
198980
|
function installationConfigUrl(ctx) {
|
|
198957
198981
|
return ctx.isOrg ? `https://github.com/organizations/${ctx.owner}/settings/installations/${ctx.installationId}` : `https://github.com/settings/installations/${ctx.installationId}`;
|
|
198958
198982
|
}
|
|
198959
|
-
|
|
198960
|
-
|
|
198961
|
-
const initial = await fetchStatus2(ctx);
|
|
198962
|
-
if (initial.installed) {
|
|
198963
|
-
activeSpin2.stop(`pullfrog app is installed on ${import_picocolors3.default.cyan(`@${ctx.owner}`)}`);
|
|
198964
|
-
if (initial.installationId) {
|
|
198965
|
-
const configUrl = installationConfigUrl({
|
|
198966
|
-
owner: ctx.owner,
|
|
198967
|
-
installationId: initial.installationId,
|
|
198968
|
-
isOrg: initial.isOrg
|
|
198969
|
-
});
|
|
198970
|
-
process.stdout.write(`${import_picocolors3.default.gray(S_BAR)} ${link(import_picocolors3.default.dim(configUrl), configUrl)}
|
|
198971
|
-
`);
|
|
198972
|
-
}
|
|
198973
|
-
return initial;
|
|
198974
|
-
}
|
|
198975
|
-
const sessionId = await createSession(ctx);
|
|
198976
|
-
if (initial.installationId) {
|
|
198977
|
-
const repoRef = import_picocolors3.default.bold(`${ctx.owner}/${ctx.repo}`);
|
|
198978
|
-
const configUrl = installationConfigUrl({
|
|
198979
|
-
owner: ctx.owner,
|
|
198980
|
-
installationId: initial.installationId,
|
|
198981
|
-
isOrg: initial.isOrg
|
|
198982
|
-
});
|
|
198983
|
-
activeSpin2.stop(`pullfrog is installed on selected repos, but ${repoRef} is not included.`);
|
|
198984
|
-
log.info(
|
|
198985
|
-
`add it under "Repository access" on the installation config page.
|
|
198986
|
-
${import_picocolors3.default.dim(configUrl)}`
|
|
198987
|
-
);
|
|
198988
|
-
const openIt = await confirm({ message: "open browser?", active: "yes", inactive: "no" });
|
|
198989
|
-
handleCancel2(openIt);
|
|
198990
|
-
if (openIt) openBrowser(configUrl);
|
|
198991
|
-
} else {
|
|
198992
|
-
activeSpin2.stop("pullfrog app not installed");
|
|
198993
|
-
const installUrl = `https://github.com/apps/${initial.appSlug}/installations/select_target?state=cli`;
|
|
198994
|
-
log.info(`opening browser to install...
|
|
198995
|
-
${import_picocolors3.default.dim(installUrl)}`);
|
|
198996
|
-
openBrowser(installUrl);
|
|
198997
|
-
}
|
|
198998
|
-
const isRepoAccessUpdate = !!initial.installationId;
|
|
198999
|
-
const baseMsg = isRepoAccessUpdate ? "once you've added the repo, onboarding will proceed automatically" : "once you've installed the app, onboarding will proceed automatically";
|
|
199000
|
-
activeSpin2.start(baseMsg);
|
|
199001
|
-
let activeSessionId = sessionId;
|
|
199002
|
-
let pollMs = activeSessionId ? SESSION_POLL_MS : FALLBACK_POLL_MS;
|
|
199003
|
-
const listener = listenForKey("r");
|
|
199004
|
-
const startedAt = Date.now();
|
|
199005
|
-
let hintShown = false;
|
|
199006
|
-
try {
|
|
199007
|
-
while (Date.now() - startedAt < TIMEOUT_MS) {
|
|
199008
|
-
await new Promise((r2) => setTimeout(r2, pollMs));
|
|
199009
|
-
if (!hintShown && Date.now() - startedAt > HINT_AFTER_MS) {
|
|
199010
|
-
activeSpin2.message(`${baseMsg} ${import_picocolors3.default.dim("(press r to recheck manually)")}`);
|
|
199011
|
-
hintShown = true;
|
|
199012
|
-
}
|
|
199013
|
-
const doneMsg = isRepoAccessUpdate ? "repo access confirmed" : "pullfrog app installed";
|
|
199014
|
-
if (listener.consume()) {
|
|
199015
|
-
activeSpin2.message("rechecking via GitHub API");
|
|
199016
|
-
try {
|
|
199017
|
-
const status = await fetchStatus2(ctx);
|
|
199018
|
-
if (status.installed) {
|
|
199019
|
-
if (activeSessionId) cleanupSession({ token: ctx.token, sessionId: activeSessionId });
|
|
199020
|
-
activeSpin2.stop(doneMsg);
|
|
199021
|
-
return status;
|
|
199022
|
-
}
|
|
199023
|
-
} catch {
|
|
199024
|
-
}
|
|
199025
|
-
activeSpin2.message(`${baseMsg} ${import_picocolors3.default.dim("(press r to recheck manually)")}`);
|
|
199026
|
-
continue;
|
|
199027
|
-
}
|
|
199028
|
-
if (activeSessionId) {
|
|
199029
|
-
try {
|
|
199030
|
-
const result = await pollSession({ token: ctx.token, sessionId: activeSessionId });
|
|
199031
|
-
if (result === "expired") {
|
|
199032
|
-
activeSessionId = null;
|
|
199033
|
-
pollMs = FALLBACK_POLL_MS;
|
|
199034
|
-
continue;
|
|
199035
|
-
}
|
|
199036
|
-
if (result === "installed") {
|
|
199037
|
-
const status = await fetchStatus2(ctx);
|
|
199038
|
-
if (status.installed) {
|
|
199039
|
-
cleanupSession({ token: ctx.token, sessionId: activeSessionId });
|
|
199040
|
-
activeSpin2.stop(doneMsg);
|
|
199041
|
-
return status;
|
|
199042
|
-
}
|
|
199043
|
-
}
|
|
199044
|
-
} catch {
|
|
199045
|
-
}
|
|
199046
|
-
} else {
|
|
199047
|
-
try {
|
|
199048
|
-
const status = await fetchStatus2(ctx);
|
|
199049
|
-
if (status.installed) {
|
|
199050
|
-
activeSpin2.stop(doneMsg);
|
|
199051
|
-
return status;
|
|
199052
|
-
}
|
|
199053
|
-
} catch {
|
|
199054
|
-
}
|
|
199055
|
-
}
|
|
199056
|
-
}
|
|
199057
|
-
} finally {
|
|
199058
|
-
listener.stop();
|
|
199059
|
-
}
|
|
199060
|
-
if (activeSessionId) cleanupSession({ token: ctx.token, sessionId: activeSessionId });
|
|
199061
|
-
bail2(
|
|
199062
|
-
isRepoAccessUpdate ? `timed out waiting for repo access.
|
|
199063
|
-
${import_picocolors3.default.dim("add the repo, then re-run:")} npx pullfrog init` : `timed out waiting for app installation.
|
|
199064
|
-
${import_picocolors3.default.dim("if your org requires admin approval, ask an admin to approve,")}
|
|
199065
|
-
${import_picocolors3.default.dim("then re-run:")} npx pullfrog init`
|
|
199066
|
-
);
|
|
198983
|
+
function consoleUrl(ctx) {
|
|
198984
|
+
return `${PULLFROG_API_URL2}/console/${ctx.owner}?repo=${encodeURIComponent(ctx.repo)}`;
|
|
199067
198985
|
}
|
|
199068
|
-
function
|
|
199069
|
-
|
|
199070
|
-
|
|
199071
|
-
try {
|
|
199072
|
-
execFileSync8("gh", ["secret", "set", ctx.name, "--org", ctx.org, "--visibility", "all"], {
|
|
199073
|
-
input: ctx.value,
|
|
199074
|
-
stdio: ["pipe", "ignore", "pipe"],
|
|
199075
|
-
encoding: "utf-8"
|
|
199076
|
-
});
|
|
199077
|
-
return { saved: true, orgFailed: false };
|
|
199078
|
-
} catch {
|
|
199079
|
-
orgFailed = true;
|
|
199080
|
-
}
|
|
199081
|
-
}
|
|
199082
|
-
try {
|
|
199083
|
-
execFileSync8("gh", ["secret", "set", ctx.name, "--repo", ctx.repoSlug], {
|
|
199084
|
-
input: ctx.value,
|
|
199085
|
-
stdio: ["pipe", "ignore", "pipe"],
|
|
199086
|
-
encoding: "utf-8"
|
|
199087
|
-
});
|
|
199088
|
-
return { saved: true, orgFailed };
|
|
199089
|
-
} catch {
|
|
199090
|
-
return { saved: false, orgFailed };
|
|
199091
|
-
}
|
|
199092
|
-
}
|
|
199093
|
-
async function setPullfrogSecret2(ctx) {
|
|
199094
|
-
const result = await pullfrogApi2({
|
|
199095
|
-
path: "/api/cli/secrets",
|
|
199096
|
-
token: ctx.token,
|
|
199097
|
-
method: "POST",
|
|
199098
|
-
body: {
|
|
199099
|
-
owner: ctx.owner,
|
|
199100
|
-
repo: ctx.repo,
|
|
199101
|
-
name: ctx.name,
|
|
199102
|
-
value: ctx.value,
|
|
199103
|
-
scope: ctx.scope
|
|
199104
|
-
}
|
|
199105
|
-
});
|
|
199106
|
-
if (result.ok && result.data.success === true) {
|
|
199107
|
-
return { saved: true, error: "" };
|
|
199108
|
-
}
|
|
199109
|
-
return { saved: false, error: result.data.error || `api returned ${result.status}` };
|
|
199110
|
-
}
|
|
199111
|
-
async function promptScope2(ctx) {
|
|
199112
|
-
const scope2 = await select({
|
|
199113
|
-
message: "secret scope",
|
|
199114
|
-
options: [
|
|
199115
|
-
{ value: "account", label: `${ctx.owner} organization`, hint: "shared across repos" },
|
|
199116
|
-
{ value: "repo", label: `${ctx.owner}/${ctx.repo} only` }
|
|
199117
|
-
]
|
|
199118
|
-
});
|
|
199119
|
-
handleCancel2(scope2);
|
|
199120
|
-
return scope2;
|
|
199121
|
-
}
|
|
199122
|
-
async function handleSecret(ctx) {
|
|
199123
|
-
const repoSecretsUrl = `https://github.com/${ctx.owner}/${ctx.repo}/settings/secrets/actions`;
|
|
199124
|
-
const matches = [];
|
|
199125
|
-
for (const v of ctx.provider.envVars) {
|
|
199126
|
-
if (ctx.secrets.pullfrogSecrets.includes(v)) matches.push({ name: v, source: "pullfrog" });
|
|
199127
|
-
else if (ctx.secrets.secretsAccessible && ctx.secrets.orgSecrets.includes(v))
|
|
199128
|
-
matches.push({ name: v, source: "org secret" });
|
|
199129
|
-
else if (ctx.secrets.secretsAccessible && ctx.secrets.repoSecrets.includes(v))
|
|
199130
|
-
matches.push({ name: v, source: "repo secret" });
|
|
199131
|
-
}
|
|
199132
|
-
if (matches.length > 0) {
|
|
199133
|
-
activeSpin2.start("");
|
|
199134
|
-
activeSpin2.stop("secrets already configured");
|
|
199135
|
-
for (const m of matches) {
|
|
199136
|
-
process.stdout.write(
|
|
199137
|
-
`${import_picocolors3.default.gray(S_BAR)} ${import_picocolors3.default.cyan(m.name)} ${import_picocolors3.default.dim(`(${m.source})`)}
|
|
199138
|
-
`
|
|
199139
|
-
);
|
|
199140
|
-
}
|
|
199141
|
-
return;
|
|
199142
|
-
}
|
|
199143
|
-
if (!ctx.secrets.secretsAccessible) {
|
|
199144
|
-
log.info(`could not verify GitHub secrets (app lacks permission)`);
|
|
199145
|
-
}
|
|
199146
|
-
const hasOAuthOption = ctx.provider.envVars.includes("CLAUDE_CODE_OAUTH_TOKEN");
|
|
199147
|
-
let envVar = ctx.provider.envVars[0];
|
|
199148
|
-
if (hasOAuthOption) {
|
|
199149
|
-
const authMethod = await select({
|
|
199150
|
-
message: "which credential do you want to use?",
|
|
199151
|
-
options: [
|
|
199152
|
-
{
|
|
199153
|
-
value: "oauth",
|
|
199154
|
-
label: "Claude Code OAuth token",
|
|
199155
|
-
hint: `run ${import_picocolors3.default.cyan("claude setup-token")} \u2014 works with Pro/Max subscriptions`
|
|
199156
|
-
},
|
|
199157
|
-
{
|
|
199158
|
-
value: "api",
|
|
199159
|
-
label: "Anthropic API key",
|
|
199160
|
-
hint: "from console.anthropic.com"
|
|
199161
|
-
}
|
|
199162
|
-
]
|
|
199163
|
-
});
|
|
199164
|
-
handleCancel2(authMethod);
|
|
199165
|
-
if (authMethod === "oauth") envVar = "CLAUDE_CODE_OAUTH_TOKEN";
|
|
199166
|
-
}
|
|
199167
|
-
const method = await select({
|
|
199168
|
-
message: `where should ${import_picocolors3.default.cyan(envVar)} be stored?`,
|
|
199169
|
-
options: [
|
|
199170
|
-
{
|
|
199171
|
-
value: "pullfrog",
|
|
199172
|
-
label: "Pullfrog",
|
|
199173
|
-
hint: "recommended \u2014 auto-injected, no workflow changes"
|
|
199174
|
-
},
|
|
199175
|
-
{
|
|
199176
|
-
value: "github",
|
|
199177
|
-
label: "GitHub Actions secret",
|
|
199178
|
-
hint: "requires env block in pullfrog.yml"
|
|
199179
|
-
}
|
|
199180
|
-
]
|
|
199181
|
-
});
|
|
199182
|
-
handleCancel2(method);
|
|
199183
|
-
const pasteLabel = envVar === "CLAUDE_CODE_OAUTH_TOKEN" ? "OAuth token" : `${ctx.provider.name} API key`;
|
|
199184
|
-
const apiKey = await password({
|
|
199185
|
-
message: `paste your ${pasteLabel} ${import_picocolors3.default.dim("(Enter to skip)")}`,
|
|
199186
|
-
mask: "*",
|
|
199187
|
-
validate: () => void 0
|
|
199188
|
-
});
|
|
199189
|
-
handleCancel2(apiKey);
|
|
199190
|
-
if (!apiKey) {
|
|
199191
|
-
log.info(
|
|
199192
|
-
`skipped \u2014 set it manually at:
|
|
199193
|
-
${import_picocolors3.default.dim(method === "pullfrog" ? `${PULLFROG_API_URL2}/console/${ctx.owner}` : repoSecretsUrl)}`
|
|
199194
|
-
);
|
|
199195
|
-
return;
|
|
199196
|
-
}
|
|
199197
|
-
if (method === "pullfrog") {
|
|
199198
|
-
const scope2 = ctx.secrets.isOrg ? await promptScope2(ctx) : "account";
|
|
199199
|
-
const target = describeSecretTarget({ owner: ctx.owner, repo: ctx.repo, scope: scope2 });
|
|
199200
|
-
activeSpin2.start(`saving ${import_picocolors3.default.cyan(envVar)} to ${target}`);
|
|
199201
|
-
let saveResult;
|
|
199202
|
-
try {
|
|
199203
|
-
saveResult = await setPullfrogSecret2({
|
|
199204
|
-
token: ctx.token,
|
|
199205
|
-
owner: ctx.owner,
|
|
199206
|
-
repo: ctx.repo,
|
|
199207
|
-
name: envVar,
|
|
199208
|
-
value: apiKey,
|
|
199209
|
-
scope: scope2
|
|
199210
|
-
});
|
|
199211
|
-
} catch (error52) {
|
|
199212
|
-
activeSpin2.stop(import_picocolors3.default.red("could not save secret"));
|
|
199213
|
-
log.warn(
|
|
199214
|
-
`${error52 instanceof Error ? error52.message : "network error"}
|
|
199215
|
-
set it manually at: ${import_picocolors3.default.dim(`${PULLFROG_API_URL2}/console/${ctx.owner}`)}`
|
|
199216
|
-
);
|
|
199217
|
-
return;
|
|
199218
|
-
}
|
|
199219
|
-
if (saveResult.saved) {
|
|
199220
|
-
activeSpin2.stop(`saved ${import_picocolors3.default.cyan(envVar)} to ${target}`);
|
|
199221
|
-
} else {
|
|
199222
|
-
activeSpin2.stop(import_picocolors3.default.red("could not save secret"));
|
|
199223
|
-
log.warn(
|
|
199224
|
-
`${saveResult.error}
|
|
199225
|
-
set it manually at: ${import_picocolors3.default.dim(`${PULLFROG_API_URL2}/console/${ctx.owner}`)}`
|
|
199226
|
-
);
|
|
199227
|
-
}
|
|
199228
|
-
return;
|
|
199229
|
-
}
|
|
199230
|
-
let org = null;
|
|
199231
|
-
if (ctx.secrets.isOrg) {
|
|
199232
|
-
const scope2 = await promptScope2(ctx);
|
|
199233
|
-
org = scope2 === "account" ? ctx.owner : null;
|
|
199234
|
-
}
|
|
199235
|
-
const secretsUrl = org ? `https://github.com/organizations/${org}/settings/secrets/actions` : repoSecretsUrl;
|
|
199236
|
-
activeSpin2.start(`saving ${envVar}`);
|
|
199237
|
-
const secretResult = setGhSecret({
|
|
199238
|
-
name: envVar,
|
|
199239
|
-
value: apiKey,
|
|
199240
|
-
org,
|
|
199241
|
-
repoSlug: `${ctx.owner}/${ctx.repo}`
|
|
199242
|
-
});
|
|
199243
|
-
if (secretResult.saved) {
|
|
199244
|
-
activeSpin2.stop(
|
|
199245
|
-
`saved ${import_picocolors3.default.cyan(envVar)} to ${org && !secretResult.orgFailed ? `${import_picocolors3.default.dim(ctx.owner)} org secret` : "GitHub Actions secret"}`
|
|
199246
|
-
);
|
|
199247
|
-
if (secretResult.orgFailed) {
|
|
199248
|
-
log.warn("org secret failed (admin access required) \u2014 saved as repo secret instead");
|
|
199249
|
-
}
|
|
199250
|
-
} else {
|
|
199251
|
-
activeSpin2.stop(import_picocolors3.default.red("could not set secret"));
|
|
199252
|
-
log.warn(`set it manually at:
|
|
199253
|
-
${import_picocolors3.default.dim(secretsUrl)}`);
|
|
199254
|
-
}
|
|
198986
|
+
function installUrl(ctx) {
|
|
198987
|
+
const state = encodeURIComponent(`cli:${ctx.owner}/${ctx.repo}`);
|
|
198988
|
+
return `https://github.com/apps/${ctx.appSlug}/installations/select_target?state=${state}`;
|
|
199255
198989
|
}
|
|
199256
|
-
|
|
199257
|
-
|
|
199258
|
-
|
|
199259
|
-
options: [
|
|
199260
|
-
{ value: true, label: "yes", hint: "dispatches a test run in your GitHub Actions" },
|
|
199261
|
-
{ value: false, label: "skip" }
|
|
199262
|
-
]
|
|
199263
|
-
});
|
|
199264
|
-
handleCancel2(proceed);
|
|
199265
|
-
if (!proceed) return;
|
|
199266
|
-
activeSpin2.start("dispatching test run");
|
|
199267
|
-
const result = await pullfrogApi2({
|
|
199268
|
-
path: "/api/cli/dispatch",
|
|
199269
|
-
token: ctx.token,
|
|
199270
|
-
method: "POST",
|
|
199271
|
-
body: { owner: ctx.owner, repo: ctx.repo, prompt: "Tell me a joke" }
|
|
199272
|
-
});
|
|
199273
|
-
if (!result.ok) {
|
|
199274
|
-
activeSpin2.stop(import_picocolors3.default.red("could not dispatch"));
|
|
199275
|
-
log.warn(result.data.error || `dispatch failed (${result.status})`);
|
|
199276
|
-
return;
|
|
199277
|
-
}
|
|
199278
|
-
activeSpin2.stop("dispatched test run");
|
|
199279
|
-
if (result.data.url) {
|
|
199280
|
-
process.stdout.write(
|
|
199281
|
-
`${import_picocolors3.default.gray(S_BAR)} ${link(import_picocolors3.default.dim(result.data.url), result.data.url)}
|
|
199282
|
-
`
|
|
199283
|
-
);
|
|
199284
|
-
openBrowser(result.data.url);
|
|
199285
|
-
}
|
|
198990
|
+
function printLink(url4) {
|
|
198991
|
+
process.stdout.write(`${import_picocolors3.default.gray(S_BAR)} ${link(import_picocolors3.default.dim(url4), url4)}
|
|
198992
|
+
`);
|
|
199286
198993
|
}
|
|
199287
198994
|
async function main2() {
|
|
199288
198995
|
intro(import_picocolors3.default.bgGreen(import_picocolors3.default.black(" pullfrog ")));
|
|
@@ -199304,98 +199011,58 @@ async function main2() {
|
|
|
199304
199011
|
spin.start("detecting repository");
|
|
199305
199012
|
const remote = parseGitRemote2();
|
|
199306
199013
|
spin.stop(`detected repo ${import_picocolors3.default.cyan(`${remote.owner}/${remote.repo}`)}`);
|
|
199307
|
-
|
|
199308
|
-
|
|
199309
|
-
|
|
199310
|
-
if (
|
|
199311
|
-
|
|
199312
|
-
const resolved = resolveModelProvider(secrets.model);
|
|
199313
|
-
if (!resolved) bail2(`unknown model provider: ${secrets.model}`);
|
|
199314
|
-
provider2 = resolved;
|
|
199315
|
-
const displayAlias = resolveDisplayAlias(secrets.model);
|
|
199316
|
-
const label = displayAlias ? displayAlias.displayName : secrets.model;
|
|
199014
|
+
spin.start("checking pullfrog app installation");
|
|
199015
|
+
const status = await fetchStatus2({ token, owner: remote.owner, repo: remote.repo });
|
|
199016
|
+
const handoff = consoleUrl({ owner: remote.owner, repo: remote.repo });
|
|
199017
|
+
if (status.installed) {
|
|
199018
|
+
spin.stop(`pullfrog app is installed on ${import_picocolors3.default.cyan(`@${remote.owner}`)}`);
|
|
199317
199019
|
spin.start("");
|
|
199318
|
-
spin.stop(
|
|
199319
|
-
|
|
199320
|
-
|
|
199321
|
-
|
|
199322
|
-
|
|
199323
|
-
|
|
199324
|
-
label: cp.name
|
|
199325
|
-
}))
|
|
199326
|
-
});
|
|
199327
|
-
handleCancel2(providerId);
|
|
199328
|
-
const found = CLI_PROVIDERS.find((cp) => cp.id === providerId);
|
|
199329
|
-
if (!found) bail2(`unknown provider: ${providerId}`);
|
|
199330
|
-
provider2 = found;
|
|
199331
|
-
if (provider2.models.length === 1) {
|
|
199332
|
-
model = provider2.models[0].value;
|
|
199333
|
-
spin.start("");
|
|
199334
|
-
spin.stop(`using ${import_picocolors3.default.bold(provider2.models[0].label)}`);
|
|
199335
|
-
} else {
|
|
199336
|
-
const recommendedModel = provider2.models.find((m) => m.hint === "recommended");
|
|
199337
|
-
const options = provider2.models.map((m) => {
|
|
199338
|
-
if (m.hint) return { value: m.value, label: m.label, hint: m.hint };
|
|
199339
|
-
return { value: m.value, label: m.label };
|
|
199340
|
-
});
|
|
199341
|
-
const selected = await select(
|
|
199342
|
-
recommendedModel ? { message: "select model", initialValue: recommendedModel.value, options } : { message: "select model", options }
|
|
199343
|
-
);
|
|
199344
|
-
handleCancel2(selected);
|
|
199345
|
-
model = selected;
|
|
199346
|
-
}
|
|
199347
|
-
}
|
|
199348
|
-
await handleSecret({ token, owner: remote.owner, repo: remote.repo, provider: provider2, secrets });
|
|
199349
|
-
spin.start("creating pullfrog.yml workflow");
|
|
199350
|
-
const result = await pullfrogApi2({
|
|
199351
|
-
path: "/api/cli/setup",
|
|
199352
|
-
token,
|
|
199353
|
-
method: "POST",
|
|
199354
|
-
body: { owner: remote.owner, repo: remote.repo, model }
|
|
199355
|
-
});
|
|
199356
|
-
if (!result.ok) {
|
|
199357
|
-
bail2(result.data.error || `api returned ${result.status}`);
|
|
199358
|
-
}
|
|
199359
|
-
let skipTestRun = false;
|
|
199360
|
-
if (result.data.already_existed) {
|
|
199361
|
-
spin.stop("pullfrog.yml already exists");
|
|
199362
|
-
} else if (result.data.pull_request_url) {
|
|
199363
|
-
spin.stop("opened pull request with pullfrog.yml");
|
|
199364
|
-
process.stdout.write(
|
|
199365
|
-
`${import_picocolors3.default.gray(S_BAR)} ${link(import_picocolors3.default.dim(result.data.pull_request_url), result.data.pull_request_url)}
|
|
199366
|
-
`
|
|
199020
|
+
spin.stop("opening your dashboard");
|
|
199021
|
+
printLink(handoff);
|
|
199022
|
+
openBrowser(handoff);
|
|
199023
|
+
activeSpin2 = null;
|
|
199024
|
+
outro(
|
|
199025
|
+
`finish setup in your browser \u2014 ${import_picocolors3.default.cyan(`${remote.owner}/${remote.repo}`)} is ready.`
|
|
199367
199026
|
);
|
|
199368
|
-
|
|
199369
|
-
|
|
199370
|
-
|
|
199371
|
-
|
|
199372
|
-
|
|
199373
|
-
|
|
199374
|
-
|
|
199027
|
+
return;
|
|
199028
|
+
}
|
|
199029
|
+
if (status.installationId) {
|
|
199030
|
+
const repoRef = import_picocolors3.default.bold(`${remote.owner}/${remote.repo}`);
|
|
199031
|
+
const configUrl = installationConfigUrl({
|
|
199032
|
+
owner: remote.owner,
|
|
199033
|
+
installationId: status.installationId,
|
|
199034
|
+
isOrg: status.isOrg
|
|
199375
199035
|
});
|
|
199376
|
-
|
|
199377
|
-
|
|
199378
|
-
|
|
199379
|
-
|
|
199380
|
-
spin.stop(
|
|
199381
|
-
short ? `committed pullfrog.yml to repo ${import_picocolors3.default.dim(short)}` : "committed pullfrog.yml to repo"
|
|
199036
|
+
spin.stop(`pullfrog is installed on selected repos, but ${repoRef} is not included.`);
|
|
199037
|
+
log.info(
|
|
199038
|
+
`add it under "Repository access" on the installation config page.
|
|
199039
|
+
${import_picocolors3.default.dim(configUrl)}`
|
|
199382
199040
|
);
|
|
199041
|
+
const openIt = await confirm({ message: "open browser?", active: "yes", inactive: "no" });
|
|
199042
|
+
handleCancel2(openIt);
|
|
199043
|
+
if (openIt) openBrowser(configUrl);
|
|
199044
|
+
log.info("once the repo is added, finish setup at:");
|
|
199045
|
+
printLink(handoff);
|
|
199046
|
+
activeSpin2 = null;
|
|
199047
|
+
outro("done.");
|
|
199048
|
+
return;
|
|
199383
199049
|
}
|
|
199384
|
-
|
|
199385
|
-
|
|
199386
|
-
|
|
199387
|
-
|
|
199388
|
-
|
|
199389
|
-
|
|
199390
|
-
|
|
199391
|
-
|
|
199050
|
+
spin.stop("pullfrog app not installed");
|
|
199051
|
+
const install = installUrl({
|
|
199052
|
+
appSlug: status.appSlug,
|
|
199053
|
+
owner: remote.owner,
|
|
199054
|
+
repo: remote.repo
|
|
199055
|
+
});
|
|
199056
|
+
log.info("opening browser to install...");
|
|
199057
|
+
printLink(install);
|
|
199058
|
+
openBrowser(install);
|
|
199392
199059
|
activeSpin2 = null;
|
|
199393
|
-
outro("
|
|
199060
|
+
outro("GitHub will drop you at your dashboard to finish setup.");
|
|
199394
199061
|
}
|
|
199395
199062
|
function printInitUsage(params) {
|
|
199396
199063
|
params.stream(`usage: ${params.prog} init
|
|
199397
199064
|
`);
|
|
199398
|
-
params.stream("
|
|
199065
|
+
params.stream("install pullfrog on the current repository and open its dashboard.");
|
|
199399
199066
|
params.stream("");
|
|
199400
199067
|
params.stream("options:");
|
|
199401
199068
|
params.stream(" -h, --help show help");
|
|
@@ -199567,7 +199234,7 @@ async function runCli4(input) {
|
|
|
199567
199234
|
}
|
|
199568
199235
|
|
|
199569
199236
|
// cli.ts
|
|
199570
|
-
var VERSION10 = "0.1.
|
|
199237
|
+
var VERSION10 = "0.1.61";
|
|
199571
199238
|
var bin = basename2(process.argv[1] || "");
|
|
199572
199239
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
199573
199240
|
var rawArgs = process.argv.slice(2);
|
|
@@ -199575,7 +199242,7 @@ function printMainUsage(stream) {
|
|
|
199575
199242
|
stream(`usage: ${PROG} <command>
|
|
199576
199243
|
`);
|
|
199577
199244
|
stream("commands:");
|
|
199578
|
-
stream(" init
|
|
199245
|
+
stream(" init install pullfrog on the current repository and open its dashboard");
|
|
199579
199246
|
stream(" auth manage provider credentials for the current repository");
|
|
199580
199247
|
stream(" watch stream a PR's activity as one JSON line per event");
|
|
199581
199248
|
stream("");
|