brnch-cli 0.19.0 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/brnch.mjs +269 -24
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # brnch 0.19.0
1
+ # brnch 0.19.1
2
2
 
3
3
  Command-line client for [Brnch](https://brnch.io) — Git hosting for humans and agents. Single file, no runtime dependencies, Node 20 or newer.
4
4
 
package/brnch.mjs CHANGED
@@ -30573,7 +30573,8 @@ var PLATFORM_TOKEN_PREFIXES = {
30573
30573
  fsfa: "pending second-factor sign-in",
30574
30574
  fbkc: "Buildkite checkout token",
30575
30575
  fsvw: "staff read-only view",
30576
- fcrt: "container registry token"
30576
+ fcrt: "container registry token",
30577
+ fcpk: "clone-pack build token"
30577
30578
  };
30578
30579
  var PLATFORM_TOKEN_RE = new RegExp(`^(${Object.keys(PLATFORM_TOKEN_PREFIXES).join("|")})_[0-9a-f]{64}$`);
30579
30580
 
@@ -31636,7 +31637,6 @@ var looksLikeHostedLabel = (label2) => /^brnch-hosted(?:-|$)/.test(label2);
31636
31637
  var DEPLOY_LIMITS = {
31637
31638
  maxStepsPerTarget: 12,
31638
31639
  maxWatchPaths: 50,
31639
- maxTargetsPerRepository: 10,
31640
31640
  maxCommandLength: 2e3,
31641
31641
  maxSummaryLength: 500,
31642
31642
  maxDetailKeys: 20,
@@ -31746,7 +31746,8 @@ var PLANS = Object.freeze({
31746
31746
  storage_bytes_per_seat: 0,
31747
31747
  ai: { fast: 100, strong: 25, agent: 0, assistant: 0 },
31748
31748
  private_repo_members: 5,
31749
- overage: false
31749
+ overage: false,
31750
+ deploy_targets_per_repository: 1
31750
31751
  },
31751
31752
  pro: {
31752
31753
  id: "pro",
@@ -31765,7 +31766,8 @@ var PLANS = Object.freeze({
31765
31766
  storage_bytes_per_seat: 0,
31766
31767
  ai: { fast: 300, strong: 100, agent: 10, assistant: 100 },
31767
31768
  private_repo_members: 5,
31768
- overage: true
31769
+ overage: true,
31770
+ deploy_targets_per_repository: 10
31769
31771
  },
31770
31772
  team: {
31771
31773
  id: "team",
@@ -31784,7 +31786,8 @@ var PLANS = Object.freeze({
31784
31786
  storage_bytes_per_seat: 2 * GB,
31785
31787
  ai: { fast: 600, strong: 200, agent: 10, assistant: 100 },
31786
31788
  private_repo_members: Number.POSITIVE_INFINITY,
31787
- overage: true
31789
+ overage: true,
31790
+ deploy_targets_per_repository: 10
31788
31791
  },
31789
31792
  enterprise: {
31790
31793
  id: "enterprise",
@@ -31803,7 +31806,8 @@ var PLANS = Object.freeze({
31803
31806
  storage_bytes_per_seat: 5 * GB,
31804
31807
  ai: { fast: 1500, strong: 500, agent: 40, assistant: 300 },
31805
31808
  private_repo_members: Number.POSITIVE_INFINITY,
31806
- overage: true
31809
+ overage: true,
31810
+ deploy_targets_per_repository: 1e3
31807
31811
  }
31808
31812
  });
31809
31813
 
@@ -32211,15 +32215,27 @@ var UpdateRepoRequest = external_exports.object({
32211
32215
  default_branch: external_exports.string().min(1).max(100).optional(),
32212
32216
  /** Replaces the topic list (≤20, lowercase). */
32213
32217
  topics: Topics.optional(),
32218
+ /** The repository Actions switch (ADR-0009 addendum 2026-10-18). Off: no automatic runs, workflow_dispatch refused. */
32219
+ actions_enabled: external_exports.boolean().optional(),
32214
32220
  version: external_exports.number().int()
32215
32221
  });
32216
32222
  var PATH_HISTORY_SCAN_MAX = 500;
32217
32223
  var RepoPathQuery = external_exports.string().min(1).max(4096).transform((p) => p.replace(/\/{2,}/g, "/").replace(/\/+$/, "")).refine((p) => p.length > 0 && !p.startsWith("/"), "path must be relative to the repository root").refine((p) => !/[\u0000-\u001f\u007f]/.test(p), "path must not contain control characters").refine((p) => p.split("/").every((s) => s !== "." && s !== ".."), "path must not contain . or .. segments");
32224
+ var isoDate = external_exports.string().max(64).refine((v) => !Number.isNaN(Date.parse(v)), { message: "must be an ISO-8601 date" });
32225
+ var queryFlag = external_exports.enum(["1", "true", "0", "false"]).transform((v) => v === "1" || v === "true");
32218
32226
  var CommitListQuery = external_exports.object({
32219
32227
  ref: external_exports.string().optional(),
32220
32228
  path: RepoPathQuery.optional(),
32221
- scan_limit: external_exports.coerce.number().int().min(1).max(PATH_HISTORY_SCAN_MAX).optional()
32222
- }).refine((q) => q.scan_limit === void 0 || q.path !== void 0, { message: "scan_limit requires path", path: ["scan_limit"] });
32229
+ scan_limit: external_exports.coerce.number().int().min(1).max(PATH_HISTORY_SCAN_MAX).optional(),
32230
+ // Log filters (M6-SCL-02): substring matches, case-insensitive (no regular expressions); dates bound the committer
32231
+ // date, inclusive.
32232
+ author: external_exports.string().min(1).max(200).optional(),
32233
+ grep: external_exports.string().min(1).max(200).optional(),
32234
+ since: isoDate.optional(),
32235
+ until: isoDate.optional(),
32236
+ all_parents: queryFlag.optional()
32237
+ }).refine((q) => q.scan_limit === void 0 || q.path !== void 0 || isLogFiltered(q), { message: "scan_limit requires path or a filter", path: ["scan_limit"] });
32238
+ var isLogFiltered = (q) => q.author !== void 0 || q.grep !== void 0 || q.since !== void 0 || q.until !== void 0 || q.all_parents === true;
32223
32239
  var RequiredCheckSchema = external_exports.object({
32224
32240
  name: external_exports.string().min(1).max(200),
32225
32241
  subject: external_exports.enum(["head", "candidate"]).default("head"),
@@ -32454,7 +32470,9 @@ var UpdateOrgRequest = external_exports.object({
32454
32470
  /** Idle timeout of new agent sessions in minutes (ADR-0040). */
32455
32471
  agent_session_idle_minutes: external_exports.number().int().min(15).max(1440).optional(),
32456
32472
  /** Brnch Secure Defaults for new repositories (ADR-0081). Turning it on also requires a second factor for admins. */
32457
- secure_defaults: external_exports.boolean().optional()
32473
+ secure_defaults: external_exports.boolean().optional(),
32474
+ /** The organization's Actions switch (ADR-0009 addendum 2026-10-19): off, no repository of it starts workflow runs. */
32475
+ actions_enabled: external_exports.boolean().optional()
32458
32476
  });
32459
32477
  var OAuthDecisionRequest = external_exports.object({
32460
32478
  approve: external_exports.boolean(),
@@ -33716,6 +33734,16 @@ var CreateConnectionRequest = external_exports.discriminatedUnion("auth", [
33716
33734
  name: Name3,
33717
33735
  credentials: external_exports.object({ api_token: Secret2 })
33718
33736
  }),
33737
+ external_exports.object({
33738
+ // Codex / Claude Code reviews with the key already saved in Settings → AI providers (an OpenAI or Anthropic
33739
+ // provider of this organization). Nothing is copied: the key is read from the provider at every review, so
33740
+ // rotating it there applies here, and deleting the provider stops the reviews.
33741
+ auth: external_exports.literal("ai_provider"),
33742
+ provider: external_exports.enum(["codex", "claude"]),
33743
+ purpose: external_exports.literal("code_review").default("code_review"),
33744
+ name: Name3,
33745
+ config: external_exports.object({ ai_provider_id: external_exports.string().min(1).max(100) })
33746
+ }),
33719
33747
  external_exports.object({
33720
33748
  auth: external_exports.literal("infisical_oidc"),
33721
33749
  provider: external_exports.literal("infisical"),
@@ -34495,6 +34523,48 @@ var MirrorStatus = external_exports.object({
34495
34523
  timings: external_exports.record(external_exports.string(), external_exports.number())
34496
34524
  });
34497
34525
 
34526
+ // packages/protocol/src/clone-pack.ts
34527
+ var CLONE_PACK_ID = /^cpk_[0-9a-z]{10,40}$/;
34528
+ var Authorization2 = external_exports.string().regex(/^Bearer [A-Za-z0-9+/=._~-]{8,4096}$/);
34529
+ var HttpUrl2 = external_exports.string().max(2048).regex(/^https?:\/\/[^\s@]+$/);
34530
+ var Oid2 = external_exports.string().regex(/^[0-9a-f]{40}$/);
34531
+ var RefName3 = external_exports.string().max(1024).regex(/^refs\/[^\s\0]+$/);
34532
+ var PackRef = external_exports.object({ name: RefName3, oid: Oid2 });
34533
+ var PackJob = external_exports.object({
34534
+ /** One build; one container per id. */
34535
+ id: external_exports.string().regex(CLONE_PACK_ID),
34536
+ /**
34537
+ * The platform's endpoint for this build, `<base>/git` its git remote (storage behind the storage gate, private refs
34538
+ * hidden) and `<base>/pack/:n`, `<base>/idx`, `<base>/complete` its upload; `authorization` holds for all of them and
34539
+ * for nothing else.
34540
+ */
34541
+ base_url: HttpUrl2,
34542
+ authorization: Authorization2,
34543
+ default_branch: external_exports.string().regex(/^[A-Za-z0-9._\/-]{1,200}$/).refine((b) => !b.includes(".."), "invalid branch"),
34544
+ /** The last ready pack, downloaded from `<base>/previous/pack` and `/previous/idx`, and the refs it holds. */
34545
+ previous: external_exports.object({ pack_sha1: Oid2, refs: external_exports.array(PackRef).max(2e4) }).nullable(),
34546
+ limits: external_exports.object({ max_pack_bytes: external_exports.number().int().positive(), part_bytes: external_exports.number().int().positive() }),
34547
+ expires_at: external_exports.iso.datetime()
34548
+ });
34549
+ var PackComplete = external_exports.object({
34550
+ pack_sha1: Oid2,
34551
+ pack_bytes: external_exports.number().int().positive(),
34552
+ parts: external_exports.number().int().positive().max(1e4),
34553
+ objects: external_exports.number().int().nonnegative(),
34554
+ refs: external_exports.array(PackRef).max(2e4)
34555
+ });
34556
+ var PackStatus = external_exports.object({
34557
+ id: external_exports.string(),
34558
+ state: external_exports.enum(["running", "completed", "failed"]),
34559
+ phase: external_exports.string().max(40),
34560
+ reason: external_exports.enum(["fetch_failed", "too_large", "upload_failed", "error"]).optional(),
34561
+ message: external_exports.string().max(4e3).nullable(),
34562
+ pack_bytes: external_exports.number().nullable(),
34563
+ objects: external_exports.number().nullable(),
34564
+ updated_at: external_exports.string(),
34565
+ timings: external_exports.record(external_exports.string(), external_exports.number())
34566
+ });
34567
+
34498
34568
  // packages/protocol/src/repo-mirrors.ts
34499
34569
  var StopMirrorRequest = external_exports.object({ confirm: external_exports.string().min(3).max(300), expected_version: external_exports.number().int().positive().optional() }).strict();
34500
34570
  var ResetMirrorRefsRequest = external_exports.object({ refs: external_exports.array(external_exports.string().min(1).max(1024)).min(1).max(100) }).strict();
@@ -36023,6 +36093,7 @@ function orgsGroup(t) {
36023
36093
  create: (body, o) => t.json({ method: "POST", path: "/v1/orgs", json: validate2(CreateOrgRequest, body, "CreateOrgRequest"), options: o }),
36024
36094
  list: (q = {}, o) => t.json({ method: "GET", path: "/v1/orgs", query: { ...q }, options: o }),
36025
36095
  get: (org, o) => t.json({ method: "GET", path: path2`/v1/orgs/${org}`, options: o }),
36096
+ update: (org, body, o) => t.json({ method: "PATCH", path: path2`/v1/orgs/${org}`, json: validate2(UpdateOrgRequest, body, "UpdateOrgRequest"), options: o }),
36026
36097
  members: {
36027
36098
  list: (org, q = {}, o) => t.json({ method: "GET", path: path2`/v1/orgs/${org}/members`, query: { ...q }, options: o }),
36028
36099
  update: (org, userId, body, o) => t.json({
@@ -36314,13 +36385,25 @@ function reposGroup(t) {
36314
36385
  /** Raw file bytes; never interpret them as HTML. */
36315
36386
  raw: (repo, q, o) => t.bytes({ method: "GET", path: path2`/v1/repos/${repo}/raw`, query: { ...q }, options: o }),
36316
36387
  /** First-parent history of `ref`; with `path`, only commits that changed it — each page examines at most
36317
- * `scan_limit` commits and reports how many in `path_scan` (`complete` is false until the walk reaches the root). */
36388
+ * `scan_limit` commits and reports how many in `path_scan` (`complete` is false until the walk reaches the root).
36389
+ * With log filters (`author`, `grep`, `since`, `until`, `all_parents`; substring matches), the same in `log_scan`. */
36318
36390
  commits: (repo, q = {}, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/commits`, query: { ...q }, options: o }),
36319
36391
  /** Write text files to a branch as one commit, through the same checks as a push (ADR-0078). Retry with the same
36320
36392
  * `idempotencyKey`: a retry after an uncertain outcome never commits twice. */
36321
36393
  commitFiles: (repo, body, o) => t.json({ method: "POST", path: path2`/v1/repos/${repo}/commits`, json: validate2(CommitFilesRequest, body, "CommitFilesRequest"), options: o }),
36322
36394
  commit: (repo, oid, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/commits/${oid}`, options: o }),
36395
+ /** The changes that brought a commit onto each ref it landed on (M6-SCL-01). */
36396
+ commitChange: (repo, oid, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/commits/${oid}/change`, options: o }),
36397
+ /** `mode: "two-dot"` diffs the base tree against the head tree instead of against their merge base. */
36323
36398
  compare: (repo, q, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/compare`, query: { ...q }, options: o }),
36399
+ /** The commits in `base..head` (newest first, at most `limit`, max 250) with ahead/behind counts (M6-SCL-02). */
36400
+ compareCommits: (repo, q, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/compare/commits`, query: { ...q }, options: o }),
36401
+ /** Every best common ancestor of two refs or commits (`git merge-base --all`). */
36402
+ mergeBase: (repo, q, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/merge-base`, query: { ...q }, options: o }),
36403
+ /** Whether `ancestor` is in `descendant`'s history; `is_ancestor` is null when the bounded walk cannot decide. */
36404
+ isAncestor: (repo, q, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/is-ancestor`, query: { ...q }, options: o }),
36405
+ /** Branches and tags whose history contains a commit (`git for-each-ref --contains`). */
36406
+ refsContaining: (repo, commit, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/commits/${commit}/refs`, options: o }),
36324
36407
  diff: (repo, q, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/diff`, query: { ...q }, options: o }),
36325
36408
  pushes: (repo, q = {}, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/pushes`, query: { ...q }, options: o }),
36326
36409
  search: (repo, q, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/search`, query: { ...q }, options: o }),
@@ -36628,6 +36711,7 @@ function dbMigrationsGroup(t) {
36628
36711
  }
36629
36712
 
36630
36713
  // packages/sdk/src/groups/deploy.ts
36714
+ var isCallOptions = (x) => "signal" in x || "idempotencyKey" in x;
36631
36715
  function deployGroup(t) {
36632
36716
  return {
36633
36717
  stepKinds: (o) => t.json({ method: "GET", path: "/v1/deploy/step-kinds", options: o }),
@@ -36637,7 +36721,14 @@ function deployGroup(t) {
36637
36721
  suggestions: (repo, ref, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/deploy-suggestions`, query: ref ? { ref } : {}, options: o }),
36638
36722
  resources: (repo, connection, type, parent, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/deploy-connections/${connection}/resources`, query: { type, ...parent ? { parent } : {} }, options: o }),
36639
36723
  targets: {
36640
- list: (repo, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/deploy-targets`, options: o }),
36724
+ /**
36725
+ * A page of the repository's targets (50 by default); `name` finds one target by name. Callers from before
36726
+ * pagination passed call options second: those are still taken as call options.
36727
+ */
36728
+ list: (repo, q = {}, o) => {
36729
+ if (isCallOptions(q)) [q, o] = [{}, q];
36730
+ return t.json({ method: "GET", path: path2`/v1/repos/${repo}/deploy-targets`, query: { ...q }, options: o });
36731
+ },
36641
36732
  create: (repo, body, o) => t.json({ method: "POST", path: path2`/v1/repos/${repo}/deploy-targets`, json: validate2(CreateDeployTargetRequest, body, "CreateDeployTargetRequest"), options: o }),
36642
36733
  get: (id2, o) => t.json({ method: "GET", path: path2`/v1/deploy-targets/${id2}`, options: o }),
36643
36734
  update: (id2, body, o) => t.json({ method: "PATCH", path: path2`/v1/deploy-targets/${id2}`, json: validate2(UpdateDeployTargetRequest, body, "UpdateDeployTargetRequest"), options: o }),
@@ -37138,7 +37229,7 @@ function invalidAgentToolFlag(flag) {
37138
37229
  }
37139
37230
 
37140
37231
  // packages/cli/package.json
37141
- var version2 = "0.19.0";
37232
+ var version2 = "0.19.1";
37142
37233
 
37143
37234
  // packages/cli/src/brand.ts
37144
37235
  var BRAND = {
@@ -39821,6 +39912,7 @@ function instructionsSnippet(tool) {
39821
39912
  ` - Commit only what you wrote: stage your paths (\`git add <path>\u2026\`), not the person's uncommitted edits (\`git add -A\` takes them too). Do not squash other sessions' or the person's commits into yours.`,
39822
39913
  ` - Commit authors and trailers (\`Agent-Session:\`, \`Co-Authored-By:\`) are claims and attribute nothing; you do not need them for attribution.`,
39823
39914
  ` - Hooks wired: \`${cli} hooks status\`. Codex runs them only after the person trusts them once (\`${cli} hooks trust\` in their terminal).`,
39915
+ `- Ask Brnch which change brought a commit instead of searching \`git log\` or guessing from commit-message words: \`${cli} repo commit-change <commit>\` (MCP: \`commit_change_get\`), for every merge strategy. The change's description and review say why.`,
39824
39916
  `- Record key decisions as you make them: \`${cli} decision "\u2026" --reason "\u2026" --file <path>\`. Blame shows them with the lines they explain.`,
39825
39917
  `- Other agents may be working here at the same time. Run \`${cli} status\` before you start to see who is working on what.`,
39826
39918
  `- Claim ADR numbers, migration numbers, release versions and exclusive operations before you use them, and use exactly the key you get: \`${cli} claim adr:<n> --next\` (also \`migration:<n>\`, \`version:<pkg>:<x.y.z>\`, \`lock:<operation>\`). If a key is taken, take the next; never reuse another session's key.`,
@@ -48487,8 +48579,8 @@ ${renderPlan2(v.plan)}`);
48487
48579
  // packages/cli/src/commands/deploy.ts
48488
48580
  var TERMINAL4 = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled", "superseded", "blocked"]);
48489
48581
  var NO_DO_PREVIEWS = "previews off: Cloudflare makes no version URLs for Workers that implement a Durable Object (--previews --preview-mode worker_previews uses Worker Previews; it needs a `previews` block in the Wrangler configuration)";
48490
- var renderTargets = (v) => table(
48491
- v.items.map((t) => ({ ...t, resource: t.resource_name, env: t.environment ?? "-", last_production: t.last_production ? `${t.last_production.status} ${t.last_production.commit_oid.slice(0, 12)}` : "-" })),
48582
+ var renderTargets = (v) => listWithCursor(
48583
+ { ...v, items: v.items.map((t) => ({ ...t, resource: t.resource_name, env: t.environment ?? "-", last_production: t.last_production ? `${t.last_production.status} ${t.last_production.commit_oid.slice(0, 12)}` : "-" })) },
48492
48584
  ["name", "id", "platform", "resource", "env", "branch", "runs_on", "previews", "state", "last_production"]
48493
48585
  );
48494
48586
  var renderTarget = (t) => kv({ ...t, approvers: ruleSummary(t.approval_rule), watch_paths: t.watch_paths?.length ? t.watch_paths.join(" ") : "every change", connection: t.connection ? `${t.connection.name} (${t.connection.id}, ${t.connection.state})` : "-", last_production: t.last_production ? `${t.last_production.id} ${t.last_production.status}` : "-" }, [
@@ -48531,10 +48623,12 @@ ${lines2}
48531
48623
  }
48532
48624
  async function targetId(ctx, ref) {
48533
48625
  if (ref.startsWith("dpt_")) return ref;
48534
- const { items } = await ctx.client("user").deploy.targets.list(await ctx.repoId(void 0, "user"));
48535
- const t = items.find((x) => x.name === ref);
48536
- if (!t) throw new CliError(`no deploy target named "${ref}" (have: ${items.map((x) => x.name).join(", ") || "none"})`, EXIT.NOT_FOUND, "NOT_FOUND");
48537
- return t.id;
48626
+ const client = ctx.client("user");
48627
+ const repo = await ctx.repoId(void 0, "user");
48628
+ const t = (await client.deploy.targets.list(repo, { name: ref })).items.find((x) => x.name === ref);
48629
+ if (t) return t.id;
48630
+ const { items, next_cursor } = await client.deploy.targets.list(repo, { limit: 20 });
48631
+ throw new CliError(`no deploy target named "${ref}" (have: ${items.map((x) => x.name).join(", ") || "none"}${next_cursor ? ", \u2026" : ""})`, EXIT.NOT_FOUND, "NOT_FOUND");
48538
48632
  }
48539
48633
  var orNull = (v) => v === void 0 ? void 0 : v || null;
48540
48634
  async function stepsOf(ctx, spec) {
@@ -48651,7 +48745,7 @@ function registerDeploy(program2, rt) {
48651
48745
  Merges to ${v.branch} deploy automatically. Deploy now: brnch deploy run ${v.id}`);
48652
48746
  })
48653
48747
  );
48654
- const targets2 = deploy.command("targets").description("Deploy targets of --repo; `protect` requires approvers for a target's production deploys").action(act(rt, async (ctx) => ctx.result(await ctx.client("user").deploy.targets.list(await ctx.repoId(void 0, "user")), renderTargets)));
48748
+ const targets2 = deploy.command("targets").description("Deploy targets of --repo; `protect` requires approvers for a target's production deploys").option("--limit <n>", "page size", (v) => parseIntOption(v, "--limit")).option("--cursor <cursor>", "page cursor").action(act(rt, async (ctx, o) => ctx.result(await ctx.client("user").deploy.targets.list(await ctx.repoId(void 0, "user"), { limit: o.limit, cursor: o.cursor }), renderTargets)));
48655
48749
  protectOptions(targets2.command("protect <target>").description("Require approvals for the target's production deploys and rollbacks (people with deploy:manage). Replaces the current rule")).action(
48656
48750
  act(rt, async (ctx, o, [ref]) => {
48657
48751
  const c8 = ctx.client("user").deploy.targets;
@@ -49342,15 +49436,78 @@ function authGuidance(cli, helperConfigured) {
49342
49436
  }
49343
49437
 
49344
49438
  // packages/cli/src/commands/repo.ts
49345
- var REPO_FIELDS = ["id", "org_slug", "name", "description", "visibility", "default_branch", "lifecycle", "clone_url", "updated_at"];
49439
+ var REPO_FIELDS = ["id", "org_slug", "name", "description", "visibility", "default_branch", "lifecycle", "clone_url", "actions_enabled", "updated_at"];
49346
49440
  var renderRepo2 = (r) => kv(r, REPO_FIELDS) + (r.import ? `
49347
49441
  import ${r.import.status}${r.import.shallow ? " (shallow)" : ""}` : "");
49348
49442
  function renderLog(p) {
49349
49443
  const rows2 = p.items.map((c8) => ({ oid: c8.oid.slice(0, 12), date: c8.committer.date.slice(0, 10), author: c8.author.name, subject: c8.message.split("\n", 1)[0] }));
49350
49444
  const lines2 = [table(rows2, ["oid", "date", "author", "subject"])];
49351
49445
  const s = p.path_scan;
49446
+ const l = p.log_scan;
49352
49447
  if (s) lines2.push(s.complete ? `scanned ${s.scanned_total} commits: full first-parent history of ${s.path}` : `scanned ${s.scanned_total} commits so far; older history not searched yet (more: --cursor ${p.next_cursor})`);
49353
- else if (p.next_cursor) lines2.push(`(more: --cursor ${p.next_cursor})`);
49448
+ else if (l) {
49449
+ if (l.complete) lines2.push(`scanned ${l.scanned_total} commits: all matching history searched`);
49450
+ else if (l.exhausted) lines2.push(`scanned ${l.scanned_total} commits, the most one search walks; older history not searched`);
49451
+ else lines2.push(`scanned ${l.scanned_total} commits so far; older history not searched yet (more: --cursor ${p.next_cursor})`);
49452
+ } else if (p.next_cursor) lines2.push(`(more: --cursor ${p.next_cursor})`);
49453
+ return lines2.join("\n");
49454
+ }
49455
+ function renderCommitChange(r) {
49456
+ const lines2 = r.landings.map((l) => {
49457
+ const how2 = l.role === "landing" ? `${l.strategy} commit of` : `brought in by ${l.strategy} of`;
49458
+ const receipt = l.receipt_id ? `, receipt ${l.receipt_id}` : "";
49459
+ return `${l.target_ref.replace(/^refs\/heads\//, "")}: ${how2} #${l.change.number} ${l.change.title} (${l.change.state}; landing ${l.landing_oid.slice(0, 12)}${receipt})`;
49460
+ });
49461
+ if (!lines2.length) lines2.push(r.indexing ? "no change on record yet: earlier landings are still being indexed, ask again shortly" : "no change brought this commit (pushed straight to a branch, or older than the repository on Brnch)");
49462
+ else if (r.indexing) lines2.push("(earlier landings are still being indexed)");
49463
+ return lines2.join("\n");
49464
+ }
49465
+ var renderTree = (t) => [`${t.path || "/"} at ${t.commit_oid.slice(0, 12)}`, table(t.entries.map((e) => ({ ...e, name: e.type === "tree" ? `${e.name}/` : e.name })), ["mode", "type", "name"])].join("\n");
49466
+ function renderBlob(b) {
49467
+ if (b.binary) return `(binary file, ${b.size} bytes; ${BRAND.cli} --json repo blob for its id)`;
49468
+ if (b.truncated || b.encoding !== "utf8") return `(${b.size} bytes, too large to show inline: clone, or download ${b.path} at ${b.commit_oid.slice(0, 12)})`;
49469
+ return b.content;
49470
+ }
49471
+ function renderBlame(b) {
49472
+ const rows2 = b.lines.map((l) => ({ line: l.line, oid: l.commit_oid.slice(0, 12), date: l.date.slice(0, 10), author: l.author, summary: l.summary }));
49473
+ const lines2 = [table(rows2, ["line", "oid", "date", "author", "summary"])];
49474
+ if (b.partial) lines2.push("partial: the walk ran out of time, older lines may belong to newer commits; the full blame is being computed, ask again shortly");
49475
+ else if (b.truncated) lines2.push("history not walked to the end: unresolved lines are attributed to the oldest commit examined");
49476
+ return lines2.join("\n");
49477
+ }
49478
+ function renderSearch(r) {
49479
+ const rows2 = r.items.map((i) => ({ kind: i.kind, where: i.path ?? i.title, snippet: i.snippet.replace(/\s+/g, " ").slice(0, 100) }));
49480
+ const lines2 = [table(rows2, ["kind", "where", "snippet"])];
49481
+ const ref = r.index.ref;
49482
+ if (ref) {
49483
+ lines2.push(
49484
+ ref.complete ? `code as of ${ref.name} (${ref.commit_oid.slice(0, 12)}): the index plus the ${ref.searched_files} files it changed` : `incomplete: ${ref.name} changed ${ref.changed_files}${ref.changed_files >= 500 ? "+" : ""} files and ${ref.searched_files} were searched; a missing hit is not proof of absence`
49485
+ );
49486
+ } else if (r.index.commit_oid && !r.index.fresh) lines2.push(`code index is behind the default branch (indexed ${r.index.commit_oid.slice(0, 12)}); catching up`);
49487
+ else if (!r.index.commit_oid && r.index.indexed_at === null) lines2.push("code index not built yet; catching up");
49488
+ if (r.index.truncated) lines2.push("the code index stopped at its size bounds: some files are not searchable");
49489
+ return lines2.join("\n");
49490
+ }
49491
+ function renderCompare(c8) {
49492
+ const rows2 = c8.files.map((f) => ({ status: f.status, path: f.status === "renamed" ? `${f.old_path} \u2192 ${f.new_path}` : f.new_path ?? f.old_path }));
49493
+ const head = c8.mode === "two-dot" ? `${c8.base_oid.slice(0, 12)}..${c8.head_oid.slice(0, 12)} (trees)` : `${c8.base_oid.slice(0, 12)}...${c8.head_oid.slice(0, 12)} (since merge base ${c8.merge_base_oid?.slice(0, 12) ?? "none: unrelated histories"})`;
49494
+ return [head, table(rows2, ["status", "path"]), ...c8.truncated ? ["(more files changed than listed)"] : []].join("\n");
49495
+ }
49496
+ function renderRange(r) {
49497
+ const rows2 = r.commits.map((c8) => ({ oid: c8.oid.slice(0, 12), date: c8.committer.date.slice(0, 10), author: c8.author.name, subject: c8.message.split("\n", 1)[0] }));
49498
+ const count = (n) => n === null ? "unknown" : String(n);
49499
+ return [
49500
+ `ahead ${count(r.ahead_by)}, behind ${count(r.behind_by)}`,
49501
+ table(rows2, ["oid", "date", "author", "subject"]),
49502
+ ...r.truncated ? ["(more commits than listed: --limit up to 250)"] : [],
49503
+ ...r.complete ? [] : ["incomplete: the bounded history walk did not finish; counts and the list may be short"]
49504
+ ].join("\n");
49505
+ }
49506
+ var renderMergeBase = (m) => m.merge_bases.length ? m.merge_bases.join("\n") : m.complete ? "(none: unrelated histories)" : "unknown: the bounded history walk did not finish";
49507
+ var renderAncestry = (a) => a.is_ancestor === null ? "unknown: the bounded history walk could not decide (not a no)" : `${a.is_ancestor ? "yes" : "no"}: ${a.ancestor_oid.slice(0, 12)} is ${a.is_ancestor ? "" : "not "}in the history of ${a.descendant_oid.slice(0, 12)}`;
49508
+ function renderContaining(r) {
49509
+ const lines2 = r.refs.length ? [...r.refs] : [r.complete ? "(no branch or tag contains it)" : "(none found so far)"];
49510
+ if (r.unknown.length) lines2.push(`not settled (may contain it): ${r.unknown.join(", ")}`);
49354
49511
  return lines2.join("\n");
49355
49512
  }
49356
49513
  function registerOrg(program2, rt) {
@@ -49361,6 +49518,14 @@ function registerOrg(program2, rt) {
49361
49518
  ctx.result(created, (v) => kv(v, ["id", "slug", "name", "created_at"]));
49362
49519
  })
49363
49520
  );
49521
+ org.command("edit [org]").description("Change an organization's settings (organization admins)").option("--actions <on|off>", "turn Actions on or off for every repository of the organization: off starts no runs from pushes, changes or schedules and refuses workflow_dispatch; runs already started go on. A repository can also turn it off for itself (repo edit --actions)").action(
49522
+ act(rt, async (ctx, o, [spec]) => {
49523
+ if (o.actions === void 0) throw usage("nothing to change: pass --actions on|off");
49524
+ if (o.actions !== "on" && o.actions !== "off") throw usage("--actions must be on or off");
49525
+ const updated = await ctx.client("user").orgs.update(spec ?? ctx.org(), { actions_enabled: o.actions === "on" });
49526
+ ctx.result(updated, (v) => kv({ ...v, actions: v.settings.actions_enabled === false ? "off" : "on" }, ["id", "slug", "name", "actions"]));
49527
+ })
49528
+ );
49364
49529
  org.command("list").description("Your personal account and the organizations you belong to").option("--limit <n>", "page size", (v) => parseIntOption(v, "--limit")).option("--cursor <cursor>", "page cursor").action(
49365
49530
  act(rt, async (ctx, o) => {
49366
49531
  const page = await ctx.client("user").orgs.list({ limit: o.limit, cursor: o.cursor });
@@ -49490,13 +49655,93 @@ function registerRepo(program2, rt) {
49490
49655
  ctx.result(await ctx.client("agent").repos.get(await ctx.repoId(spec)), renderRepo2);
49491
49656
  })
49492
49657
  );
49493
- repo.command("log [repo]").description("First-parent commit history of a ref; with --path, only commits that changed that file or directory (no rename detection)").option("--ref <ref>", "branch, tag or commit (default branch when omitted)").option("--path <path>", "repository-relative file or directory").option("--limit <n>", "page size", (v) => parseIntOption(v, "--limit")).option("--scan-limit <n>", "with --path: commits examined per page (server default 200, max 500)", (v) => parseIntOption(v, "--scan-limit")).option("--cursor <cursor>", "page cursor (resumes a --path walk where it stopped)").action(
49658
+ repo.command("edit [repo]").description("Change a repository's settings (repository admins)").option("--actions <on|off>", "turn Actions on or off: off starts no runs from pushes, changes or schedules and refuses workflow_dispatch; runs already started go on").action(
49659
+ act(rt, async (ctx, o, [spec]) => {
49660
+ if (o.actions === void 0) throw usage("nothing to change: pass --actions on|off");
49661
+ if (o.actions !== "on" && o.actions !== "off") throw usage("--actions must be on or off");
49662
+ const c8 = ctx.client("user");
49663
+ const id2 = await ctx.repoId(spec);
49664
+ const current = await c8.repos.get(id2);
49665
+ ctx.result(await c8.repos.update(id2, { actions_enabled: o.actions === "on", version: current.version }), renderRepo2);
49666
+ })
49667
+ );
49668
+ repo.command("log [repo]").description("First-parent commit history of a ref; with --path, only commits that changed that file or directory (no rename detection); filter like git log with --author, --grep, --since, --until (substring matches) and --all-parents (with --path: git log --full-history)").option("--ref <ref>", "branch, tag or commit (default branch when omitted)").option("--path <path>", "repository-relative file or directory").option("--limit <n>", "page size", (v) => parseIntOption(v, "--limit")).option("--scan-limit <n>", "with --path or a filter: commits examined per page (server default 200, 50 with --all-parents; max 500)", (v) => parseIntOption(v, "--scan-limit")).option("--author <text>", "author name or email contains this (case-insensitive)").option("--grep <text>", "commit message contains this (case-insensitive; not a regular expression)").option("--since <date>", "committed at or after this date (ISO-8601)").option("--until <date>", "committed at or before this date (ISO-8601)").option("--all-parents", "walk merged branches too, not just the first-parent line").option("--cursor <cursor>", "page cursor (resumes a --path walk where it stopped)").action(
49494
49669
  act(rt, async (ctx, o, [spec]) => {
49495
- if (o.scanLimit !== void 0 && o.path === void 0) throw usage("--scan-limit requires --path");
49496
- const page = await ctx.client("agent").repos.commits(await ctx.repoId(spec), { ref: o.ref, path: o.path, limit: o.limit, cursor: o.cursor, scan_limit: o.scanLimit });
49670
+ const filtered = o.author !== void 0 || o.grep !== void 0 || o.since !== void 0 || o.until !== void 0 || !!o.allParents;
49671
+ if (o.scanLimit !== void 0 && o.path === void 0 && !filtered) throw usage("--scan-limit requires --path or a filter");
49672
+ const page = await ctx.client("agent").repos.commits(await ctx.repoId(spec), {
49673
+ ref: o.ref,
49674
+ path: o.path,
49675
+ limit: o.limit,
49676
+ cursor: o.cursor,
49677
+ scan_limit: o.scanLimit,
49678
+ author: o.author,
49679
+ grep: o.grep,
49680
+ since: o.since,
49681
+ until: o.until,
49682
+ all_parents: o.allParents || void 0
49683
+ });
49497
49684
  ctx.result(page, renderLog);
49498
49685
  })
49499
49686
  );
49687
+ repo.command("tree [repo]").description("List a directory of a ref without a clone").option("--ref <ref>", "branch, tag or commit (default branch when omitted)").option("--path <path>", "repository-relative directory (root when omitted)").action(
49688
+ act(rt, async (ctx, o, [spec]) => {
49689
+ ctx.result(await ctx.client("agent").repos.tree(await ctx.repoId(spec), { ref: o.ref, path: o.path }), renderTree);
49690
+ })
49691
+ );
49692
+ repo.command("blob <path> [repo]").description("Print a file of a ref without a clone (binary and very large files are described, not printed)").option("--ref <ref>", "branch, tag or commit (default branch when omitted)").action(
49693
+ act(rt, async (ctx, o, [path3, spec]) => {
49694
+ ctx.result(await ctx.client("agent").repos.blob(await ctx.repoId(spec), { ref: o.ref, path: path3 }), renderBlob);
49695
+ })
49696
+ );
49697
+ repo.command("blame <path> [repo]").description("Who last changed each line of a file, without a clone").option("--ref <ref>", "branch, tag or commit (default branch when omitted)").action(
49698
+ act(rt, async (ctx, o, [path3, spec]) => {
49699
+ ctx.result(await ctx.client("agent").repos.blame(await ctx.repoId(spec), { ref: o.ref, path: path3 }), renderBlame);
49700
+ })
49701
+ );
49702
+ repo.command("search <query> [repo]").description("Search code, tasks, changes and comments; with --ref, code as of that branch, tag or commit (says when a ref was searched incompletely)").option("--kind <kind>", "code, task, change or comment").option("--ref <ref>", "search code as of this branch, tag or commit instead of the default branch").action(
49703
+ act(rt, async (ctx, o, [q, spec]) => {
49704
+ if (o.kind !== void 0 && !["code", "task", "change", "comment"].includes(o.kind)) throw usage("--kind must be code, task, change or comment");
49705
+ if (o.ref !== void 0 && o.kind !== void 0 && o.kind !== "code") throw usage("--ref searches code only");
49706
+ ctx.result(await ctx.client("agent").repos.search(await ctx.repoId(spec), { q, kind: o.kind, ref: o.ref }), renderSearch);
49707
+ })
49708
+ );
49709
+ repo.command("compare <base> <head> [repo]").description("Files head changed since its merge base with base (git diff base...head); --two-dot compares the trees; --commits lists the commits in base..head with ahead/behind counts").option("--two-dot", "diff base's tree against head's tree (git diff base head)").option("--commits", "list the commits in base..head instead of the files").option("--limit <n>", "with --commits: commits listed (default 100, max 250)", (v) => parseIntOption(v, "--limit")).action(
49710
+ act(rt, async (ctx, o, [base2, head, spec]) => {
49711
+ if (o.commits && o.twoDot) throw usage("--commits cannot be combined with --two-dot");
49712
+ if (o.limit !== void 0 && !o.commits) throw usage("--limit requires --commits");
49713
+ const c8 = ctx.client("agent");
49714
+ const id2 = await ctx.repoId(spec);
49715
+ if (o.commits) return ctx.result(await c8.repos.compareCommits(id2, { base: base2, head, limit: o.limit }), renderRange);
49716
+ ctx.result(await c8.repos.compare(id2, { base: base2, head, mode: o.twoDot ? "two-dot" : void 0 }), renderCompare);
49717
+ })
49718
+ );
49719
+ repo.command("merge-base <base> <head> [repo]").description("Every best common ancestor of two branches, tags or commits (git merge-base --all), without a clone").action(
49720
+ act(rt, async (ctx, _o, [base2, head, spec]) => {
49721
+ ctx.result(await ctx.client("agent").repos.mergeBase(await ctx.repoId(spec), { base: base2, head }), renderMergeBase);
49722
+ })
49723
+ );
49724
+ repo.command("is-ancestor <ancestor> <descendant> [repo]").description("Whether a commit is in another's history (git merge-base --is-ancestor): yes, no, or unknown when the bounded walk cannot decide").action(
49725
+ act(rt, async (ctx, _o, [ancestor, descendant, spec]) => {
49726
+ ctx.result(await ctx.client("agent").repos.isAncestor(await ctx.repoId(spec), { ancestor, descendant }), renderAncestry);
49727
+ })
49728
+ );
49729
+ repo.command("contains <commit> [repo]").description("Branches and tags whose history contains a commit (git for-each-ref --contains), without a clone").action(
49730
+ act(rt, async (ctx, _o, [commit, spec]) => {
49731
+ ctx.result(await ctx.client("agent").repos.refsContaining(await ctx.repoId(spec), commit), renderContaining);
49732
+ })
49733
+ );
49734
+ repo.command("commit-change <commit> [repo]").description("The change that brought a commit onto each branch it landed on, from Brnch's record of the landing (every merge strategy); ask this instead of searching git log").action(
49735
+ act(rt, async (ctx, _o, [commit, spec]) => {
49736
+ let oid = /^[0-9a-f]{40}$/.test(commit) ? commit : null;
49737
+ if (!oid) {
49738
+ const r = await ctx.deps.gitOutput(["rev-parse", "--verify", "--quiet", `${commit}^{commit}`], ctx.deps.cwd);
49739
+ if (r.code !== 0) throw usage("pass a full 40-character commit id; short ids and revisions like HEAD resolve only inside a clone");
49740
+ oid = r.stdout.trim();
49741
+ }
49742
+ ctx.result(await ctx.client("agent").repos.commitChange(await ctx.repoId(spec), oid), renderCommitChange);
49743
+ })
49744
+ );
49500
49745
  repo.command("clone [repo] [directory]").description("git clone over SSH when this machine is set up for it (`ssh setup`) and the key can be used now, else HTTPS with your login (never written to .git/config); at a terminal without a repository, pick one of yours").option("--ssh", "clone over SSH").option("--https", "clone over HTTPS").action(
49501
49746
  act(rt, async (ctx, o, [spec, dir]) => {
49502
49747
  if (o.ssh && o.https) throw usage("--ssh and --https are mutually exclusive");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brnch-cli",
3
- "version": "0.19.0",
3
+ "version": "0.19.1",
4
4
  "description": "Brnch CLI: Git hosting for humans and agents, from the command line",
5
5
  "keywords": [
6
6
  "git",