@4pm/cli 1.23.0 → 1.24.0

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 (2) hide show
  1. package/dist/index.js +599 -213
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5713,14 +5713,14 @@ var require_react_development = __commonJS({
5713
5713
  var thenableResult = result;
5714
5714
  var wasAwaited = false;
5715
5715
  var thenable = {
5716
- then: function(resolve11, reject) {
5716
+ then: function(resolve12, reject) {
5717
5717
  wasAwaited = true;
5718
5718
  thenableResult.then(function(returnValue2) {
5719
5719
  popActScope(prevActScopeDepth);
5720
5720
  if (actScopeDepth === 0) {
5721
- recursivelyFlushAsyncActWork(returnValue2, resolve11, reject);
5721
+ recursivelyFlushAsyncActWork(returnValue2, resolve12, reject);
5722
5722
  } else {
5723
- resolve11(returnValue2);
5723
+ resolve12(returnValue2);
5724
5724
  }
5725
5725
  }, function(error2) {
5726
5726
  popActScope(prevActScopeDepth);
@@ -5750,20 +5750,20 @@ var require_react_development = __commonJS({
5750
5750
  ReactCurrentActQueue.current = null;
5751
5751
  }
5752
5752
  var _thenable = {
5753
- then: function(resolve11, reject) {
5753
+ then: function(resolve12, reject) {
5754
5754
  if (ReactCurrentActQueue.current === null) {
5755
5755
  ReactCurrentActQueue.current = [];
5756
- recursivelyFlushAsyncActWork(returnValue, resolve11, reject);
5756
+ recursivelyFlushAsyncActWork(returnValue, resolve12, reject);
5757
5757
  } else {
5758
- resolve11(returnValue);
5758
+ resolve12(returnValue);
5759
5759
  }
5760
5760
  }
5761
5761
  };
5762
5762
  return _thenable;
5763
5763
  } else {
5764
5764
  var _thenable2 = {
5765
- then: function(resolve11, reject) {
5766
- resolve11(returnValue);
5765
+ then: function(resolve12, reject) {
5766
+ resolve12(returnValue);
5767
5767
  }
5768
5768
  };
5769
5769
  return _thenable2;
@@ -5779,7 +5779,7 @@ var require_react_development = __commonJS({
5779
5779
  actScopeDepth = prevActScopeDepth;
5780
5780
  }
5781
5781
  }
5782
- function recursivelyFlushAsyncActWork(returnValue, resolve11, reject) {
5782
+ function recursivelyFlushAsyncActWork(returnValue, resolve12, reject) {
5783
5783
  {
5784
5784
  var queue = ReactCurrentActQueue.current;
5785
5785
  if (queue !== null) {
@@ -5788,16 +5788,16 @@ var require_react_development = __commonJS({
5788
5788
  enqueueTask(function() {
5789
5789
  if (queue.length === 0) {
5790
5790
  ReactCurrentActQueue.current = null;
5791
- resolve11(returnValue);
5791
+ resolve12(returnValue);
5792
5792
  } else {
5793
- recursivelyFlushAsyncActWork(returnValue, resolve11, reject);
5793
+ recursivelyFlushAsyncActWork(returnValue, resolve12, reject);
5794
5794
  }
5795
5795
  });
5796
5796
  } catch (error2) {
5797
5797
  reject(error2);
5798
5798
  }
5799
5799
  } else {
5800
- resolve11(returnValue);
5800
+ resolve12(returnValue);
5801
5801
  }
5802
5802
  }
5803
5803
  }
@@ -16203,9 +16203,9 @@ var require_react_reconciler_development = __commonJS({
16203
16203
  var node = row;
16204
16204
  while (node !== null) {
16205
16205
  if (node.tag === SuspenseComponent) {
16206
- var state = node.memoizedState;
16207
- if (state !== null) {
16208
- var dehydrated = state.dehydrated;
16206
+ var state2 = node.memoizedState;
16207
+ if (state2 !== null) {
16208
+ var dehydrated = state2.dehydrated;
16209
16209
  if (dehydrated === null || isSuspenseInstancePending(dehydrated) || isSuspenseInstanceFallback(dehydrated)) {
16210
16210
  return node;
16211
16211
  }
@@ -16549,8 +16549,8 @@ var require_react_reconciler_development = __commonJS({
16549
16549
  stores: null
16550
16550
  };
16551
16551
  }
16552
- function basicStateReducer(state, action) {
16553
- return typeof action === "function" ? action(state) : action;
16552
+ function basicStateReducer(state2, action) {
16553
+ return typeof action === "function" ? action(state2) : action;
16554
16554
  }
16555
16555
  function mountReducer(reducer, initialArg, init) {
16556
16556
  var hook = mountWorkInProgressHook();
@@ -18537,10 +18537,10 @@ var require_react_reconciler_development = __commonJS({
18537
18537
  }
18538
18538
  }
18539
18539
  }
18540
- var state = workInProgress2.memoizedState = instance.state !== null && instance.state !== void 0 ? instance.state : null;
18540
+ var state2 = workInProgress2.memoizedState = instance.state !== null && instance.state !== void 0 ? instance.state : null;
18541
18541
  adoptClassInstance(workInProgress2, instance);
18542
18542
  {
18543
- if (typeof ctor.getDerivedStateFromProps === "function" && state === null) {
18543
+ if (typeof ctor.getDerivedStateFromProps === "function" && state2 === null) {
18544
18544
  var componentName = getComponentNameFromType(ctor) || "Component";
18545
18545
  if (!didWarnAboutUninitializedState.has(componentName)) {
18546
18546
  didWarnAboutUninitializedState.add(componentName);
@@ -19463,8 +19463,8 @@ var require_react_reconciler_development = __commonJS({
19463
19463
  var _instance = workInProgress2.stateNode;
19464
19464
  var ctor = workInProgress2.type;
19465
19465
  var tempInstance = new ctor(workInProgress2.memoizedProps, _instance.context);
19466
- var state = tempInstance.state;
19467
- _instance.updater.enqueueSetState(_instance, state, null);
19466
+ var state2 = tempInstance.state;
19467
+ _instance.updater.enqueueSetState(_instance, state2, null);
19468
19468
  break;
19469
19469
  }
19470
19470
  case true: {
@@ -20239,8 +20239,8 @@ var require_react_reconciler_development = __commonJS({
20239
20239
  var node = firstChild;
20240
20240
  while (node !== null) {
20241
20241
  if (node.tag === SuspenseComponent) {
20242
- var state = node.memoizedState;
20243
- if (state !== null) {
20242
+ var state2 = node.memoizedState;
20243
+ if (state2 !== null) {
20244
20244
  scheduleSuspenseWorkOnFiber(node, renderLanes2, workInProgress2);
20245
20245
  }
20246
20246
  } else if (node.tag === SuspenseListComponent) {
@@ -20669,9 +20669,9 @@ var require_react_reconciler_development = __commonJS({
20669
20669
  }
20670
20670
  break;
20671
20671
  case SuspenseComponent: {
20672
- var state = workInProgress2.memoizedState;
20673
- if (state !== null) {
20674
- if (state.dehydrated !== null) {
20672
+ var state2 = workInProgress2.memoizedState;
20673
+ if (state2 !== null) {
20674
+ if (state2.dehydrated !== null) {
20675
20675
  pushSuspenseContext(workInProgress2, setDefaultShallowSuspenseContext(suspenseStackCursor.current));
20676
20676
  workInProgress2.flags |= DidCapture;
20677
20677
  return null;
@@ -32755,7 +32755,7 @@ async function checkAndUpdate(serverUrl, autoUpdate) {
32755
32755
  init_wrapper();
32756
32756
  import { randomUUID as randomUUID3 } from "crypto";
32757
32757
  import { basename as basename7, join as join32 } from "path";
32758
- import { existsSync as existsSync17, mkdirSync as mkdirSync8 } from "fs";
32758
+ import { existsSync as existsSync18, mkdirSync as mkdirSync9 } from "fs";
32759
32759
 
32760
32760
  // vendor/@4pm/utils/helpers.ts
32761
32761
  function backoffJitterMs(attempt, baseMs = 500, maxMs = 1e4) {
@@ -37017,6 +37017,39 @@ var CLI_SLASH_COMMANDS = [
37017
37017
  ];
37018
37018
  var CLI_SLASH_COMMAND_NAMES = CLI_SLASH_COMMANDS.map((c) => c.name);
37019
37019
 
37020
+ // vendor/@4pm/dto/git-credential.ts
37021
+ var hostSchema = external_exports.string().trim().toLowerCase().regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+(:\d{1,5})?$/, "invalid host");
37022
+ var gitCredentialRepoInputSchema = external_exports.object({
37023
+ url: external_exports.string().trim().max(500),
37024
+ subdir: external_exports.string().trim().max(120).optional().default(""),
37025
+ primary: external_exports.boolean().optional().default(false)
37026
+ });
37027
+ var testGitCredentialRequestSchema = external_exports.object({
37028
+ repos: external_exports.array(gitCredentialRepoInputSchema).max(50).optional()
37029
+ });
37030
+ var putGitCredentialRequestSchema = external_exports.object({
37031
+ appId: external_exports.string().trim().regex(/^\d{1,20}$/, "App ID must be numeric"),
37032
+ /** PEM; omit to keep the stored key (required on first save — enforced server-side). */
37033
+ privateKey: external_exports.string().trim().max(16384).optional(),
37034
+ enterprise: external_exports.boolean().default(false),
37035
+ host: hostSchema.optional(),
37036
+ apiBaseUrl: external_exports.string().trim().url().startsWith("https://").max(500).nullable().optional(),
37037
+ caCert: external_exports.string().trim().max(32768).nullable().optional(),
37038
+ installationOverrides: external_exports.array(external_exports.object({ url: external_exports.string().trim().max(500), installationId: external_exports.string().trim().regex(/^\d{1,20}$/).nullable() })).max(50).optional(),
37039
+ /** The wizard's in-progress repos (else the project's spec / draft repos are used). */
37040
+ repos: external_exports.array(gitCredentialRepoInputSchema).max(50).optional()
37041
+ }).superRefine((v2, ctx) => {
37042
+ if (v2.enterprise && !v2.host) {
37043
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["host"], message: "host is required for GitHub Enterprise" });
37044
+ }
37045
+ });
37046
+ function githubHostKind(host) {
37047
+ const h = host.toLowerCase();
37048
+ if (h === "github.com") return "github";
37049
+ if (h.endsWith(".ghe.com")) return "ghe-cloud";
37050
+ return "ghes";
37051
+ }
37052
+
37020
37053
  // vendor/@4pm/validation/schemas.ts
37021
37054
  var passwordSchema = external_exports.string().min(8, "password: at least 8 characters").max(32, "password: at most 32 characters").regex(/[a-zA-Z]/, "password: needs at least 1 letter").regex(/[0-9]/, "password: needs at least 1 digit");
37022
37055
  var usernameSchema = external_exports.string().min(3).max(32).regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/, "username: invalid characters");
@@ -38372,6 +38405,9 @@ var WsChannels = {
38372
38405
  // server → cli (reply): manage the rented worker's ssh deploy key (ADR-0173) — generate
38373
38406
  // (ed25519, on the worker) / get / delete. The private key never leaves the worker.
38374
38407
  GIT_SSH_KEY: "git.ssh-key",
38408
+ // cli → server (reply): a short-lived GitHub-App installation token for the served project's repo,
38409
+ // per job scope; also the cli's "scope revoked" report (ADR-0356). The App key stays on the server.
38410
+ GIT_TOKEN: "git.token",
38375
38411
  PROJECT_CREATE: "project.create",
38376
38412
  PROJECT_ADD: "project.add",
38377
38413
  PROJECT_PROGRESS: "project.progress",
@@ -38411,6 +38447,8 @@ var WsChannels = {
38411
38447
  // command.dispatch({ ai:true }) → the profile-failover AI path (ADR-0100).
38412
38448
  // quota & usage (ADR-0020)
38413
38449
  QUOTA_CHECK: "quota.check",
38450
+ // cli → server (reply): per-org concurrent AI-run slot — acquire / renew / release (ADR-0359)
38451
+ RUN_SLOT: "run.slot",
38414
38452
  USAGE_REPORT: "usage.report",
38415
38453
  // cli → server: Claude subscription usage snapshot (5h / weekly — ADR-0072)
38416
38454
  MACHINE_USAGE: "machine.usage",
@@ -38509,6 +38547,11 @@ function decryptPayload(key, payloadBase64, nonceBase64) {
38509
38547
  return JSON.parse(plaintext.toString("utf8"));
38510
38548
  }
38511
38549
 
38550
+ // vendor/@4pm/ws/messages/run-slot.ts
38551
+ var RUN_SLOT_RENEW_MS = 45e3;
38552
+ var RUN_SLOT_RETRY_MS = 3e3;
38553
+ var RUN_SLOT_MAX_WAIT_MS = 30 * 6e4;
38554
+
38512
38555
  // src/core/update-scheduler.ts
38513
38556
  import { spawn as spawn2 } from "child_process";
38514
38557
 
@@ -38625,8 +38668,8 @@ function resolveInstallTimeoutMs(sec) {
38625
38668
  return sec && sec > 0 ? sec * 1e3 : TOOL_INSTALL_TIMEOUT_DEFAULT_MS;
38626
38669
  }
38627
38670
  function sleep(ms2) {
38628
- return new Promise((resolve11) => {
38629
- const t2 = setTimeout(resolve11, ms2);
38671
+ return new Promise((resolve12) => {
38672
+ const t2 = setTimeout(resolve12, ms2);
38630
38673
  t2.unref?.();
38631
38674
  });
38632
38675
  }
@@ -38654,14 +38697,14 @@ function classifyToolFailure(code, out) {
38654
38697
  return { reason: "other", retryable: false };
38655
38698
  }
38656
38699
  function run3(cmd, args, onLine, timeoutMs = 18e4) {
38657
- return new Promise((resolve11) => {
38700
+ return new Promise((resolve12) => {
38658
38701
  let out = "";
38659
38702
  let buf = "";
38660
38703
  let child = null;
38661
38704
  const done = (code) => {
38662
38705
  clearTimeout(timer);
38663
38706
  if (onLine && buf.trim()) onLine(buf.trim());
38664
- resolve11({ code, out });
38707
+ resolve12({ code, out });
38665
38708
  };
38666
38709
  const timer = setTimeout(() => {
38667
38710
  child?.kill();
@@ -39044,11 +39087,11 @@ function getWorkingProfile(profileDir2, cmd) {
39044
39087
  return readState(profileDir2)[cmd] ?? null;
39045
39088
  }
39046
39089
  function setWorkingProfile(profileDir2, cmd, dir2) {
39047
- const state = readState(profileDir2);
39048
- if (state[cmd] === dir2) return;
39049
- state[cmd] = dir2;
39090
+ const state2 = readState(profileDir2);
39091
+ if (state2[cmd] === dir2) return;
39092
+ state2[cmd] = dir2;
39050
39093
  try {
39051
- writeFileSync4(statePath(profileDir2), JSON.stringify(state, null, 2), "utf8");
39094
+ writeFileSync4(statePath(profileDir2), JSON.stringify(state2, null, 2), "utf8");
39052
39095
  } catch {
39053
39096
  }
39054
39097
  }
@@ -39057,11 +39100,11 @@ function getWorkingCredential(profileDir2) {
39057
39100
  return readState(profileDir2)[WORKING_CREDENTIAL_KEY] ?? null;
39058
39101
  }
39059
39102
  function setWorkingCredential(profileDir2, key) {
39060
- const state = readState(profileDir2);
39061
- if (state[WORKING_CREDENTIAL_KEY] === key) return;
39062
- state[WORKING_CREDENTIAL_KEY] = key;
39103
+ const state2 = readState(profileDir2);
39104
+ if (state2[WORKING_CREDENTIAL_KEY] === key) return;
39105
+ state2[WORKING_CREDENTIAL_KEY] = key;
39063
39106
  try {
39064
- writeFileSync4(statePath(profileDir2), JSON.stringify(state, null, 2), "utf8");
39107
+ writeFileSync4(statePath(profileDir2), JSON.stringify(state2, null, 2), "utf8");
39065
39108
  } catch {
39066
39109
  }
39067
39110
  }
@@ -39070,20 +39113,20 @@ function getPinnedCredential(profileDir2) {
39070
39113
  return readState(profileDir2)[PINNED_CREDENTIAL_KEY] ?? null;
39071
39114
  }
39072
39115
  function setPinnedCredential(profileDir2, key) {
39073
- const state = readState(profileDir2);
39074
- if (state[PINNED_CREDENTIAL_KEY] === key) return;
39075
- state[PINNED_CREDENTIAL_KEY] = key;
39116
+ const state2 = readState(profileDir2);
39117
+ if (state2[PINNED_CREDENTIAL_KEY] === key) return;
39118
+ state2[PINNED_CREDENTIAL_KEY] = key;
39076
39119
  try {
39077
- writeFileSync4(statePath(profileDir2), JSON.stringify(state, null, 2), "utf8");
39120
+ writeFileSync4(statePath(profileDir2), JSON.stringify(state2, null, 2), "utf8");
39078
39121
  } catch {
39079
39122
  }
39080
39123
  }
39081
39124
  function clearPinnedCredential(profileDir2) {
39082
- const state = readState(profileDir2);
39083
- if (!(PINNED_CREDENTIAL_KEY in state)) return;
39084
- delete state[PINNED_CREDENTIAL_KEY];
39125
+ const state2 = readState(profileDir2);
39126
+ if (!(PINNED_CREDENTIAL_KEY in state2)) return;
39127
+ delete state2[PINNED_CREDENTIAL_KEY];
39085
39128
  try {
39086
- writeFileSync4(statePath(profileDir2), JSON.stringify(state, null, 2), "utf8");
39129
+ writeFileSync4(statePath(profileDir2), JSON.stringify(state2, null, 2), "utf8");
39087
39130
  } catch {
39088
39131
  }
39089
39132
  }
@@ -39438,16 +39481,16 @@ async function fetchUsage(token) {
39438
39481
  }
39439
39482
  }
39440
39483
  function runUsageCli(dir2, aiCli2) {
39441
- return new Promise((resolve11) => {
39484
+ return new Promise((resolve12) => {
39442
39485
  let out = "";
39443
39486
  let child = null;
39444
39487
  const finish = () => {
39445
39488
  clearTimeout(timer);
39446
- resolve11(out);
39489
+ resolve12(out);
39447
39490
  };
39448
39491
  const timer = setTimeout(() => {
39449
39492
  child?.kill();
39450
- resolve11(out);
39493
+ resolve12(out);
39451
39494
  }, 3e4);
39452
39495
  timer.unref?.();
39453
39496
  try {
@@ -39937,6 +39980,7 @@ var NO_USAGE = { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0
39937
39980
  var execFileAsync = promisify3(execFile3);
39938
39981
  var MAX_CONTEXT_CHARS = 12e4;
39939
39982
  var ANSWER_TIMEOUT_MS = 12e4;
39983
+ var LEGAL_DRAFT_TIMEOUT_MS = 54e4;
39940
39984
  var DOC_EXTENSIONS = [".md", ".mdx", ".txt"];
39941
39985
  function kbRoot(profileDir2) {
39942
39986
  return join14(profileDir2, "support-kb");
@@ -40048,6 +40092,7 @@ function buildPrompt(docs, question, askerRole) {
40048
40092
  ].join("\n");
40049
40093
  }
40050
40094
  function buildDraftPrompt(docs, context, task) {
40095
+ if (task === "legal_draft") return buildLegalDraftPrompt(docs, context);
40051
40096
  const goal = task === "reply_draft" ? [
40052
40097
  "You are drafting a reply FROM the 4PM support team TO the customer in the support ticket below.",
40053
40098
  "Address the customer's latest message, using the whole conversation for context. Be accurate,",
@@ -40074,6 +40119,31 @@ function buildDraftPrompt(docs, context, task) {
40074
40119
  context
40075
40120
  ].join("\n");
40076
40121
  }
40122
+ function buildLegalDraftPrompt(docs, context) {
40123
+ return [
40124
+ "You are drafting the body of one of 4PM's legal documents (Terms of Service, Privacy Policy,",
40125
+ "Add-on Terms, Rented Machine Addendum, \u2026) for the platform admin to review and publish.",
40126
+ "Write or revise the WHOLE document body in the target locale given in the context. Keep the",
40127
+ "existing heading structure and numbering unless the admin's instructions say otherwise.",
40128
+ "When the target locale is not English and an English reference is given, follow its meaning closely.",
40129
+ "Rules:",
40130
+ "- Every {{placeholder}} (e.g. {{companyName}}) must be kept verbatim \u2014 never fill or rename it.",
40131
+ "- Describe plans, prices, upgrades, downgrades, cancellations, renewals, refunds and add-ons ONLY",
40132
+ " as stated in the PLAN CATALOG and BILLING FACTS below or the documentation; never invent",
40133
+ " commitments, prices or legal guarantees. Where something is unknown write `[TODO: \u2026]`.",
40134
+ "- Plain, precise legal English (or the target language); short numbered sections and bullet lists.",
40135
+ "- This is a draft, not legal advice; do not add a disclaimer about that inside the document.",
40136
+ "",
40137
+ "Output ONLY the document body in markdown \u2014 no title line, no preamble, no explanation, no code",
40138
+ "fences around the whole document.",
40139
+ "",
40140
+ "===== DOCUMENTATION =====",
40141
+ docs,
40142
+ "",
40143
+ "===== CONTEXT =====",
40144
+ context
40145
+ ].join("\n");
40146
+ }
40077
40147
  function imageExt(mime) {
40078
40148
  switch (mime) {
40079
40149
  case "image/png":
@@ -40115,8 +40185,8 @@ function parseModeratedAnswer(text) {
40115
40185
  }
40116
40186
  return { body: text.trim() };
40117
40187
  }
40118
- function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = []) {
40119
- return new Promise((resolve11, reject) => {
40188
+ function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = [], timeoutMs = ANSWER_TIMEOUT_MS) {
40189
+ return new Promise((resolve12, reject) => {
40120
40190
  const isClaude = cmd.includes("claude");
40121
40191
  const args = [
40122
40192
  "-p",
@@ -40147,7 +40217,7 @@ function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = [])
40147
40217
  const timer = setTimeout(() => {
40148
40218
  child.kill("SIGKILL");
40149
40219
  reject(new Error("claude answer timed out"));
40150
- }, ANSWER_TIMEOUT_MS);
40220
+ }, timeoutMs);
40151
40221
  timer.unref();
40152
40222
  child.stdout.on("data", (d) => out += parser.push(d.toString()));
40153
40223
  child.stderr.on("data", (d) => err += d.toString());
@@ -40160,17 +40230,25 @@ function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = [])
40160
40230
  clearTimeout(timer);
40161
40231
  cleanup();
40162
40232
  out += parser.flush();
40163
- resolve11({ code: code ?? -1, out, err, usage: parser.usage() });
40233
+ resolve12({ code: code ?? -1, out, err, usage: parser.usage() });
40164
40234
  });
40165
40235
  child.stdin.write(prompt);
40166
40236
  child.stdin.end();
40167
40237
  });
40168
40238
  }
40169
- async function runClaudeWithFailover(prompt, ai2, imageDir) {
40239
+ async function runClaudeWithFailover(prompt, ai2, imageDir, timeoutMs = ANSWER_TIMEOUT_MS) {
40170
40240
  const attempts = ai2.profiles.length > 0 ? ai2.profiles : [null];
40171
40241
  let lastReason = "no attempt";
40172
40242
  for (let i = 0; i < attempts.length; i++) {
40173
- const { code, out, err, usage } = await runClaudeOnce(ai2.cmd, attempts[i], prompt, ai2.env, imageDir, ai2.profiles.map((p2) => p2.dir));
40243
+ const { code, out, err, usage } = await runClaudeOnce(
40244
+ ai2.cmd,
40245
+ attempts[i],
40246
+ prompt,
40247
+ ai2.env,
40248
+ imageDir,
40249
+ ai2.profiles.map((p2) => p2.dir),
40250
+ timeoutMs
40251
+ );
40174
40252
  if (code === 0 && out.trim()) return { text: out.trim(), usage };
40175
40253
  const combined = err || out;
40176
40254
  lastReason = `exited ${code}: ${combined.slice(0, 500)}`;
@@ -40191,7 +40269,8 @@ async function runSupportAnswer(req, ai2, profileDir2) {
40191
40269
  const docs = collectDocs(dir2);
40192
40270
  if (!docs.trim()) return { body: "", error: "KB repo has no documentation" };
40193
40271
  const prompt = req.task ? buildDraftPrompt(docs, question, req.task) : buildPrompt(docs, question, req.askerRole);
40194
- const { text, usage } = await runClaudeWithFailover(prompt, ai2, imageDir);
40272
+ const timeoutMs = req.task === "legal_draft" ? LEGAL_DRAFT_TIMEOUT_MS : ANSWER_TIMEOUT_MS;
40273
+ const { text, usage } = await runClaudeWithFailover(prompt, ai2, imageDir, timeoutMs);
40195
40274
  if (!text) return { body: "", error: "empty answer" };
40196
40275
  const { body, moderation } = req.task ? { body: text.trim(), moderation: void 0 } : parseModeratedAnswer(text);
40197
40276
  if (!body) return { body: "", error: "empty answer" };
@@ -40428,10 +40507,10 @@ async function crontabList() {
40428
40507
  }
40429
40508
  }
40430
40509
  async function crontabSet(content) {
40431
- return new Promise((resolve11) => {
40510
+ return new Promise((resolve12) => {
40432
40511
  const child = spawn5("crontab", ["-"], { stdio: ["pipe", "ignore", "ignore"] });
40433
- child.on("error", () => resolve11(false));
40434
- child.on("close", (code) => resolve11(code === 0));
40512
+ child.on("error", () => resolve12(false));
40513
+ child.on("close", (code) => resolve12(code === 0));
40435
40514
  child.stdin.end(content.endsWith("\n") ? content : content + "\n");
40436
40515
  });
40437
40516
  }
@@ -40600,14 +40679,14 @@ function detectContainerized() {
40600
40679
  }
40601
40680
  }
40602
40681
  function canReach(host, port) {
40603
- return new Promise((resolve11) => {
40682
+ return new Promise((resolve12) => {
40604
40683
  const socket = connect({ host, port });
40605
40684
  let done = false;
40606
40685
  const finish = (ok) => {
40607
40686
  if (done) return;
40608
40687
  done = true;
40609
40688
  socket.destroy();
40610
- resolve11(ok);
40689
+ resolve12(ok);
40611
40690
  };
40612
40691
  socket.setTimeout(PROBE_TIMEOUT_MS);
40613
40692
  socket.once("connect", () => finish(true));
@@ -40660,32 +40739,177 @@ async function probeNetwork() {
40660
40739
  }
40661
40740
 
40662
40741
  // src/core/git-auth.ts
40663
- import { writeFileSync as writeFileSync8 } from "fs";
40664
- import { homedir as homedir4 } from "os";
40665
- import { join as join18 } from "path";
40666
- function tokenTarget(method) {
40667
- if (method === "gitlab-group-token") {
40668
- return { host: process.env.FOURPM_GIT_HOST || "gitlab.com", user: "oauth2" };
40669
- }
40670
- if (method === "github-app") {
40671
- return { host: process.env.FOURPM_GIT_HOST || "github.com", user: "x-access-token" };
40742
+ import { chmodSync as chmodSync3, existsSync as existsSync12, mkdirSync as mkdirSync6, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
40743
+ import { request as httpsRequest } from "https";
40744
+ import { delimiter, dirname as dirname4, join as join18, resolve as resolve3 } from "path";
40745
+ var JOB_ID_ENV = "FOURPM_JOB_ID";
40746
+ var PROFILE_DIR_ENV = "FOURPM_PROFILE_DIR";
40747
+ var GIT_HOST_ENV = "FOURPM_GIT_HOST";
40748
+ var GIT_HOST_KIND_ENV = "FOURPM_GIT_HOST_KIND";
40749
+ var DEFAULT_SCOPE = "link";
40750
+ var HELPER_ENV_ENTRIES = 3;
40751
+ var DEFAULT_SCOPE_IDLE_MS = 10 * 60 * 1e3;
40752
+ var state = { host: null, transport: null, baseEnv: null, scopes: /* @__PURE__ */ new Map(), defaultTimer: null };
40753
+ function cliInvocation() {
40754
+ const quote = (s3) => `"${s3.replace(/(["\\$`])/g, "\\$1")}"`;
40755
+ const script = process.argv[1];
40756
+ return [process.execPath, ...process.execArgv, ...script ? [script] : []].map(quote).join(" ");
40757
+ }
40758
+ function helperCommand() {
40759
+ return `!${cliInvocation()} git-credential`;
40760
+ }
40761
+ function findRealGh(pathValue, shimDir) {
40762
+ for (const dir2 of pathValue.split(delimiter)) {
40763
+ if (!dir2 || dir2 === shimDir) continue;
40764
+ const candidate = join18(dir2, process.platform === "win32" ? "gh.exe" : "gh");
40765
+ if (existsSync12(candidate)) return candidate;
40672
40766
  }
40673
40767
  return null;
40674
40768
  }
40675
- function applyGitAuth(method) {
40676
- if (!method) return false;
40677
- const target = tokenTarget(method);
40678
- const token = process.env.FOURPM_GIT_TOKEN;
40679
- if (!target || !token) return false;
40680
- try {
40681
- const line = `https://${target.user}:${token}@${target.host}
40769
+ function shimScript(realGh, cli) {
40770
+ return `#!/bin/sh
40771
+ # 4PM gh shim (ADR-0356) \u2014 injects a short-lived GitHub-App token for the served project.
40772
+ if [ -z "$GH_TOKEN$GH_ENTERPRISE_TOKEN$GITHUB_TOKEN" ]; then
40773
+ T="$(${cli} git-token 2>/dev/null)"
40774
+ if [ -n "$T" ]; then
40775
+ case "$${GIT_HOST_KIND_ENV}" in
40776
+ ghes) export GH_HOST="$${GIT_HOST_ENV}" GH_ENTERPRISE_TOKEN="$T"; unset GH_TOKEN ;;
40777
+ ghe-cloud) export GH_HOST="$${GIT_HOST_ENV}" GH_TOKEN="$T"; unset GH_ENTERPRISE_TOKEN ;;
40778
+ *) export GH_TOKEN="$T"; unset GH_ENTERPRISE_TOKEN ;;
40779
+ esac
40780
+ fi
40781
+ fi
40782
+ exec "${realGh}" "$@"
40682
40783
  `;
40683
- writeFileSync8(join18(homedir4(), ".git-credentials"), line, { encoding: "utf8", mode: 384 });
40784
+ }
40785
+ function clearEnv() {
40786
+ if (!state.baseEnv) return;
40787
+ const { count, path } = state.baseEnv;
40788
+ for (let i = count; i < count + HELPER_ENV_ENTRIES; i++) {
40789
+ delete process.env[`GIT_CONFIG_KEY_${i}`];
40790
+ delete process.env[`GIT_CONFIG_VALUE_${i}`];
40791
+ }
40792
+ if (count > 0) process.env.GIT_CONFIG_COUNT = String(count);
40793
+ else delete process.env.GIT_CONFIG_COUNT;
40794
+ process.env.PATH = path;
40795
+ delete process.env[GIT_HOST_ENV];
40796
+ delete process.env[GIT_HOST_KIND_ENV];
40797
+ state.baseEnv = null;
40798
+ }
40799
+ function configureGitAuth(method, host, profileDir2, transport) {
40800
+ try {
40801
+ const active = method === "github-app" && !!host;
40802
+ if (!active || state.host && state.host !== host) {
40803
+ if (state.host) void revokeAll();
40804
+ clearEnv();
40805
+ state.host = null;
40806
+ }
40807
+ if (!active) return false;
40808
+ state.transport = transport;
40809
+ if (state.host === host && state.baseEnv) return true;
40810
+ const baseCount = Number(process.env.GIT_CONFIG_COUNT ?? "0") || 0;
40811
+ state.baseEnv = { count: baseCount, path: process.env.PATH ?? "" };
40812
+ const prefix = `credential.https://${host}`;
40813
+ const entries2 = [
40814
+ [`${prefix}.helper`, ""],
40815
+ [`${prefix}.helper`, helperCommand()],
40816
+ [`${prefix}.useHttpPath`, "true"]
40817
+ ];
40818
+ entries2.forEach(([key, value], i) => {
40819
+ process.env[`GIT_CONFIG_KEY_${baseCount + i}`] = key;
40820
+ process.env[`GIT_CONFIG_VALUE_${baseCount + i}`] = value;
40821
+ });
40822
+ process.env.GIT_CONFIG_COUNT = String(baseCount + HELPER_ENV_ENTRIES);
40823
+ process.env[PROFILE_DIR_ENV] = resolve3(profileDir2);
40824
+ const script = process.argv[1];
40825
+ const devTsconfig = script ? resolve3(dirname4(script), "..", "tsconfig.json") : "";
40826
+ if (process.execArgv.some((a) => a.includes("tsx")) && devTsconfig && existsSync12(devTsconfig) && !process.env.TSX_TSCONFIG_PATH) {
40827
+ process.env.TSX_TSCONFIG_PATH = devTsconfig;
40828
+ }
40829
+ process.env[GIT_HOST_ENV] = host;
40830
+ process.env[GIT_HOST_KIND_ENV] = githubHostKind(host);
40831
+ if (process.platform !== "win32") {
40832
+ const shimDir = join18(profileDir2, "bin");
40833
+ const realGh = findRealGh(state.baseEnv.path, shimDir);
40834
+ if (realGh) {
40835
+ mkdirSync6(shimDir, { recursive: true });
40836
+ const shim = join18(shimDir, "gh");
40837
+ writeFileSync8(shim, shimScript(realGh, cliInvocation()), { encoding: "utf8", mode: 493 });
40838
+ chmodSync3(shim, 493);
40839
+ process.env.PATH = `${shimDir}${delimiter}${state.baseEnv.path}`;
40840
+ } else {
40841
+ rmSync6(join18(shimDir, "gh"), { force: true });
40842
+ }
40843
+ }
40844
+ state.host = host;
40845
+ logger.info("git.auth.github-app", { host });
40684
40846
  return true;
40685
- } catch {
40847
+ } catch (err) {
40848
+ logger.warn("git.auth.configure.failed", { error: String(err) });
40686
40849
  return false;
40687
40850
  }
40688
40851
  }
40852
+ function touchDefaultScope() {
40853
+ if (state.defaultTimer) clearTimeout(state.defaultTimer);
40854
+ state.defaultTimer = setTimeout(() => void endGitScope(DEFAULT_SCOPE), DEFAULT_SCOPE_IDLE_MS);
40855
+ state.defaultTimer.unref?.();
40856
+ }
40857
+ async function issueGitToken(scope, host, path) {
40858
+ try {
40859
+ if (!state.host || !state.transport || host.toLowerCase() !== state.host) return null;
40860
+ const s3 = scope || DEFAULT_SCOPE;
40861
+ const reply = await state.transport({ op: "issue", scope: s3, repo: { host, path } });
40862
+ if (!reply.token) return null;
40863
+ const list = state.scopes.get(s3) ?? [];
40864
+ if (!list.some((t2) => t2.token === reply.token)) list.push({ token: reply.token, apiBase: reply.apiBase ?? "" });
40865
+ state.scopes.set(s3, list);
40866
+ if (s3 === DEFAULT_SCOPE) touchDefaultScope();
40867
+ return reply.token;
40868
+ } catch (err) {
40869
+ logger.warn("git.token.issue.failed", { error: String(err) });
40870
+ return null;
40871
+ }
40872
+ }
40873
+ function revokeAtGithub(t2) {
40874
+ return new Promise((resolve12) => {
40875
+ try {
40876
+ const req = httpsRequest(
40877
+ new URL(`${t2.apiBase.replace(/\/+$/, "")}/installation/token`),
40878
+ {
40879
+ method: "DELETE",
40880
+ timeout: 1e4,
40881
+ headers: { Authorization: `token ${t2.token}`, Accept: "application/vnd.github+json", "User-Agent": "4pm-cli" }
40882
+ },
40883
+ (res) => {
40884
+ res.resume();
40885
+ res.on("end", () => resolve12());
40886
+ }
40887
+ );
40888
+ req.on("timeout", () => req.destroy());
40889
+ req.on("error", () => resolve12());
40890
+ req.end();
40891
+ } catch {
40892
+ resolve12();
40893
+ }
40894
+ });
40895
+ }
40896
+ async function endGitScope(scope) {
40897
+ const tokens = state.scopes.get(scope);
40898
+ if (!tokens) return;
40899
+ state.scopes.delete(scope);
40900
+ if (scope === DEFAULT_SCOPE && state.defaultTimer) {
40901
+ clearTimeout(state.defaultTimer);
40902
+ state.defaultTimer = null;
40903
+ }
40904
+ await Promise.all(tokens.filter((t2) => t2.apiBase).map((t2) => revokeAtGithub(t2)));
40905
+ try {
40906
+ await state.transport?.({ op: "revoked", scope });
40907
+ } catch {
40908
+ }
40909
+ }
40910
+ async function revokeAll() {
40911
+ await Promise.all([...state.scopes.keys()].map((s3) => endGitScope(s3)));
40912
+ }
40689
40913
 
40690
40914
  // src/core/ws-client/transcript.ts
40691
40915
  function toDtoEntry(e) {
@@ -40716,13 +40940,18 @@ import { randomUUID as randomCommandId } from "crypto";
40716
40940
  // src/core/executor.ts
40717
40941
  import { spawn as spawn6 } from "child_process";
40718
40942
  var KILL_GRACE_MS = 5e3;
40719
- async function runCommand(dispatch, emit, opts) {
40943
+ async function runCommand(dispatch, emitRaw, opts) {
40720
40944
  let seq = 0;
40945
+ const emit = (output) => {
40946
+ emitRaw(output);
40947
+ if (output.done) void endGitScope(dispatch.commandId);
40948
+ };
40721
40949
  let child;
40722
40950
  try {
40723
40951
  child = spawn6(dispatch.cmd, dispatch.args ?? [], {
40724
40952
  cwd: dispatch.path,
40725
- env: { ...process.env, ...dispatch.env },
40953
+ // FOURPM_JOB_ID scopes the git helper / gh shim tokens to this run (ADR-0356).
40954
+ env: { ...process.env, ...dispatch.env, [JOB_ID_ENV]: dispatch.commandId },
40726
40955
  shell: false
40727
40956
  });
40728
40957
  } catch (err) {
@@ -40779,7 +41008,7 @@ async function runCommand(dispatch, emit, opts) {
40779
41008
  });
40780
41009
  child.stdout?.on("data", (data) => batcher.push(data.toString("utf8")));
40781
41010
  child.stderr?.on("data", (data) => batcher.push(data.toString("utf8")));
40782
- await new Promise((resolve11) => {
41011
+ await new Promise((resolve12) => {
40783
41012
  child.on("close", (exitCode) => {
40784
41013
  clearTimers();
40785
41014
  batcher.flush();
@@ -40792,7 +41021,7 @@ async function runCommand(dispatch, emit, opts) {
40792
41021
  // server settles the command as failed and the web surfaces the note instead of spinning.
40793
41022
  exitCode: timedOut ? 124 : exitCode ?? -1
40794
41023
  });
40795
- resolve11();
41024
+ resolve12();
40796
41025
  });
40797
41026
  child.on("error", (err) => {
40798
41027
  clearTimers();
@@ -40804,7 +41033,7 @@ async function runCommand(dispatch, emit, opts) {
40804
41033
  done: true,
40805
41034
  exitCode: -1
40806
41035
  });
40807
- resolve11();
41036
+ resolve12();
40808
41037
  });
40809
41038
  });
40810
41039
  }
@@ -40827,15 +41056,15 @@ function isOutOfCredits(text) {
40827
41056
  );
40828
41057
  }
40829
41058
  function runAttempt(commandId, cmd, args, stdin, cwd2, env3, onChunk, timeoutMs) {
40830
- return new Promise((resolve11) => {
41059
+ return new Promise((resolve12) => {
40831
41060
  runCommand(
40832
41061
  { commandId, cmd, args, path: cwd2, env: env3 },
40833
41062
  (out) => {
40834
41063
  if (out.chunk) onChunk(out.chunk);
40835
- if (out.done) resolve11(out.exitCode ?? -1);
41064
+ if (out.done) resolve12(out.exitCode ?? -1);
40836
41065
  },
40837
41066
  { stdin, ...timeoutMs > 0 ? { timeoutMs } : {} }
40838
- ).catch(() => resolve11(-1));
41067
+ ).catch(() => resolve12(-1));
40839
41068
  });
40840
41069
  }
40841
41070
  async function runAiFailover(plan, commandId, cwd2, handlers, timeoutMs = 0) {
@@ -40918,7 +41147,7 @@ function formatTimestampInZone(date, timeZone) {
40918
41147
  }
40919
41148
 
40920
41149
  // src/ui/slash-commands.ts
40921
- import { existsSync as existsSync12 } from "fs";
41150
+ import { existsSync as existsSync13 } from "fs";
40922
41151
  import { join as join19 } from "path";
40923
41152
  var CONFIG_KEYS = [
40924
41153
  "aiCli",
@@ -40981,7 +41210,7 @@ function runConfig(ctx) {
40981
41210
  ctx.print(t("slash.config.wroteDefault", { verb, path }));
40982
41211
  ctx.print(t("slash.config.addClaudeHint"));
40983
41212
  };
40984
- if (existsSync12(path)) {
41213
+ if (existsSync13(path)) {
40985
41214
  ctx.confirm(
40986
41215
  t("slash.config.replaceConfirm", { path }),
40987
41216
  () => writeDefaults(t("slash.config.verbReplaced"))
@@ -41043,8 +41272,8 @@ function runHistory(ctx) {
41043
41272
  ctx.print(t("slash.history.header", { n: recent.length, total: all.length }));
41044
41273
  recent.forEach((e, i) => {
41045
41274
  const time = new Date(e.startedAt).toLocaleTimeString();
41046
- const state = e.status === "done" ? "\u2713" : e.status === "failed" ? `\u2717 exit ${e.exitCode}` : t("slash.history.running");
41047
- ctx.print(` [${i + 1}] ${time} ${commandLine(e.cmd, e.args)} \xB7 ${state}`);
41275
+ const state2 = e.status === "done" ? "\u2713" : e.status === "failed" ? `\u2717 exit ${e.exitCode}` : t("slash.history.running");
41276
+ ctx.print(` [${i + 1}] ${time} ${commandLine(e.cmd, e.args)} \xB7 ${state2}`);
41048
41277
  });
41049
41278
  }
41050
41279
  function runOutput(ctx) {
@@ -41055,8 +41284,8 @@ function runOutput(ctx) {
41055
41284
  return;
41056
41285
  }
41057
41286
  const entry = all.slice().reverse()[n - 1];
41058
- const state = entry.exitCode != null ? ` (exit ${entry.exitCode})` : "";
41059
- ctx.print(t("slash.output.header", { n, cmd: commandLine(entry.cmd, entry.args), status: entry.status, state }));
41287
+ const state2 = entry.exitCode != null ? ` (exit ${entry.exitCode})` : "";
41288
+ ctx.print(t("slash.output.header", { n, cmd: commandLine(entry.cmd, entry.args), status: entry.status, state: state2 }));
41060
41289
  const out = readCommandOutput(entry.commandId);
41061
41290
  if (out == null) {
41062
41291
  ctx.print(t("slash.output.none"), "warn");
@@ -41351,7 +41580,7 @@ function buildPrompt2(input) {
41351
41580
  ].join("\n");
41352
41581
  }
41353
41582
  function runOnce(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
41354
- return new Promise((resolve11, reject) => {
41583
+ return new Promise((resolve12, reject) => {
41355
41584
  const args = [
41356
41585
  "-p",
41357
41586
  "--output-format",
@@ -41386,7 +41615,7 @@ function runOnce(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
41386
41615
  });
41387
41616
  child.on("close", (code) => {
41388
41617
  clearTimeout(timer);
41389
- resolve11({ code: code ?? -1, out, err });
41618
+ resolve12({ code: code ?? -1, out, err });
41390
41619
  });
41391
41620
  child.stdin.write(prompt);
41392
41621
  child.stdin.end();
@@ -41424,6 +41653,54 @@ async function setCommitAuthor(cwd2, machineUsername) {
41424
41653
  }
41425
41654
  }
41426
41655
 
41656
+ // src/core/ws-client/run-slot.ts
41657
+ var NO_SLOT = { release: () => void 0 };
41658
+ function sleep2(ms2) {
41659
+ return new Promise((resolve12) => setTimeout(resolve12, ms2));
41660
+ }
41661
+ function holdLease(ctx, lease) {
41662
+ const timer = setInterval(() => {
41663
+ ctx.request(WsChannels.RUN_SLOT, { op: "renew", lease }).catch(() => void 0);
41664
+ }, RUN_SLOT_RENEW_MS);
41665
+ timer.unref?.();
41666
+ let released = false;
41667
+ return {
41668
+ release: () => {
41669
+ if (released) return;
41670
+ released = true;
41671
+ clearInterval(timer);
41672
+ ctx.request(WsChannels.RUN_SLOT, { op: "release", lease }).catch(() => void 0);
41673
+ }
41674
+ };
41675
+ }
41676
+ async function acquireRunSlot(ctx, onQueued) {
41677
+ const startedAt = Date.now();
41678
+ let ticket;
41679
+ let lastPosition = -1;
41680
+ let limit = 0;
41681
+ for (; ; ) {
41682
+ let reply;
41683
+ try {
41684
+ reply = await ctx.request(WsChannels.RUN_SLOT, { op: "acquire", ...ticket ? { ticket } : {} });
41685
+ } catch (err) {
41686
+ logger.warn("run.slot.unavailable", { error: err.message });
41687
+ return { kind: "granted", handle: NO_SLOT };
41688
+ }
41689
+ if (!reply || !("granted" in reply)) return { kind: "granted", handle: NO_SLOT };
41690
+ if (reply.granted) {
41691
+ return { kind: "granted", handle: reply.lease ? holdLease(ctx, reply.lease) : NO_SLOT };
41692
+ }
41693
+ ticket = reply.ticket;
41694
+ limit = reply.limit;
41695
+ if (reply.position !== lastPosition) {
41696
+ lastPosition = reply.position;
41697
+ onQueued({ position: reply.position, limit: reply.limit, running: reply.running });
41698
+ }
41699
+ if (Date.now() - startedAt >= RUN_SLOT_MAX_WAIT_MS) return { kind: "timeout", limit };
41700
+ await sleep2(reply.retryAfterMs > 0 ? reply.retryAfterMs : RUN_SLOT_RETRY_MS);
41701
+ }
41702
+ }
41703
+
41427
41704
  // src/core/ws-client/command-dispatch.ts
41428
41705
  var MEMORY_SEED_HEADER = "CONTEXT MEMORY from earlier in this conversation (may span prior sessions/accounts). Use it as background; do not repeat it back unless relevant:";
41429
41706
  function handleCommandChannels(ctx, message, payload) {
@@ -41677,8 +41954,28 @@ ${guardedPrompt}`;
41677
41954
  }
41678
41955
  };
41679
41956
  let result;
41957
+ let queueTimedOut = false;
41680
41958
  try {
41681
- result = await runAiFailover(plan, commandId, cwd2, handlers, aiRunTimeoutMs);
41959
+ const slot = await acquireRunSlot(ctx, ({ position, limit, running: running2 }) => {
41960
+ const text = `\u23F3 queued \u2014 ${position} ahead, ${running2}/${limit} parallel runs in use (plan limit)`;
41961
+ ctx.bus.push({ source: origin, kind: "log", text });
41962
+ ctx.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: `${text}
41963
+ `, log: true });
41964
+ });
41965
+ if (slot.kind === "timeout") {
41966
+ queueTimedOut = true;
41967
+ const text = `RUN_QUEUE_TIMEOUT: waited 30 min for a free run slot (plan limit ${slot.limit} parallel runs) \u2014 not run`;
41968
+ ctx.bus.push({ source: origin, kind: "log", text, level: "warn" });
41969
+ ctx.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: `${text}
41970
+ `, log: true });
41971
+ result = { exitCode: 1, workedDir: null, workedKey: null, workedCmd: null, usage: { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 }, sessionId: "" };
41972
+ } else {
41973
+ try {
41974
+ result = await runAiFailover(plan, commandId, cwd2, handlers, aiRunTimeoutMs);
41975
+ } finally {
41976
+ slot.handle.release();
41977
+ }
41978
+ }
41682
41979
  } finally {
41683
41980
  ctx.bus.endBusy(plan.cmd);
41684
41981
  }
@@ -41739,7 +42036,7 @@ ${guardedPrompt}`;
41739
42036
  });
41740
42037
  ctx.bus.addTokens?.(result.usage.tokens);
41741
42038
  }
41742
- ctx.reportUsage(events, profileLabel);
42039
+ if (!queueTimedOut) ctx.reportUsage(events, profileLabel);
41743
42040
  void ctx.pollUsage();
41744
42041
  ctx.bus.push({
41745
42042
  source: origin,
@@ -42222,11 +42519,11 @@ async function allAiProfilesExhausted(ctx, cfg) {
42222
42519
 
42223
42520
  // src/core/fs-browse.ts
42224
42521
  import { readdir as readdir3 } from "fs/promises";
42225
- import { resolve as resolve3, sep as sep2 } from "path";
42522
+ import { resolve as resolve4, sep as sep2 } from "path";
42226
42523
  async function listDir(path, root) {
42227
42524
  if (!root) return { path: "", entries: [] };
42228
- const base = resolve3(root);
42229
- const requested = resolve3(base, path && path.trim() ? path : ".");
42525
+ const base = resolve4(root);
42526
+ const requested = resolve4(base, path && path.trim() ? path : ".");
42230
42527
  const target = requested === base || requested.startsWith(base + sep2) ? requested : base;
42231
42528
  try {
42232
42529
  const dirents = await readdir3(target, { withFileTypes: true });
@@ -42245,12 +42542,12 @@ async function listDir(path, root) {
42245
42542
 
42246
42543
  // src/core/fs-read.ts
42247
42544
  import { readFile as readFile6 } from "fs/promises";
42248
- import { resolve as resolve4, sep as sep3 } from "path";
42545
+ import { resolve as resolve5, sep as sep3 } from "path";
42249
42546
  var MAX_BYTES = 256 * 1024;
42250
42547
  function resolveInRoot(root, relPath) {
42251
42548
  if (!root) return null;
42252
- const base = resolve4(root);
42253
- const target = resolve4(base, relPath || ".");
42549
+ const base = resolve5(root);
42550
+ const target = resolve5(base, relPath || ".");
42254
42551
  return target === base || target.startsWith(base + sep3) ? target : null;
42255
42552
  }
42256
42553
  async function readWorkerFile(root, path) {
@@ -42270,12 +42567,12 @@ async function readWorkerFile(root, path) {
42270
42567
 
42271
42568
  // src/core/fs-write.ts
42272
42569
  import { mkdir as mkdir4, writeFile as writeFile5 } from "fs/promises";
42273
- import { dirname as dirname4, resolve as resolve5, sep as sep4 } from "path";
42570
+ import { dirname as dirname5, resolve as resolve6, sep as sep4 } from "path";
42274
42571
  var MAX_BYTES2 = 2 * 1024 * 1024;
42275
42572
  function resolveInRoot2(root, relPath) {
42276
42573
  if (!root) return null;
42277
- const base = resolve5(root);
42278
- const target = resolve5(base, relPath || ".");
42574
+ const base = resolve6(root);
42575
+ const target = resolve6(base, relPath || ".");
42279
42576
  return target === base || target.startsWith(base + sep4) ? target : null;
42280
42577
  }
42281
42578
  async function writeWorkerFile(root, relPath, content) {
@@ -42288,7 +42585,7 @@ async function writeWorkerFile(root, relPath, content) {
42288
42585
  return { ok: false, path: target, bytes: 0, error: `content too large (max ${MAX_BYTES2} bytes)` };
42289
42586
  }
42290
42587
  try {
42291
- await mkdir4(dirname4(target), { recursive: true });
42588
+ await mkdir4(dirname5(target), { recursive: true });
42292
42589
  await writeFile5(target, content, "utf8");
42293
42590
  return { ok: true, path: target, bytes };
42294
42591
  } catch (err) {
@@ -42298,11 +42595,11 @@ async function writeWorkerFile(root, relPath, content) {
42298
42595
 
42299
42596
  // src/core/fs-mutate.ts
42300
42597
  import { mkdir as mkdir5, writeFile as writeFile6, rename, rm as rm3, stat as stat2 } from "fs/promises";
42301
- import { basename as basename3, dirname as dirname5, resolve as resolve6, sep as sep5 } from "path";
42598
+ import { basename as basename3, dirname as dirname6, resolve as resolve7, sep as sep5 } from "path";
42302
42599
  var MAX_BYTES3 = 2 * 1024 * 1024;
42303
42600
  function resolveInRoot3(root, rel) {
42304
- const base = resolve6(root);
42305
- const target = resolve6(base, rel || ".");
42601
+ const base = resolve7(root);
42602
+ const target = resolve7(base, rel || ".");
42306
42603
  return target === base || target.startsWith(base + sep5) ? target : null;
42307
42604
  }
42308
42605
  async function isDir(p2) {
@@ -42327,19 +42624,19 @@ async function mutateFs(root, req) {
42327
42624
  switch (req.op) {
42328
42625
  case "mkdir": {
42329
42626
  const target = resolveInRoot3(root, req.path ?? "");
42330
- if (!target || target === resolve6(root)) return escaped(req.path ?? "");
42627
+ if (!target || target === resolve7(root)) return escaped(req.path ?? "");
42331
42628
  await mkdir5(target, { recursive: true });
42332
42629
  return { ok: true, path: target };
42333
42630
  }
42334
42631
  case "create": {
42335
42632
  const target = resolveInRoot3(root, req.path ?? "");
42336
- if (!target || target === resolve6(root)) return escaped(req.path ?? "");
42633
+ if (!target || target === resolve7(root)) return escaped(req.path ?? "");
42337
42634
  if (await exists(target)) return { ok: false, path: target, error: "already exists" };
42338
42635
  const content = req.content ?? "";
42339
42636
  if (Buffer.byteLength(content, "utf8") > MAX_BYTES3) {
42340
42637
  return { ok: false, path: target, error: `content too large (max ${MAX_BYTES3} bytes)` };
42341
42638
  }
42342
- await mkdir5(dirname5(target), { recursive: true });
42639
+ await mkdir5(dirname6(target), { recursive: true });
42343
42640
  await writeFile6(target, content, "utf8");
42344
42641
  return { ok: true, path: target };
42345
42642
  }
@@ -42347,18 +42644,18 @@ async function mutateFs(root, req) {
42347
42644
  const from = resolveInRoot3(root, req.from ?? "");
42348
42645
  let to2 = resolveInRoot3(root, req.to ?? "");
42349
42646
  if (!from || !to2) return escaped(req.from ?? req.to ?? "");
42350
- if (from === resolve6(root)) return { ok: false, path: from, error: "cannot move the project root" };
42351
- if (await isDir(to2)) to2 = resolve6(to2, basename3(from));
42352
- if (to2 !== resolve6(root) && !to2.startsWith(resolve6(root) + sep5)) return escaped(req.to ?? "");
42647
+ if (from === resolve7(root)) return { ok: false, path: from, error: "cannot move the project root" };
42648
+ if (await isDir(to2)) to2 = resolve7(to2, basename3(from));
42649
+ if (to2 !== resolve7(root) && !to2.startsWith(resolve7(root) + sep5)) return escaped(req.to ?? "");
42353
42650
  if (await exists(to2)) return { ok: false, path: to2, error: "destination already exists" };
42354
- await mkdir5(dirname5(to2), { recursive: true });
42651
+ await mkdir5(dirname6(to2), { recursive: true });
42355
42652
  await rename(from, to2);
42356
42653
  return { ok: true, path: to2 };
42357
42654
  }
42358
42655
  case "delete": {
42359
42656
  const target = resolveInRoot3(root, req.path ?? "");
42360
42657
  if (!target) return escaped(req.path ?? "");
42361
- if (target === resolve6(root)) return { ok: false, path: target, error: "cannot delete the project root" };
42658
+ if (target === resolve7(root)) return { ok: false, path: target, error: "cannot delete the project root" };
42362
42659
  await rm3(target, { recursive: true, force: true });
42363
42660
  return { ok: true, path: target };
42364
42661
  }
@@ -42372,11 +42669,11 @@ async function mutateFs(root, req) {
42372
42669
 
42373
42670
  // src/core/fs-transfer.ts
42374
42671
  import { mkdir as mkdir6, readFile as readFile7, stat as stat3, writeFile as writeFile7 } from "fs/promises";
42375
- import { basename as basename4, dirname as dirname6, extname, resolve as resolve7, sep as sep6 } from "path";
42672
+ import { basename as basename4, dirname as dirname7, extname, resolve as resolve8, sep as sep6 } from "path";
42376
42673
  function resolveInRoot4(root, relPath) {
42377
42674
  if (!root) return null;
42378
- const base = resolve7(root);
42379
- const target = resolve7(base, relPath || ".");
42675
+ const base = resolve8(root);
42676
+ const target = resolve8(base, relPath || ".");
42380
42677
  return target === base || target.startsWith(base + sep6) ? target : null;
42381
42678
  }
42382
42679
  var MIME_BY_EXT = {
@@ -42415,7 +42712,7 @@ async function uploadBinaryFile(root, relPath, contentBase64) {
42415
42712
  return { ok: false, path: target, bytes: 0, error: `file too large (max ${FS_TRANSFER_MAX_BYTES} bytes)` };
42416
42713
  }
42417
42714
  try {
42418
- await mkdir6(dirname6(target), { recursive: true });
42715
+ await mkdir6(dirname7(target), { recursive: true });
42419
42716
  await writeFile7(target, buf);
42420
42717
  return { ok: true, path: target, bytes: buf.byteLength };
42421
42718
  } catch (err) {
@@ -42492,13 +42789,13 @@ function handleFsChannels(ctx, message, payload) {
42492
42789
  // src/core/git-diff.ts
42493
42790
  import { readFile as readFile8 } from "fs/promises";
42494
42791
  import { execFile as execFile6 } from "child_process";
42495
- import { basename as basename5, dirname as dirname7, resolve as resolve8, sep as sep7 } from "path";
42792
+ import { basename as basename5, dirname as dirname8, resolve as resolve9, sep as sep7 } from "path";
42496
42793
  import { promisify as promisify6 } from "util";
42497
42794
  var run6 = promisify6(execFile6);
42498
42795
  function resolveInRoot5(root, relPath) {
42499
42796
  if (!root) return null;
42500
- const base = resolve8(root);
42501
- const target = resolve8(base, relPath || ".");
42797
+ const base = resolve9(root);
42798
+ const target = resolve9(base, relPath || ".");
42502
42799
  return target === base || target.startsWith(base + sep7) ? target : null;
42503
42800
  }
42504
42801
  function looksBinary(buf) {
@@ -42513,7 +42810,7 @@ async function gitDiff(root, path) {
42513
42810
  if (!target) {
42514
42811
  return { path, oldContent: "", newContent: "", isBinary: false };
42515
42812
  }
42516
- const dir2 = dirname7(target);
42813
+ const dir2 = dirname8(target);
42517
42814
  const base = basename5(target);
42518
42815
  let newBuf = null;
42519
42816
  try {
@@ -42652,15 +42949,15 @@ async function manageSshKey(op, profileDir2) {
42652
42949
 
42653
42950
  // src/core/git-history.ts
42654
42951
  import { execFile as execFile9 } from "child_process";
42655
- import { resolve as resolve9, sep as sep8 } from "path";
42952
+ import { resolve as resolve10, sep as sep8 } from "path";
42656
42953
  import { promisify as promisify9 } from "util";
42657
42954
  var run9 = promisify9(execFile9);
42658
42955
  var FS = "";
42659
42956
  var RS = "";
42660
42957
  function resolveRepo(root, repo) {
42661
42958
  if (!root) return null;
42662
- const base = resolve9(root);
42663
- const requested = resolve9(base, repo && repo.trim() ? repo : ".");
42959
+ const base = resolve10(root);
42960
+ const requested = resolve10(base, repo && repo.trim() ? repo : ".");
42664
42961
  return requested === base || requested.startsWith(base + sep8) ? requested : base;
42665
42962
  }
42666
42963
  async function git(cwd2, args) {
@@ -42677,7 +42974,7 @@ async function git(cwd2, args) {
42677
42974
  }
42678
42975
  async function gitRepos(root) {
42679
42976
  if (!root) return { repos: [] };
42680
- const base = resolve9(root);
42977
+ const base = resolve10(root);
42681
42978
  const top = (await git(base, ["rev-parse", "--show-toplevel"])).trim();
42682
42979
  if (!top || !(top === base || top.startsWith(base + sep8))) return { repos: [] };
42683
42980
  const repos = [];
@@ -42689,7 +42986,7 @@ async function gitRepos(root) {
42689
42986
  const m2 = line.trim().match(/^[-+U ]?[0-9a-f]{7,40}\s+(\S+)/);
42690
42987
  const subPath = m2?.[1];
42691
42988
  if (!subPath) continue;
42692
- const abs = resolve9(base, subPath);
42989
+ const abs = resolve10(base, subPath);
42693
42990
  if (!(abs === base || abs.startsWith(base + sep8))) continue;
42694
42991
  const remote = (await git(abs, ["remote", "get-url", "origin"])).trim();
42695
42992
  const branch = (await git(abs, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
@@ -42918,7 +43215,7 @@ function handleMiscChannels(ctx, message, payload) {
42918
43215
  }
42919
43216
 
42920
43217
  // src/core/ws-client/handlers/project.ts
42921
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, renameSync, rmSync as rmSync6 } from "fs";
43218
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, renameSync, rmSync as rmSync7 } from "fs";
42922
43219
  import { join as join23 } from "path";
42923
43220
  function handleProjectChannels(ctx, message, payload) {
42924
43221
  switch (message.channel) {
@@ -42931,11 +43228,11 @@ function handleProjectChannels(ctx, message, payload) {
42931
43228
  const oldRoot = ctx.physicFolderPath(oldName);
42932
43229
  if (oldRoot) void uninstallCron(oldRoot).catch(() => void 0);
42933
43230
  }
42934
- if (oldPath && oldName !== sync.newName && existsSync13(oldPath) && !existsSync13(newPath)) {
43231
+ if (oldPath && oldName !== sync.newName && existsSync14(oldPath) && !existsSync14(newPath)) {
42935
43232
  renameSync(oldPath, newPath);
42936
43233
  } else {
42937
- if (oldPath && oldName !== sync.newName && existsSync13(oldPath)) rmSync6(oldPath, { recursive: true, force: true });
42938
- mkdirSync6(newPath, { recursive: true });
43234
+ if (oldPath && oldName !== sync.newName && existsSync14(oldPath)) rmSync7(oldPath, { recursive: true, force: true });
43235
+ mkdirSync7(newPath, { recursive: true });
42939
43236
  }
42940
43237
  ctx.physicRoot = ctx.physicFolderPath(sync.newName);
42941
43238
  ctx.bus.setProject(sync.newName);
@@ -42948,7 +43245,7 @@ function handleProjectChannels(ctx, message, payload) {
42948
43245
  if (del.name) {
42949
43246
  const delRoot = ctx.physicFolderPath(del.name);
42950
43247
  if (delRoot) void uninstallCron(delRoot).catch(() => void 0);
42951
- rmSync6(join23(ctx.profileDir, del.name), { recursive: true, force: true });
43248
+ rmSync7(join23(ctx.profileDir, del.name), { recursive: true, force: true });
42952
43249
  ctx.bus.log(t("project.folderDeleted", { name: del.name }));
42953
43250
  }
42954
43251
  void manageSshKey("delete", ctx.profileDir).catch(() => void 0);
@@ -43012,7 +43309,7 @@ Extra focus from the author: ${req.prompt}` : "";
43012
43309
  ].join("\n");
43013
43310
  }
43014
43311
  function runOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
43015
- return new Promise((resolve11, reject) => {
43312
+ return new Promise((resolve12, reject) => {
43016
43313
  const args = ["-p", ...profile?.model ? ["--model", profile.model] : [], ...denySettingsArgs(cmd, [...denyDirs, profile?.dir])];
43017
43314
  const env3 = {
43018
43315
  ...process.env,
@@ -43035,7 +43332,7 @@ function runOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
43035
43332
  });
43036
43333
  child.on("close", (code) => {
43037
43334
  clearTimeout(timer);
43038
- resolve11({ code: code ?? -1, out, err });
43335
+ resolve12({ code: code ?? -1, out, err });
43039
43336
  });
43040
43337
  child.stdin.write(prompt);
43041
43338
  child.stdin.end();
@@ -43066,7 +43363,7 @@ async function runKnowledgeCompose(req, ai2, physicRoot) {
43066
43363
 
43067
43364
  // src/core/faq-compose.ts
43068
43365
  import { spawn as spawn9, execFile as execFile10 } from "child_process";
43069
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, rmSync as rmSync7 } from "fs";
43366
+ import { existsSync as existsSync15, mkdirSync as mkdirSync8, rmSync as rmSync8 } from "fs";
43070
43367
  import { randomUUID as randomUUID2 } from "crypto";
43071
43368
  import { join as join24 } from "path";
43072
43369
  import { tmpdir as tmpdir4 } from "os";
@@ -43136,7 +43433,7 @@ function parsePerTicket(text) {
43136
43433
  return [];
43137
43434
  }
43138
43435
  function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
43139
- return new Promise((resolve11, reject) => {
43436
+ return new Promise((resolve12, reject) => {
43140
43437
  const isClaude = cmd.includes("claude");
43141
43438
  const args = [
43142
43439
  "-p",
@@ -43169,7 +43466,7 @@ function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
43169
43466
  child.on("close", (code) => {
43170
43467
  clearTimeout(timer);
43171
43468
  out += parser.flush();
43172
- resolve11({ code: code ?? -1, out, err, usage: parser.usage() });
43469
+ resolve12({ code: code ?? -1, out, err, usage: parser.usage() });
43173
43470
  });
43174
43471
  child.stdin.write(prompt);
43175
43472
  child.stdin.end();
@@ -43193,7 +43490,7 @@ async function runFaqCompose(req, ai2) {
43193
43490
  const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
43194
43491
  const auth = gitAuthArgs("x-access-token", req.repo.token);
43195
43492
  try {
43196
- mkdirSync7(workDir, { recursive: true });
43493
+ mkdirSync8(workDir, { recursive: true });
43197
43494
  await execFileAsync2("git", [...auth, "clone", "--depth", "1", "--branch", req.repo.branch, req.repo.url, workDir], { env: gitEnv });
43198
43495
  await execFileAsync2("git", ["-C", workDir, "config", "user.name", "4PM FAQ Bot"], { env: gitEnv });
43199
43496
  await execFileAsync2("git", ["-C", workDir, "config", "user.email", "faq-bot@4pm.app"], { env: gitEnv });
@@ -43233,9 +43530,9 @@ async function runFaqCompose(req, ai2) {
43233
43530
  logger.warn("faq.compose.failed", { error: String(err) });
43234
43531
  return { prUrl: "", error: String(err) };
43235
43532
  } finally {
43236
- if (existsSync14(workDir)) {
43533
+ if (existsSync15(workDir)) {
43237
43534
  try {
43238
- rmSync7(workDir, { recursive: true, force: true });
43535
+ rmSync8(workDir, { recursive: true, force: true });
43239
43536
  } catch {
43240
43537
  }
43241
43538
  }
@@ -43559,7 +43856,7 @@ async function writeSecrets(root, req) {
43559
43856
 
43560
43857
  // src/core/agent-tools.ts
43561
43858
  import { readFile as readFile12, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
43562
- import { dirname as dirname8, join as join27 } from "path";
43859
+ import { dirname as dirname9, join as join27 } from "path";
43563
43860
  var SHARED_REL = ".claude/settings.json";
43564
43861
  var LOCAL_REL = ".claude/settings.local.json";
43565
43862
  var SECRETS_DENY = "Read(./project.secrets.json)";
@@ -43590,7 +43887,7 @@ async function writeAgentTools(root, scope, permissions) {
43590
43887
  const next = normalizePermissions(permissions);
43591
43888
  if (scope === "shared" && !next.deny.includes(SECRETS_DENY)) next.deny = [...next.deny, SECRETS_DENY];
43592
43889
  obj.permissions = next;
43593
- await mkdir9(dirname8(path), { recursive: true });
43890
+ await mkdir9(dirname9(path), { recursive: true });
43594
43891
  await writeFile10(path, JSON.stringify(obj, null, 2) + "\n", "utf8");
43595
43892
  return { ok: true };
43596
43893
  } catch (err) {
@@ -43601,7 +43898,7 @@ async function writeAgentTools(root, scope, permissions) {
43601
43898
  // src/core/packages.ts
43602
43899
  import { createHash as createHash4 } from "crypto";
43603
43900
  import { readFile as readFile13, writeFile as writeFile11, mkdir as mkdir10, readdir as readdir5, rm as rm6, stat as stat4 } from "fs/promises";
43604
- import { dirname as dirname9, join as join28, relative } from "path";
43901
+ import { dirname as dirname10, join as join28, relative } from "path";
43605
43902
  var AGENTS_REL2 = ".claude/agents";
43606
43903
  var SKILLS_REL2 = ".claude/skills";
43607
43904
  var MANIFEST_REL = ".claude/.4pm-packages.json";
@@ -43682,7 +43979,7 @@ async function readManifest(root) {
43682
43979
  }
43683
43980
  async function writeManifest(root, manifest) {
43684
43981
  const path = join28(root, MANIFEST_REL);
43685
- await mkdir10(dirname9(path), { recursive: true });
43982
+ await mkdir10(dirname10(path), { recursive: true });
43686
43983
  await writeFile11(path, JSON.stringify(manifest, null, 2), "utf8");
43687
43984
  }
43688
43985
  async function writeArtifactFiles(root, kind, slug, files) {
@@ -43708,7 +44005,7 @@ async function writeArtifactFiles(root, kind, slug, files) {
43708
44005
  for (const [rel, buf] of byPath) {
43709
44006
  if (rel.includes("..") || rel.startsWith("/")) continue;
43710
44007
  const dest = join28(base, rel);
43711
- await mkdir10(dirname9(dest), { recursive: true });
44008
+ await mkdir10(dirname10(dest), { recursive: true });
43712
44009
  await writeFile11(dest, buf);
43713
44010
  }
43714
44011
  }
@@ -43789,7 +44086,7 @@ async function removePackage(root, slug) {
43789
44086
 
43790
44087
  // src/core/graph.ts
43791
44088
  import { readFile as readFile14, readdir as readdir6 } from "fs/promises";
43792
- import { join as join29, relative as relative2, dirname as dirname10, resolve as resolve10, extname as extname2, basename as basename6 } from "path";
44089
+ import { join as join29, relative as relative2, dirname as dirname11, resolve as resolve11, extname as extname2, basename as basename6 } from "path";
43793
44090
  var MAX_FILES = 2e3;
43794
44091
  var MAX_NODES = 3e3;
43795
44092
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out", "coverage", ".turbo"]);
@@ -43844,7 +44141,7 @@ async function buildDocs(root) {
43844
44141
  for (const m2 of text.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
43845
44142
  const href = (m2[1] ?? "").split("#")[0].trim();
43846
44143
  if (!href || !href.endsWith(".md") || href.startsWith("http")) continue;
43847
- const target = relative2(root, resolve10(dirname10(f2), href)).split("\\").join("/");
44144
+ const target = relative2(root, resolve11(dirname11(f2), href)).split("\\").join("/");
43848
44145
  if (ids.has(target) && target !== rel) {
43849
44146
  const key = `${rel}->${target}`;
43850
44147
  if (!seen.has(key)) {
@@ -43929,7 +44226,7 @@ async function buildGraph(root, mode) {
43929
44226
 
43930
44227
  // src/core/rag.ts
43931
44228
  import { readFile as readFile15, writeFile as writeFile12, statfs as statfs2, mkdir as mkdir11 } from "fs/promises";
43932
- import { existsSync as existsSync15 } from "fs";
44229
+ import { existsSync as existsSync16 } from "fs";
43933
44230
  import { execFile as execFile11, spawn as spawn10 } from "child_process";
43934
44231
  import { totalmem as totalmem2, cpus as cpus2 } from "os";
43935
44232
  import { join as join30 } from "path";
@@ -44022,7 +44319,7 @@ async function ragStatus(root) {
44022
44319
  const modelPresent = modelName.length > 0;
44023
44320
  return {
44024
44321
  installed: fastembed && sqliteVec && modelPresent,
44025
- installing: existsSync15(join30(root, RAG_REL, ".installing")),
44322
+ installing: existsSync16(join30(root, RAG_REL, ".installing")),
44026
44323
  python: { found: py.found, version: py.version },
44027
44324
  deps: { pip, fastembed, sqliteVec },
44028
44325
  model: { present: modelPresent, name: modelName },
@@ -44033,7 +44330,7 @@ async function ragStatus(root) {
44033
44330
  };
44034
44331
  }
44035
44332
  async function readIndexMeta(root) {
44036
- const indexing = existsSync15(join30(root, RAG_REL, ".indexing"));
44333
+ const indexing = existsSync16(join30(root, RAG_REL, ".indexing"));
44037
44334
  try {
44038
44335
  const m2 = JSON.parse(await readFile15(join30(root, RAG_REL, ".index.json"), "utf8"));
44039
44336
  return {
@@ -44108,8 +44405,8 @@ async function ensureEngine(root) {
44108
44405
  async function ragReindex(root) {
44109
44406
  const py = await detectPython();
44110
44407
  if (!py.found) return { ok: false, started: false, error: "python3 not found" };
44111
- if (!existsSync15(join30(root, RAG_REL, ".installed"))) return { ok: false, started: false, error: "RAG is not installed" };
44112
- if (existsSync15(join30(root, RAG_REL, ".indexing"))) return { ok: true, started: true };
44408
+ if (!existsSync16(join30(root, RAG_REL, ".installed"))) return { ok: false, started: false, error: "RAG is not installed" };
44409
+ if (existsSync16(join30(root, RAG_REL, ".indexing"))) return { ok: true, started: true };
44113
44410
  await ensureEngine(root);
44114
44411
  const script = `
44115
44412
  set +e
@@ -44129,7 +44426,7 @@ rm -f "${RAG_REL}/.indexing"
44129
44426
  async function ragQuery(root, queryText, k2 = 8) {
44130
44427
  const py = await detectPython();
44131
44428
  if (!py.found) return { results: [], error: "python3 not found" };
44132
- if (!existsSync15(join30(root, RAG_REL, "index.db"))) return { results: [], error: "no index \u2014 reindex first" };
44429
+ if (!existsSync16(join30(root, RAG_REL, "index.db"))) return { results: [], error: "no index \u2014 reindex first" };
44133
44430
  await ensureEngine(root);
44134
44431
  try {
44135
44432
  const { stdout } = await run10(
@@ -44149,7 +44446,7 @@ async function ragInstall(root, model) {
44149
44446
  }
44150
44447
  const py = await detectPython();
44151
44448
  if (!py.found) return { ok: false, started: false, error: "python3 not found on the worker" };
44152
- if (existsSync15(join30(root, RAG_REL, ".installing"))) return { ok: true, started: true };
44449
+ if (existsSync16(join30(root, RAG_REL, ".installing"))) return { ok: true, started: true };
44153
44450
  try {
44154
44451
  await mkdir11(join30(root, RAG_REL), { recursive: true });
44155
44452
  } catch {
@@ -44178,7 +44475,7 @@ rm -f "${RAG_REL}/.installing"
44178
44475
  }
44179
44476
 
44180
44477
  // src/core/config-sync.ts
44181
- import { existsSync as existsSync16, readFileSync as readFileSync16 } from "fs";
44478
+ import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
44182
44479
  import { join as join31 } from "path";
44183
44480
  var SERVER_MANAGED_KEYS = [
44184
44481
  "physicPath",
@@ -44197,7 +44494,7 @@ var SERVER_MANAGED_KEYS = [
44197
44494
  ];
44198
44495
  function readConfigText(profileDir2) {
44199
44496
  const path = join31(profileDir2, "config.json");
44200
- if (existsSync16(path)) {
44497
+ if (existsSync17(path)) {
44201
44498
  try {
44202
44499
  return readFileSync16(path, "utf8");
44203
44500
  } catch {
@@ -44683,14 +44980,14 @@ var WsClient = class {
44683
44980
  */
44684
44981
  awaitConnected(timeoutMs) {
44685
44982
  if (this.sessionKey && this.bus.status === "connected") return Promise.resolve(true);
44686
- return new Promise((resolve11) => {
44983
+ return new Promise((resolve12) => {
44687
44984
  const waiter = () => {
44688
44985
  clearTimeout(timer);
44689
- resolve11(true);
44986
+ resolve12(true);
44690
44987
  };
44691
44988
  const timer = setTimeout(() => {
44692
44989
  this.connectWaiters = this.connectWaiters.filter((w2) => w2 !== waiter);
44693
- resolve11(false);
44990
+ resolve12(false);
44694
44991
  }, timeoutMs);
44695
44992
  this.connectWaiters.push(waiter);
44696
44993
  });
@@ -44717,16 +45014,16 @@ var WsClient = class {
44717
45014
  this.forceReconnect = false;
44718
45015
  return Promise.resolve();
44719
45016
  }
44720
- return new Promise((resolve11) => {
45017
+ return new Promise((resolve12) => {
44721
45018
  const timer = setTimeout(() => {
44722
45019
  this.wakeReconnect = null;
44723
- resolve11();
45020
+ resolve12();
44724
45021
  }, ms2);
44725
45022
  this.wakeReconnect = () => {
44726
45023
  clearTimeout(timer);
44727
45024
  this.wakeReconnect = null;
44728
45025
  this.forceReconnect = false;
44729
- resolve11();
45026
+ resolve12();
44730
45027
  };
44731
45028
  });
44732
45029
  }
@@ -44780,7 +45077,7 @@ var WsClient = class {
44780
45077
  * One WS session: token → connect → handshake → handle messages until close.
44781
45078
  */
44782
45079
  connectOnce() {
44783
- return new Promise((resolve11, reject) => {
45080
+ return new Promise((resolve12, reject) => {
44784
45081
  void (async () => {
44785
45082
  this.sessionEstablished = false;
44786
45083
  this.bus.setStatus("connecting");
@@ -44822,7 +45119,12 @@ var WsClient = class {
44822
45119
  if (token.aiMemory && !this.aiMemory) this.aiMemory = token.aiMemory.text;
44823
45120
  this.outboundReviewEnabled = token.outboundReview?.enabled === true;
44824
45121
  this.restrictToFolder = token.aiRestrictToFolder === true;
44825
- applyGitAuth(token.gitAuth ?? null);
45122
+ configureGitAuth(
45123
+ token.gitAuth ?? null,
45124
+ token.gitAuthHost ?? null,
45125
+ this.context.profileDir,
45126
+ (req) => this.request(WsChannels.GIT_TOKEN, req)
45127
+ );
44826
45128
  this.machineUsername = token.machineUsername ?? "";
44827
45129
  this.servingRepos = token.repos ?? [];
44828
45130
  void this.ensureServingReposCloned();
@@ -44876,7 +45178,7 @@ var WsClient = class {
44876
45178
  if (code === CLOSE_DRAINING) {
44877
45179
  this.bus.log(t("error.serverDraining"));
44878
45180
  }
44879
- resolve11();
45181
+ resolve12();
44880
45182
  });
44881
45183
  socket.on("error", (err) => {
44882
45184
  this.clearHeartbeat();
@@ -45002,7 +45304,7 @@ var WsClient = class {
45002
45304
  * Used for quota.check before spawning an AI cli (ADR-0020).
45003
45305
  */
45004
45306
  request(channel, data) {
45005
- return new Promise((resolve11, reject) => {
45307
+ return new Promise((resolve12, reject) => {
45006
45308
  if (!this.socket || !this.sessionKey) {
45007
45309
  reject(new Error("Not connected to the server."));
45008
45310
  return;
@@ -45022,7 +45324,7 @@ var WsClient = class {
45022
45324
  }, 15e3);
45023
45325
  timer.unref?.();
45024
45326
  this.pending.set(envelope.id, {
45025
- resolve: (value) => resolve11(value),
45327
+ resolve: (value) => resolve12(value),
45026
45328
  timer
45027
45329
  });
45028
45330
  this.socket.send(JSON.stringify(envelope));
@@ -45076,8 +45378,8 @@ var WsClient = class {
45076
45378
  /** Create the physic folder at an absolute path if missing (best-effort). */
45077
45379
  ensurePhysicFolderPath(folder) {
45078
45380
  try {
45079
- if (!existsSync17(folder)) {
45080
- mkdirSync8(folder, { recursive: true });
45381
+ if (!existsSync18(folder)) {
45382
+ mkdirSync9(folder, { recursive: true });
45081
45383
  this.bus.log(t("project.createdFolder", { folder }));
45082
45384
  }
45083
45385
  } catch (err) {
@@ -45106,7 +45408,7 @@ var WsClient = class {
45106
45408
  projects: [],
45107
45409
  version: CLI_VERSION,
45108
45410
  physicPath,
45109
- physicPathExists: physicPath ? existsSync17(physicPath) : void 0,
45411
+ physicPathExists: physicPath ? existsSync18(physicPath) : void 0,
45110
45412
  // Real coarse state (ADR-0152): !paused and a recent tick (was hard-coded false).
45111
45413
  autonomousRunning: physicPath ? isAutonomousRunning(physicPath, this.context.profileDir) : false,
45112
45414
  // The machine-user AI-run limit (0 = unlimited) + profile count so a dispatch can resolve the
@@ -45365,7 +45667,7 @@ var WsClient = class {
45365
45667
  };
45366
45668
 
45367
45669
  // src/core/instance-lock.ts
45368
- import { readFileSync as readFileSync17, rmSync as rmSync8, writeFileSync as writeFileSync9 } from "fs";
45670
+ import { readFileSync as readFileSync17, rmSync as rmSync9, writeFileSync as writeFileSync9 } from "fs";
45369
45671
  import { join as join33 } from "path";
45370
45672
  function lockPath(profileDir2) {
45371
45673
  return join33(profileDir2, "start.lock");
@@ -45394,7 +45696,7 @@ function acquireInstanceLock(profileDir2) {
45394
45696
  function releaseInstanceLock(profileDir2) {
45395
45697
  try {
45396
45698
  const pid = Number.parseInt(readFileSync17(lockPath(profileDir2), "utf8").trim(), 10);
45397
- if (pid === process.pid) rmSync8(lockPath(profileDir2), { force: true });
45699
+ if (pid === process.pid) rmSync9(lockPath(profileDir2), { force: true });
45398
45700
  } catch {
45399
45701
  }
45400
45702
  }
@@ -45689,7 +45991,7 @@ var SessionBus = class {
45689
45991
 
45690
45992
  // src/core/control-server.ts
45691
45993
  import { createServer } from "net";
45692
- import { chmodSync as chmodSync4, rmSync as rmSync9 } from "fs";
45994
+ import { chmodSync as chmodSync5, rmSync as rmSync10 } from "fs";
45693
45995
  import { join as join35 } from "path";
45694
45996
 
45695
45997
  // src/core/control-protocol.ts
@@ -45719,7 +46021,7 @@ function createFrameParser() {
45719
46021
  }
45720
46022
 
45721
46023
  // src/core/control-token.ts
45722
- import { chmodSync as chmodSync3, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
46024
+ import { chmodSync as chmodSync4, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
45723
46025
  import { randomBytes as randomBytes3, timingSafeEqual } from "crypto";
45724
46026
  import { join as join34 } from "path";
45725
46027
  function controlTokenPath(profileDir2) {
@@ -45730,7 +46032,7 @@ function writeControlToken(profileDir2) {
45730
46032
  const path = controlTokenPath(profileDir2);
45731
46033
  writeFileSync10(path, token, { mode: 384 });
45732
46034
  try {
45733
- chmodSync3(path, 384);
46035
+ chmodSync4(path, 384);
45734
46036
  } catch {
45735
46037
  }
45736
46038
  return token;
@@ -45754,7 +46056,7 @@ function tokenMatches(expected, given) {
45754
46056
  function startControlServer(bus, profileDir2, info) {
45755
46057
  const socketPath = join35(profileDir2, CONTROL_SOCKET_FILE);
45756
46058
  try {
45757
- rmSync9(socketPath, { force: true });
46059
+ rmSync10(socketPath, { force: true });
45758
46060
  } catch {
45759
46061
  }
45760
46062
  const clients = /* @__PURE__ */ new Set();
@@ -45807,8 +46109,10 @@ function startControlServer(bus, profileDir2, info) {
45807
46109
  if (!authed) {
45808
46110
  if (frame.t === "auth" && tokenMatches(token, frame.token)) {
45809
46111
  authed = true;
45810
- clients.add(sock);
45811
- sendSnapshot(sock);
46112
+ if (!frame.rpc) {
46113
+ clients.add(sock);
46114
+ sendSnapshot(sock);
46115
+ }
45812
46116
  } else {
45813
46117
  send(sock, { t: "authError" });
45814
46118
  sock.destroy();
@@ -45818,6 +46122,9 @@ function startControlServer(bus, profileDir2, info) {
45818
46122
  if (frame.t === "submit") bus.submitLocal(frame.input);
45819
46123
  else if (frame.t === "reconnect") bus.requestReconnect();
45820
46124
  else if (frame.t === "autonomousRun") bus.submitAutonomous();
46125
+ else if (frame.t === "gitToken") {
46126
+ void issueGitToken(frame.scope, frame.host, frame.path).then((tok) => send(sock, { t: "gitToken", token: tok }));
46127
+ }
45821
46128
  }
45822
46129
  });
45823
46130
  const drop = () => {
@@ -45830,7 +46137,7 @@ function startControlServer(bus, profileDir2, info) {
45830
46137
  });
45831
46138
  server.listen(socketPath, () => {
45832
46139
  try {
45833
- chmodSync4(socketPath, 384);
46140
+ chmodSync5(socketPath, 384);
45834
46141
  } catch {
45835
46142
  }
45836
46143
  });
@@ -45840,8 +46147,8 @@ function startControlServer(bus, profileDir2, info) {
45840
46147
  clients.clear();
45841
46148
  server.close();
45842
46149
  try {
45843
- rmSync9(socketPath, { force: true });
45844
- rmSync9(controlTokenPath(profileDir2), { force: true });
46150
+ rmSync10(socketPath, { force: true });
46151
+ rmSync10(controlTokenPath(profileDir2), { force: true });
45845
46152
  } catch {
45846
46153
  }
45847
46154
  };
@@ -50923,24 +51230,24 @@ var App = class extends import_react9.PureComponent {
50923
51230
  })
50924
51231
  }));
50925
51232
  };
50926
- findNextFocusable = (state) => {
50927
- const activeIndex = state.focusables.findIndex((focusable) => {
50928
- return focusable.id === state.activeFocusId;
51233
+ findNextFocusable = (state2) => {
51234
+ const activeIndex = state2.focusables.findIndex((focusable) => {
51235
+ return focusable.id === state2.activeFocusId;
50929
51236
  });
50930
- for (let index = activeIndex + 1; index < state.focusables.length; index++) {
50931
- const focusable = state.focusables[index];
51237
+ for (let index = activeIndex + 1; index < state2.focusables.length; index++) {
51238
+ const focusable = state2.focusables[index];
50932
51239
  if (focusable?.isActive) {
50933
51240
  return focusable.id;
50934
51241
  }
50935
51242
  }
50936
51243
  return void 0;
50937
51244
  };
50938
- findPreviousFocusable = (state) => {
50939
- const activeIndex = state.focusables.findIndex((focusable) => {
50940
- return focusable.id === state.activeFocusId;
51245
+ findPreviousFocusable = (state2) => {
51246
+ const activeIndex = state2.focusables.findIndex((focusable) => {
51247
+ return focusable.id === state2.activeFocusId;
50941
51248
  });
50942
51249
  for (let index = activeIndex - 1; index >= 0; index--) {
50943
- const focusable = state.focusables[index];
51250
+ const focusable = state2.focusables[index];
50944
51251
  if (focusable?.isActive) {
50945
51252
  return focusable.id;
50946
51253
  }
@@ -51136,8 +51443,8 @@ var Ink = class {
51136
51443
  }
51137
51444
  }
51138
51445
  async waitUntilExit() {
51139
- this.exitPromise ||= new Promise((resolve11, reject) => {
51140
- this.resolveExitPromise = resolve11;
51446
+ this.exitPromise ||= new Promise((resolve12, reject) => {
51447
+ this.resolveExitPromise = resolve12;
51141
51448
  this.rejectExitPromise = reject;
51142
51449
  });
51143
51450
  return this.exitPromise;
@@ -51504,7 +51811,7 @@ var import_react21 = __toESM(require_react(), 1);
51504
51811
  var import_react26 = __toESM(require_react(), 1);
51505
51812
 
51506
51813
  // src/core/input-history.ts
51507
- import { existsSync as existsSync19, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "fs";
51814
+ import { existsSync as existsSync20, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "fs";
51508
51815
  import { join as join36 } from "path";
51509
51816
  var MAX_HISTORY = 200;
51510
51817
  function historyPath(profileDir2) {
@@ -51512,7 +51819,7 @@ function historyPath(profileDir2) {
51512
51819
  }
51513
51820
  function loadInputHistory(profileDir2) {
51514
51821
  const path = historyPath(profileDir2);
51515
- if (!existsSync19(path)) return [];
51822
+ if (!existsSync20(path)) return [];
51516
51823
  try {
51517
51824
  const data = JSON.parse(readFileSync20(path, "utf8"));
51518
51825
  return Array.isArray(data) ? data.filter((x) => typeof x === "string") : [];
@@ -52190,7 +52497,7 @@ async function runStart(profileDir2, profileName) {
52190
52497
  logger.error("ws.run.crash", { message: e.message, stack: e.stack });
52191
52498
  if (client.isStopped) return;
52192
52499
  bus.log(t("start.recovered", { message: e.message }), "error");
52193
- await new Promise((resolve11) => setTimeout(resolve11, 1e3));
52500
+ await new Promise((resolve12) => setTimeout(resolve12, 1e3));
52194
52501
  }
52195
52502
  }
52196
52503
  };
@@ -52225,6 +52532,7 @@ async function runStart(profileDir2, profileName) {
52225
52532
  }
52226
52533
  } finally {
52227
52534
  stopIdleClear?.();
52535
+ await revokeAll().catch(() => void 0);
52228
52536
  stopControl?.();
52229
52537
  release();
52230
52538
  }
@@ -52236,7 +52544,7 @@ import { join as join38 } from "path";
52236
52544
  // src/core/control-client.ts
52237
52545
  import { connect as connect2 } from "net";
52238
52546
  function connectControl(socketPath, bus, token) {
52239
- return new Promise((resolve11, reject) => {
52547
+ return new Promise((resolve12, reject) => {
52240
52548
  const socket = connect2(socketPath);
52241
52549
  socket.setEncoding("utf8");
52242
52550
  let ready = false;
@@ -52289,7 +52597,7 @@ function connectControl(socketPath, bus, token) {
52289
52597
  bus.setTokens(frame.tokens);
52290
52598
  for (const entry of frame.transcript) bus.pushEntry(entry);
52291
52599
  ready = true;
52292
- resolve11({ info: frame.info, close });
52600
+ resolve12({ info: frame.info, close });
52293
52601
  break;
52294
52602
  case "transcript":
52295
52603
  bus.pushEntry(frame.entry);
@@ -52377,7 +52685,7 @@ async function runAutoRun(profileDir2, profileName) {
52377
52685
  initI18n(readProfileConfig(profileDir2).locale);
52378
52686
  const socketPath = join39(profileDir2, CONTROL_SOCKET_FILE);
52379
52687
  const token = readControlToken(profileDir2);
52380
- const code = await new Promise((resolve11) => {
52688
+ const code = await new Promise((resolve12) => {
52381
52689
  let settled = false;
52382
52690
  const done = (c) => {
52383
52691
  if (settled) return;
@@ -52387,7 +52695,7 @@ async function runAutoRun(profileDir2, profileName) {
52387
52695
  socket.destroy();
52388
52696
  } catch {
52389
52697
  }
52390
- resolve11(c);
52698
+ resolve12(c);
52391
52699
  };
52392
52700
  const socket = connect3(socketPath);
52393
52701
  socket.setEncoding("utf8");
@@ -52430,21 +52738,91 @@ async function runAutoRun(profileDir2, profileName) {
52430
52738
  process.exitCode = code;
52431
52739
  }
52432
52740
 
52741
+ // src/commands/git-credential.ts
52742
+ import { connect as connect4 } from "net";
52743
+ import { join as join40 } from "path";
52744
+ var REQUEST_TIMEOUT_MS = 2e4;
52745
+ function requestToken(host, path) {
52746
+ const profileDir2 = process.env[PROFILE_DIR_ENV];
52747
+ if (!profileDir2) return Promise.resolve(null);
52748
+ const token = readControlToken(profileDir2);
52749
+ return new Promise((resolve12) => {
52750
+ let done = false;
52751
+ const finish = (value) => {
52752
+ if (done) return;
52753
+ done = true;
52754
+ clearTimeout(timer);
52755
+ socket.destroy();
52756
+ resolve12(value);
52757
+ };
52758
+ const socket = connect4(join40(profileDir2, CONTROL_SOCKET_FILE));
52759
+ socket.setEncoding("utf8");
52760
+ const timer = setTimeout(() => finish(null), REQUEST_TIMEOUT_MS);
52761
+ socket.on("connect", () => {
52762
+ try {
52763
+ socket.write(encodeFrame({ t: "auth", token: token ?? "", rpc: true }));
52764
+ socket.write(encodeFrame({ t: "gitToken", scope: process.env[JOB_ID_ENV] ?? "", host, path }));
52765
+ } catch {
52766
+ finish(null);
52767
+ }
52768
+ });
52769
+ const parse = createFrameParser();
52770
+ socket.on("data", (chunk) => {
52771
+ for (const frame of parse(chunk)) {
52772
+ if (frame.t === "gitToken") finish(frame.token);
52773
+ else if (frame.t === "authError") finish(null);
52774
+ }
52775
+ });
52776
+ socket.on("error", () => finish(null));
52777
+ socket.on("close", () => finish(null));
52778
+ });
52779
+ }
52780
+ async function readCredentialInput() {
52781
+ const chunks = [];
52782
+ for await (const chunk of process.stdin) chunks.push(chunk);
52783
+ const out = {};
52784
+ for (const line of Buffer.concat(chunks).toString("utf8").split("\n")) {
52785
+ const i = line.indexOf("=");
52786
+ if (i > 0) out[line.slice(0, i)] = line.slice(i + 1).trim();
52787
+ }
52788
+ return out;
52789
+ }
52790
+ async function runGitCredential(action) {
52791
+ if (action !== "get") {
52792
+ await readCredentialInput().catch(() => void 0);
52793
+ return;
52794
+ }
52795
+ const input = await readCredentialInput();
52796
+ const host = (input.host ?? "").toLowerCase();
52797
+ if (input.protocol !== "https" || !host || host !== (process.env[GIT_HOST_ENV] ?? "").toLowerCase()) return;
52798
+ const token = await requestToken(host, input.path ?? "");
52799
+ if (token) process.stdout.write(`username=x-access-token
52800
+ password=${token}
52801
+ `);
52802
+ }
52803
+ async function runGitToken() {
52804
+ const host = process.env[GIT_HOST_ENV] ?? "";
52805
+ if (!host) return;
52806
+ const token = await requestToken(host, "");
52807
+ if (token) process.stdout.write(`${token}
52808
+ `);
52809
+ }
52810
+
52433
52811
  // src/commands/ai-login.ts
52434
52812
  import { spawn as spawn12 } from "child_process";
52435
- import { mkdirSync as mkdirSync9 } from "fs";
52813
+ import { mkdirSync as mkdirSync10 } from "fs";
52436
52814
  import { basename as basename8 } from "path";
52437
52815
  function runProviderLogin(cmd, envVar, dir2) {
52438
- return new Promise((resolve11) => {
52816
+ return new Promise((resolve12) => {
52439
52817
  const child = spawn12(cmd, [], {
52440
52818
  stdio: "inherit",
52441
52819
  env: { ...process.env, [envVar]: dir2 }
52442
52820
  });
52443
52821
  child.on("error", (err) => {
52444
52822
  console.error(t("aiLogin.launchFailed", { cmd, message: err.message }));
52445
- resolve11(1);
52823
+ resolve12(1);
52446
52824
  });
52447
- child.on("exit", (code) => resolve11(code ?? 0));
52825
+ child.on("exit", (code) => resolve12(code ?? 0));
52448
52826
  });
52449
52827
  }
52450
52828
  async function runAiLogin(profileDir2, profileName, aiFilter) {
@@ -52469,7 +52847,7 @@ async function runAiLogin(profileDir2, profileName, aiFilter) {
52469
52847
  continue;
52470
52848
  }
52471
52849
  const dir2 = resolveHomePath(cred.profile);
52472
- mkdirSync9(dir2, { recursive: true });
52850
+ mkdirSync10(dir2, { recursive: true });
52473
52851
  console.log(t("aiLogin.loggingIn", { provider: cred.provider, profile: cred.profile, envVar, dir: dir2 }));
52474
52852
  console.log(t("aiLogin.completeSignIn"));
52475
52853
  const code = await runProviderLogin(cred.provider, envVar, dir2);
@@ -52481,7 +52859,7 @@ async function runAiLogin(profileDir2, profileName, aiFilter) {
52481
52859
  }
52482
52860
 
52483
52861
  // src/commands/unlink.ts
52484
- import { rmSync as rmSync10 } from "fs";
52862
+ import { rmSync as rmSync11 } from "fs";
52485
52863
  async function runUnlink(profileDir2, profileName) {
52486
52864
  initI18n(readProfileConfig(profileDir2).locale);
52487
52865
  const cred = readCredential(profileDir2, profileName);
@@ -52495,7 +52873,7 @@ async function runUnlink(profileDir2, profileName) {
52495
52873
  } catch (err) {
52496
52874
  console.log(t("unlink.revokeFailed", { message: err.message }));
52497
52875
  }
52498
- rmSync10(profileDir2, { recursive: true, force: true });
52876
+ rmSync11(profileDir2, { recursive: true, force: true });
52499
52877
  clearDefaultProfileIf(profileName);
52500
52878
  console.log(t("unlink.deleted"));
52501
52879
  }
@@ -52584,10 +52962,10 @@ function Picker({
52584
52962
  ] });
52585
52963
  }
52586
52964
  function selectFromList(items, title) {
52587
- return new Promise((resolve11) => {
52965
+ return new Promise((resolve12) => {
52588
52966
  let chosen = null;
52589
52967
  const app = render_default(/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Picker, { items, title, onDone: (v2) => chosen = v2 }));
52590
- void app.waitUntilExit().then(() => resolve11(chosen));
52968
+ void app.waitUntilExit().then(() => resolve12(chosen));
52591
52969
  });
52592
52970
  }
52593
52971
 
@@ -52654,6 +53032,14 @@ async function main() {
52654
53032
  await runAttach(profileDir(name), name);
52655
53033
  break;
52656
53034
  }
53035
+ case "git-credential": {
53036
+ await runGitCredential(rest[0]);
53037
+ break;
53038
+ }
53039
+ case "git-token": {
53040
+ await runGitToken();
53041
+ break;
53042
+ }
52657
53043
  case "auto-run": {
52658
53044
  const name = await pickLinkedProfile(explicitProfile, "auto-run");
52659
53045
  if (!name) {