pullfrog 0.1.55 → 0.1.56

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/internal.js CHANGED
@@ -247,12 +247,22 @@ var providers = {
247
247
  displayName: "DeepSeek",
248
248
  envVars: ["DEEPSEEK_API_KEY"],
249
249
  models: {
250
+ // same fork trap as `deepseek-flash` below, and Pro fell into it on
251
+ // 2026-08-12: OpenRouter forked the 0813 release into its own id and left
252
+ // the April preview live under the unversioned one at 2.7x the input and
253
+ // 27x the CACHE-READ rate ($1.168/$2.336/$0.09855 vs
254
+ // $0.435/$0.87/$0.003625). the direct route upgraded in place, so only the
255
+ // OpenRouter side was stale — and that side is what OSS and Router runs
256
+ // use. measured: $0.1719/run authoritative against the funded default's
257
+ // $0.0405. there is no `~deepseek/*-pro-latest` pointer to track, so this
258
+ // one has to be a dated pin; the `models-catalog` drift test is what
259
+ // catches the next release.
250
260
  "deepseek-pro": {
251
261
  displayName: "DeepSeek Pro",
252
262
  resolve: "deepseek/deepseek-v4-pro",
253
263
  effort: ["high", "max"],
254
- openRouterEffort: ["high", "xhigh"],
255
- openRouterResolve: "openrouter/deepseek/deepseek-v4-pro",
264
+ openRouterEffort: ["low", "high", "max"],
265
+ openRouterResolve: "openrouter/deepseek/deepseek-v4-pro-0813",
256
266
  preferred: true
257
267
  },
258
268
  // DeepSeek upgrades the `deepseek-v4-flash` API model in place, so the
@@ -260,14 +270,19 @@ var providers = {
260
270
  // release into its own id and left the April preview live under the
261
271
  // unversioned one at a HIGHER price ($0.14/$0.28 vs $0.09/$0.18) — so the
262
272
  // OpenRouter route can never use the bare id. it tracks the rolling
263
- // `~deepseek/*-latest` pointer (rule 4), which resolves to 0731 at the
264
- // same price today and follows the next in-place upgrade on its own,
265
- // rather than pinning a dated snapshot that goes stale every release.
266
- // 0731 gained a `low` rung and renamed the top to `max`, unlike Pro
267
- // (`high, max` direct / `high, xhigh` via OpenRouter). that ladder now
268
- // reads the same on BOTH routes — the in-place direct upgrade caught up
269
- // to the fork so `low`, not `high`, is position 0 either way, which is
270
- // why the OSS effort floor in `main.ts` pins the `high` rung by name.
273
+ // `~deepseek/*-latest` pointer (rule 4), which follows the next in-place
274
+ // upgrade on its own rather than pinning a dated snapshot that goes stale
275
+ // every release. that pointer is NOT priced like 0731 its listed rates
276
+ // run +40% on output and +57.5% on cache read, and it costs 12% more per
277
+ // run on authoritative spend ($0.0405 vs $0.0361). kept anyway: ~$45/yr
278
+ // buys auto-following the upgrade, and the listed gap overstates the real
279
+ // one because a rolling alias prices at its routing ceiling.
280
+ // 0731 gained a `low` rung and renamed the top to `max`, and that ladder
281
+ // now reads the same on BOTH routes — the in-place direct upgrade caught
282
+ // up to the fork. so `low`, not `high`, is position 0 either way, which is
283
+ // why the OSS effort floor in `main.ts` pins the `high` rung by name. Pro
284
+ // is the same shape via OpenRouter since 0813; only its direct route still
285
+ // starts at `high`.
271
286
  "deepseek-flash": {
272
287
  displayName: "DeepSeek Flash",
273
288
  resolve: "deepseek/deepseek-v4-flash",
@@ -666,11 +681,13 @@ var providers = {
666
681
  effort: ["low", "medium", "high"],
667
682
  openRouterResolve: "openrouter/x-ai/grok-4.5"
668
683
  },
684
+ // dated pin, not the bare id — see `deepseek/deepseek-pro` for why
685
+ // OpenRouter's unversioned `deepseek-v4-pro` is the stale April preview.
669
686
  "deepseek-pro": {
670
687
  displayName: "DeepSeek Pro",
671
- resolve: "openrouter/deepseek/deepseek-v4-pro",
672
- effort: ["high", "xhigh"],
673
- openRouterResolve: "openrouter/deepseek/deepseek-v4-pro"
688
+ resolve: "openrouter/deepseek/deepseek-v4-pro-0813",
689
+ effort: ["low", "high", "max"],
690
+ openRouterResolve: "openrouter/deepseek/deepseek-v4-pro-0813"
674
691
  },
675
692
  // rolling pointer, not the bare id — see `deepseek/deepseek-flash` for why
676
693
  // OpenRouter's unversioned `deepseek-v4-flash` is the stale April preview.
@@ -0,0 +1,14 @@
1
+ import type { ToolContext } from "./server.ts";
2
+ export declare const CloseCurrent: import("arktype/internal/variants/object.ts").ObjectType<{
3
+ state_reason: "completed" | "not_planned" | "duplicate";
4
+ }, {}>;
5
+ export declare function CloseCurrentTool(ctx: ToolContext): import("fastmcp").Tool<any, import("@standard-schema/spec").StandardSchemaV1<{
6
+ state_reason: "completed" | "not_planned" | "duplicate";
7
+ }, {
8
+ state_reason: "completed" | "not_planned" | "duplicate";
9
+ }>> & {
10
+ mutates?: boolean;
11
+ };
12
+ export declare function ReopenCurrentTool(ctx: ToolContext): import("fastmcp").Tool<any, import("@standard-schema/spec").StandardSchemaV1<object, object>> & {
13
+ mutates?: boolean;
14
+ };
@@ -0,0 +1,32 @@
1
+ import type { ToolContext } from "./server.ts";
2
+ export declare const GhParams: import("arktype/internal/variants/object.ts").ObjectType<{
3
+ args: string[];
4
+ }, {}>;
5
+ /**
6
+ * The GitHub CLI, authenticated with a token scoped to mirror the triggering
7
+ * user's own repository role (see action/utils/roleMirror.ts).
8
+ *
9
+ * Registered only when that mirror yields a token — triage and above. Below
10
+ * that the object-scoped tools are registered instead, because no GitHub
11
+ * permission set can express "act only on the object you authored".
12
+ *
13
+ * There is deliberately NO subcommand allowlist. `gh auth token` prints its own
14
+ * credential by design, `GH_DEBUG=api` dumps the Authorization header, and
15
+ * aliases and extensions are general code execution — so containment of the
16
+ * TOKEN is not achievable and is not attempted; correct scoping plus revocation
17
+ * at run end is what makes a leak survivable.
18
+ *
19
+ * That last property is exactly why this runs through `runSandboxed` rather
20
+ * than spawning directly: `gh alias set x '!<shell>'` is arbitrary code
21
+ * execution, and outside the sandbox it would reach pullfrog-managed on-disk
22
+ * secrets, the runner's workflow-command files and a writable `.git` — none of
23
+ * which the token's scope bounds. Inside it, that capability is no more than
24
+ * the `shell` tool already grants. See wiki/agent-github-access.md.
25
+ */
26
+ export declare function GhTool(ctx: ToolContext): import("fastmcp").Tool<any, import("@standard-schema/spec").StandardSchemaV1<{
27
+ args: string[];
28
+ }, {
29
+ args: string[];
30
+ }>> & {
31
+ mutates?: boolean;
32
+ };
@@ -18,26 +18,3 @@ export declare function IssueTool(ctx: ToolContext): import("fastmcp").Tool<any,
18
18
  }>> & {
19
19
  mutates?: boolean;
20
20
  };
21
- export declare const CloseIssue: import("arktype/internal/variants/object.ts").ObjectType<{
22
- issue_number: number;
23
- state_reason: "completed" | "not_planned" | "duplicate";
24
- }, {}>;
25
- export declare function CloseIssueTool(ctx: ToolContext): import("fastmcp").Tool<any, import("@standard-schema/spec").StandardSchemaV1<{
26
- issue_number: number;
27
- state_reason: "completed" | "not_planned" | "duplicate";
28
- }, {
29
- issue_number: number;
30
- state_reason: "completed" | "not_planned" | "duplicate";
31
- }>> & {
32
- mutates?: boolean;
33
- };
34
- export declare const ReopenIssue: import("arktype/internal/variants/object.ts").ObjectType<{
35
- issue_number: number;
36
- }, {}>;
37
- export declare function ReopenIssueTool(ctx: ToolContext): import("fastmcp").Tool<any, import("@standard-schema/spec").StandardSchemaV1<{
38
- issue_number: number;
39
- }, {
40
- issue_number: number;
41
- }>> & {
42
- mutates?: boolean;
43
- };
package/dist/mcp/pr.d.ts CHANGED
@@ -6,16 +6,6 @@ export declare const PullRequest: import("arktype/internal/variants/object.ts").
6
6
  draft?: boolean;
7
7
  repo?: string;
8
8
  }, {}>;
9
- export declare const ClosePullRequest: import("arktype/internal/variants/object.ts").ObjectType<{
10
- pull_number: number;
11
- }, {}>;
12
- export declare function ClosePullRequestTool(ctx: ToolContext): import("fastmcp").Tool<any, import("@standard-schema/spec").StandardSchemaV1<{
13
- pull_number: number;
14
- }, {
15
- pull_number: number;
16
- }>> & {
17
- mutates?: boolean;
18
- };
19
9
  export declare const UpdatePullRequestBody: import("arktype/internal/variants/object.ts").ObjectType<{
20
10
  pull_number: number;
21
11
  body: string;
@@ -16,6 +16,7 @@ export interface ToolContext {
16
16
  gitToken: string;
17
17
  refreshGitToken: ((stale: string) => Promise<string>) | undefined;
18
18
  readToken: string | undefined;
19
+ ghToken: string | undefined;
19
20
  xrepo: XrepoConfig | undefined;
20
21
  apiToken: string;
21
22
  modes: Mode[];
@@ -50,6 +51,7 @@ type McpHttpServerOptions = {
50
51
  */
51
52
  export declare function startMcpHttpServer(ctx: ToolContext, options?: McpHttpServerOptions): Promise<{
52
53
  url: string;
54
+ toolNames: string[];
53
55
  [Symbol.asyncDispose]: () => Promise<void>;
54
56
  }>;
55
57
  export {};
@@ -14,6 +14,30 @@ export declare function getSandboxMethod(): SandboxMethod;
14
14
  * runners, build tools, grep on large trees), so the overflow is spilled
15
15
  * to a tempfile the agent can re-read selectively (cat/tail/grep). */
16
16
  export declare const MAX_OUTPUT_CHARS = 5000;
17
+ /** if `output` exceeds `MAX_OUTPUT_CHARS`, persist the full body to a
18
+ * tempfile and return the last `MAX_OUTPUT_CHARS` prefixed with a sentinel
19
+ * pointing at the saved path. otherwise return as-is. */
20
+ /**
21
+ * Run one command inside the mandatory shell sandbox and collect its output.
22
+ *
23
+ * Shared by `shell` and `gh` so that agent-initiated process execution carries
24
+ * the same mount- and PID-namespace guarantees no matter which tool starts it.
25
+ * A bare `spawnSync` here would escape `PROC_CLEANUP`, `SOCKET_CLEANUP` and
26
+ * `buildFsMounts` — i.e. the tmpfs over `/var/lib/pullfrog` (codex `auth.json`),
27
+ * the tmpfs over `$RUNNER_TEMP/_runner_file_commands`, and the `.git` ro-bind —
28
+ * none of which any token's scope bounds.
29
+ */
30
+ export declare function runSandboxed(params: {
31
+ command: string;
32
+ env: Record<string, string | undefined>;
33
+ cwd: string;
34
+ timeout: number;
35
+ }): Promise<{
36
+ output: string;
37
+ exitCode: number;
38
+ timedOut: boolean;
39
+ }>;
40
+ export declare function capOutput(output: string): string;
17
41
  export declare function ShellTool(ctx: ToolContext): import("fastmcp").Tool<any, import("@standard-schema/spec").StandardSchemaV1<{
18
42
  command: string;
19
43
  description?: string;
@@ -56,7 +56,15 @@ export declare function commercialPaywallBody(params: {
56
56
  ownerLogin: string;
57
57
  url: string;
58
58
  }): string;
59
- /** commercial-gate copy for action runs, linked to the account billing card. */
59
+ /**
60
+ * commercial-gate copy for action runs, linked to the account billing card.
61
+ *
62
+ * if this fires on a `pullfrog/preview-*` repo it is an infrastructure bug, not
63
+ * a billing state — those repos have no real billing state to refuse. the
64
+ * original cause was the gate comparing the subscription's originating host
65
+ * with strict equality, which no preview could satisfy against a cloned
66
+ * production row; see `isSubscriptionHostTrusted` and wiki/billing-model-v2.md.
67
+ */
60
68
  export declare function formatCommercialGateSummary(params: {
61
69
  reason: "commercial" | "subscription_unpaid";
62
70
  ownerLogin: string;
@@ -24,7 +24,7 @@ type WriteOnly = "write";
24
24
  * @see https://docs.github.com/en/rest/apps/installations#create-an-installation-access-token-for-an-app
25
25
  * @see https://github.com/github/rest-api-description — components.schemas.app-permissions
26
26
  */
27
- type GitHubAppPermissions = {
27
+ export type GitHubAppPermissions = {
28
28
  actions?: ReadWrite;
29
29
  artifact_metadata?: ReadWrite;
30
30
  attestations?: ReadWrite;
@@ -0,0 +1,42 @@
1
+ import type { AuthorPermission } from "../external.ts";
2
+ import type { GitHubAppPermissions } from "./github.ts";
3
+ /**
4
+ * The installation-token permission set that mirrors the triggering user's own
5
+ * repository role, or `null` when the role sits below the threshold at which
6
+ * the agent is handed a GitHub CLI credential at all.
7
+ *
8
+ * The invariant this exists to hold: **a leaked run token must never grant more
9
+ * than the triggering user already has on that repo.** `gh` can print its own
10
+ * credential (`gh auth token`), dump it via `GH_DEBUG=api`, and reach arbitrary
11
+ * code through aliases and extensions, so containment is not achievable and is
12
+ * not attempted — correct scoping plus revocation at run end is what makes a
13
+ * leak survivable. See wiki/agent-github-access.md.
14
+ *
15
+ * The mirror is exact from `triage` up. At `read`/`none` no permission set
16
+ * expresses "comment, and act only on objects you authored", because GitHub
17
+ * permissions have no object granularity — so those runs get no token and use
18
+ * the object-scoped MCP tools instead.
19
+ *
20
+ * The set is a strict SUBSET of what any qualifying role holds, so the mirror
21
+ * is one-directional by construction — it can never over-grant:
22
+ *
23
+ * - `contents` is **never** `write`, at any tier. That is what keeps the repo's
24
+ * `push` setting meaningful (a `push: "disabled"` repo must not receive a
25
+ * write credential by another door) and what keeps `gh pr merge` and
26
+ * `gh api` ref writes structurally unavailable — merging is authorized by
27
+ * `contents:write`, so withholding it is what makes
28
+ * wiki/autonomous-maintenance.md's "no injected instruction can cause a
29
+ * merge" still true. Legitimate pushes go through `push_branch`, which
30
+ * enforces the branch rules.
31
+ * - `checks` is absent for the same class of reason: no human role grants it,
32
+ * and it would let the agent post its own passing `pullfrog-approval` status
33
+ * (see wiki/review-approval.md).
34
+ * - Every qualifying role therefore receives the SAME set. The role decides
35
+ * whether a token is minted at all, not how wide it is — `admin`, `maintain`
36
+ * and `write` could not be told apart anyway, because everything that
37
+ * separates them needs the `administration` permission the app deliberately
38
+ * does not request (see wiki/app-permissions.md).
39
+ */
40
+ export declare function mirrorRolePermissions(role: AuthorPermission | undefined): GitHubAppPermissions | null;
41
+ /** render a permission set as `contents:write, issues:write` for logs and the prompt. */
42
+ export declare function formatPermissions(permissions: GitHubAppPermissions): string;
@@ -1,4 +1,4 @@
1
- import type { PushPermission, XrepoConfig } from "../external.ts";
1
+ import type { AuthorPermission, PushPermission, XrepoConfig } from "../external.ts";
2
2
  import { acquireNewToken, type OidcCredentials } from "./github.ts";
3
3
  export { acquireNewToken as acquireInstallationToken };
4
4
  export { revokeGitHubInstallationToken as revokeInstallationToken };
@@ -21,11 +21,13 @@ export type TokenRef = {
21
21
  gitToken: string;
22
22
  mcpToken: string;
23
23
  readToken?: string | undefined;
24
+ ghToken?: string | undefined;
24
25
  refreshGitToken?: ((stale: string) => Promise<string>) | undefined;
25
26
  [Symbol.asyncDispose]: () => Promise<void>;
26
27
  };
27
28
  type ResolveTokensParams = {
28
29
  push: PushPermission;
30
+ authorPermission: AuthorPermission | undefined;
29
31
  xrepo?: XrepoConfig | undefined;
30
32
  /**
31
33
  * OIDC credentials stashed by main.ts before the restricted-mode env wipe —
@@ -1,5 +1,16 @@
1
- /** tracked paths with staged or unstaged modifications. */
2
- export declare function dirtyTrackedPaths(): Promise<Set<string>>;
1
+ /**
2
+ * tracked paths with staged or unstaged modifications.
3
+ *
4
+ * `cwd` is not optional decoration: the CLI does NOT start inside the checkout.
5
+ * `runCli.ts` launches it from a bootstrap tmpdir (published) or `actionRoot` (a
6
+ * tarball action checkout with no `.git`), and `main.ts` only chdirs into the
7
+ * repo later. A caller that runs before that chdir and relies on `process.cwd()`
8
+ * gets `exit 129 — Not a git repository` and, since this throws, kills the run:
9
+ * that is exactly what 0.1.54 did to every customer. Pass the repo dir.
10
+ */
11
+ export declare function dirtyTrackedPaths(params?: {
12
+ cwd?: string;
13
+ }): Promise<Set<string>>;
3
14
  /**
4
15
  * discard tracked-file mods that appeared since the `before` snapshot, so the
5
16
  * customer checkout is clean before agent tools run — any dirt makes
@@ -17,4 +28,5 @@ export declare function dirtyTrackedPaths(): Promise<Set<string>>;
17
28
  export declare function restoreDirtiedSince(params: {
18
29
  before: Set<string>;
19
30
  actor: string;
31
+ cwd?: string;
20
32
  }): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pullfrog",
3
- "version": "0.1.55",
3
+ "version": "0.1.56",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "pullfrog": "dist/cli.mjs",