@4pm/cli 1.22.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 +618 -217
  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
  }
@@ -39291,13 +39334,25 @@ function claudeHomeDirs(config) {
39291
39334
  return dirs.length > 0 ? dirs : [join9(homedir3(), ".claude")];
39292
39335
  }
39293
39336
  function profileDisplayLabel(dir2) {
39337
+ return profileAccountEmail(dir2) ?? basename(dir2);
39338
+ }
39339
+ function profileAccountEmail(dir2) {
39294
39340
  try {
39295
39341
  const raw = JSON.parse(readFileSync8(join9(dir2, ".claude.json"), "utf8"));
39296
- const email = raw.oauthAccount?.emailAddress;
39297
- if (email && email.trim()) return email;
39342
+ const email = raw.oauthAccount?.emailAddress?.trim();
39343
+ return email ? email : null;
39298
39344
  } catch {
39345
+ return null;
39299
39346
  }
39300
- return basename(dir2);
39347
+ }
39348
+ function aiAccountLabels(config) {
39349
+ const labels = isUnifiedConfig(config) ? config.aiProfiles.filter(isUsableCredential).map((c) => {
39350
+ const dir2 = resolveHomePath(c.profile);
39351
+ return profileAccountEmail(dir2) ?? (c.label?.trim() || basename(dir2));
39352
+ }) : [...profileDirs(config.claudeHome), ...profileDirs(config.codexHome), ...profileDirs(config.antigravityHome)].map(
39353
+ (dir2) => profileDisplayLabel(dir2)
39354
+ );
39355
+ return [...new Set(labels)];
39301
39356
  }
39302
39357
  function profileEnvVar(cmd) {
39303
39358
  if (cmd.includes("claude")) return "CLAUDE_CONFIG_DIR";
@@ -39426,16 +39481,16 @@ async function fetchUsage(token) {
39426
39481
  }
39427
39482
  }
39428
39483
  function runUsageCli(dir2, aiCli2) {
39429
- return new Promise((resolve11) => {
39484
+ return new Promise((resolve12) => {
39430
39485
  let out = "";
39431
39486
  let child = null;
39432
39487
  const finish = () => {
39433
39488
  clearTimeout(timer);
39434
- resolve11(out);
39489
+ resolve12(out);
39435
39490
  };
39436
39491
  const timer = setTimeout(() => {
39437
39492
  child?.kill();
39438
- resolve11(out);
39493
+ resolve12(out);
39439
39494
  }, 3e4);
39440
39495
  timer.unref?.();
39441
39496
  try {
@@ -39925,6 +39980,7 @@ var NO_USAGE = { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0
39925
39980
  var execFileAsync = promisify3(execFile3);
39926
39981
  var MAX_CONTEXT_CHARS = 12e4;
39927
39982
  var ANSWER_TIMEOUT_MS = 12e4;
39983
+ var LEGAL_DRAFT_TIMEOUT_MS = 54e4;
39928
39984
  var DOC_EXTENSIONS = [".md", ".mdx", ".txt"];
39929
39985
  function kbRoot(profileDir2) {
39930
39986
  return join14(profileDir2, "support-kb");
@@ -40036,6 +40092,7 @@ function buildPrompt(docs, question, askerRole) {
40036
40092
  ].join("\n");
40037
40093
  }
40038
40094
  function buildDraftPrompt(docs, context, task) {
40095
+ if (task === "legal_draft") return buildLegalDraftPrompt(docs, context);
40039
40096
  const goal = task === "reply_draft" ? [
40040
40097
  "You are drafting a reply FROM the 4PM support team TO the customer in the support ticket below.",
40041
40098
  "Address the customer's latest message, using the whole conversation for context. Be accurate,",
@@ -40062,6 +40119,31 @@ function buildDraftPrompt(docs, context, task) {
40062
40119
  context
40063
40120
  ].join("\n");
40064
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
+ }
40065
40147
  function imageExt(mime) {
40066
40148
  switch (mime) {
40067
40149
  case "image/png":
@@ -40103,8 +40185,8 @@ function parseModeratedAnswer(text) {
40103
40185
  }
40104
40186
  return { body: text.trim() };
40105
40187
  }
40106
- function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = []) {
40107
- return new Promise((resolve11, reject) => {
40188
+ function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = [], timeoutMs = ANSWER_TIMEOUT_MS) {
40189
+ return new Promise((resolve12, reject) => {
40108
40190
  const isClaude = cmd.includes("claude");
40109
40191
  const args = [
40110
40192
  "-p",
@@ -40135,7 +40217,7 @@ function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = [])
40135
40217
  const timer = setTimeout(() => {
40136
40218
  child.kill("SIGKILL");
40137
40219
  reject(new Error("claude answer timed out"));
40138
- }, ANSWER_TIMEOUT_MS);
40220
+ }, timeoutMs);
40139
40221
  timer.unref();
40140
40222
  child.stdout.on("data", (d) => out += parser.push(d.toString()));
40141
40223
  child.stderr.on("data", (d) => err += d.toString());
@@ -40148,17 +40230,25 @@ function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = [])
40148
40230
  clearTimeout(timer);
40149
40231
  cleanup();
40150
40232
  out += parser.flush();
40151
- resolve11({ code: code ?? -1, out, err, usage: parser.usage() });
40233
+ resolve12({ code: code ?? -1, out, err, usage: parser.usage() });
40152
40234
  });
40153
40235
  child.stdin.write(prompt);
40154
40236
  child.stdin.end();
40155
40237
  });
40156
40238
  }
40157
- async function runClaudeWithFailover(prompt, ai2, imageDir) {
40239
+ async function runClaudeWithFailover(prompt, ai2, imageDir, timeoutMs = ANSWER_TIMEOUT_MS) {
40158
40240
  const attempts = ai2.profiles.length > 0 ? ai2.profiles : [null];
40159
40241
  let lastReason = "no attempt";
40160
40242
  for (let i = 0; i < attempts.length; i++) {
40161
- 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
+ );
40162
40252
  if (code === 0 && out.trim()) return { text: out.trim(), usage };
40163
40253
  const combined = err || out;
40164
40254
  lastReason = `exited ${code}: ${combined.slice(0, 500)}`;
@@ -40179,7 +40269,8 @@ async function runSupportAnswer(req, ai2, profileDir2) {
40179
40269
  const docs = collectDocs(dir2);
40180
40270
  if (!docs.trim()) return { body: "", error: "KB repo has no documentation" };
40181
40271
  const prompt = req.task ? buildDraftPrompt(docs, question, req.task) : buildPrompt(docs, question, req.askerRole);
40182
- 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);
40183
40274
  if (!text) return { body: "", error: "empty answer" };
40184
40275
  const { body, moderation } = req.task ? { body: text.trim(), moderation: void 0 } : parseModeratedAnswer(text);
40185
40276
  if (!body) return { body: "", error: "empty answer" };
@@ -40416,10 +40507,10 @@ async function crontabList() {
40416
40507
  }
40417
40508
  }
40418
40509
  async function crontabSet(content) {
40419
- return new Promise((resolve11) => {
40510
+ return new Promise((resolve12) => {
40420
40511
  const child = spawn5("crontab", ["-"], { stdio: ["pipe", "ignore", "ignore"] });
40421
- child.on("error", () => resolve11(false));
40422
- child.on("close", (code) => resolve11(code === 0));
40512
+ child.on("error", () => resolve12(false));
40513
+ child.on("close", (code) => resolve12(code === 0));
40423
40514
  child.stdin.end(content.endsWith("\n") ? content : content + "\n");
40424
40515
  });
40425
40516
  }
@@ -40588,14 +40679,14 @@ function detectContainerized() {
40588
40679
  }
40589
40680
  }
40590
40681
  function canReach(host, port) {
40591
- return new Promise((resolve11) => {
40682
+ return new Promise((resolve12) => {
40592
40683
  const socket = connect({ host, port });
40593
40684
  let done = false;
40594
40685
  const finish = (ok) => {
40595
40686
  if (done) return;
40596
40687
  done = true;
40597
40688
  socket.destroy();
40598
- resolve11(ok);
40689
+ resolve12(ok);
40599
40690
  };
40600
40691
  socket.setTimeout(PROBE_TIMEOUT_MS);
40601
40692
  socket.once("connect", () => finish(true));
@@ -40648,32 +40739,177 @@ async function probeNetwork() {
40648
40739
  }
40649
40740
 
40650
40741
  // src/core/git-auth.ts
40651
- import { writeFileSync as writeFileSync8 } from "fs";
40652
- import { homedir as homedir4 } from "os";
40653
- import { join as join18 } from "path";
40654
- function tokenTarget(method) {
40655
- if (method === "gitlab-group-token") {
40656
- return { host: process.env.FOURPM_GIT_HOST || "gitlab.com", user: "oauth2" };
40657
- }
40658
- if (method === "github-app") {
40659
- 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;
40660
40766
  }
40661
40767
  return null;
40662
40768
  }
40663
- function applyGitAuth(method) {
40664
- if (!method) return false;
40665
- const target = tokenTarget(method);
40666
- const token = process.env.FOURPM_GIT_TOKEN;
40667
- if (!target || !token) return false;
40668
- try {
40669
- 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}" "$@"
40670
40783
  `;
40671
- 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 });
40672
40846
  return true;
40673
- } catch {
40847
+ } catch (err) {
40848
+ logger.warn("git.auth.configure.failed", { error: String(err) });
40674
40849
  return false;
40675
40850
  }
40676
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
+ }
40677
40913
 
40678
40914
  // src/core/ws-client/transcript.ts
40679
40915
  function toDtoEntry(e) {
@@ -40704,13 +40940,18 @@ import { randomUUID as randomCommandId } from "crypto";
40704
40940
  // src/core/executor.ts
40705
40941
  import { spawn as spawn6 } from "child_process";
40706
40942
  var KILL_GRACE_MS = 5e3;
40707
- async function runCommand(dispatch, emit, opts) {
40943
+ async function runCommand(dispatch, emitRaw, opts) {
40708
40944
  let seq = 0;
40945
+ const emit = (output) => {
40946
+ emitRaw(output);
40947
+ if (output.done) void endGitScope(dispatch.commandId);
40948
+ };
40709
40949
  let child;
40710
40950
  try {
40711
40951
  child = spawn6(dispatch.cmd, dispatch.args ?? [], {
40712
40952
  cwd: dispatch.path,
40713
- 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 },
40714
40955
  shell: false
40715
40956
  });
40716
40957
  } catch (err) {
@@ -40767,7 +41008,7 @@ async function runCommand(dispatch, emit, opts) {
40767
41008
  });
40768
41009
  child.stdout?.on("data", (data) => batcher.push(data.toString("utf8")));
40769
41010
  child.stderr?.on("data", (data) => batcher.push(data.toString("utf8")));
40770
- await new Promise((resolve11) => {
41011
+ await new Promise((resolve12) => {
40771
41012
  child.on("close", (exitCode) => {
40772
41013
  clearTimers();
40773
41014
  batcher.flush();
@@ -40780,7 +41021,7 @@ async function runCommand(dispatch, emit, opts) {
40780
41021
  // server settles the command as failed and the web surfaces the note instead of spinning.
40781
41022
  exitCode: timedOut ? 124 : exitCode ?? -1
40782
41023
  });
40783
- resolve11();
41024
+ resolve12();
40784
41025
  });
40785
41026
  child.on("error", (err) => {
40786
41027
  clearTimers();
@@ -40792,7 +41033,7 @@ async function runCommand(dispatch, emit, opts) {
40792
41033
  done: true,
40793
41034
  exitCode: -1
40794
41035
  });
40795
- resolve11();
41036
+ resolve12();
40796
41037
  });
40797
41038
  });
40798
41039
  }
@@ -40815,15 +41056,15 @@ function isOutOfCredits(text) {
40815
41056
  );
40816
41057
  }
40817
41058
  function runAttempt(commandId, cmd, args, stdin, cwd2, env3, onChunk, timeoutMs) {
40818
- return new Promise((resolve11) => {
41059
+ return new Promise((resolve12) => {
40819
41060
  runCommand(
40820
41061
  { commandId, cmd, args, path: cwd2, env: env3 },
40821
41062
  (out) => {
40822
41063
  if (out.chunk) onChunk(out.chunk);
40823
- if (out.done) resolve11(out.exitCode ?? -1);
41064
+ if (out.done) resolve12(out.exitCode ?? -1);
40824
41065
  },
40825
41066
  { stdin, ...timeoutMs > 0 ? { timeoutMs } : {} }
40826
- ).catch(() => resolve11(-1));
41067
+ ).catch(() => resolve12(-1));
40827
41068
  });
40828
41069
  }
40829
41070
  async function runAiFailover(plan, commandId, cwd2, handlers, timeoutMs = 0) {
@@ -40906,7 +41147,7 @@ function formatTimestampInZone(date, timeZone) {
40906
41147
  }
40907
41148
 
40908
41149
  // src/ui/slash-commands.ts
40909
- import { existsSync as existsSync12 } from "fs";
41150
+ import { existsSync as existsSync13 } from "fs";
40910
41151
  import { join as join19 } from "path";
40911
41152
  var CONFIG_KEYS = [
40912
41153
  "aiCli",
@@ -40969,7 +41210,7 @@ function runConfig(ctx) {
40969
41210
  ctx.print(t("slash.config.wroteDefault", { verb, path }));
40970
41211
  ctx.print(t("slash.config.addClaudeHint"));
40971
41212
  };
40972
- if (existsSync12(path)) {
41213
+ if (existsSync13(path)) {
40973
41214
  ctx.confirm(
40974
41215
  t("slash.config.replaceConfirm", { path }),
40975
41216
  () => writeDefaults(t("slash.config.verbReplaced"))
@@ -41031,8 +41272,8 @@ function runHistory(ctx) {
41031
41272
  ctx.print(t("slash.history.header", { n: recent.length, total: all.length }));
41032
41273
  recent.forEach((e, i) => {
41033
41274
  const time = new Date(e.startedAt).toLocaleTimeString();
41034
- const state = e.status === "done" ? "\u2713" : e.status === "failed" ? `\u2717 exit ${e.exitCode}` : t("slash.history.running");
41035
- 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}`);
41036
41277
  });
41037
41278
  }
41038
41279
  function runOutput(ctx) {
@@ -41043,8 +41284,8 @@ function runOutput(ctx) {
41043
41284
  return;
41044
41285
  }
41045
41286
  const entry = all.slice().reverse()[n - 1];
41046
- const state = entry.exitCode != null ? ` (exit ${entry.exitCode})` : "";
41047
- 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 }));
41048
41289
  const out = readCommandOutput(entry.commandId);
41049
41290
  if (out == null) {
41050
41291
  ctx.print(t("slash.output.none"), "warn");
@@ -41339,7 +41580,7 @@ function buildPrompt2(input) {
41339
41580
  ].join("\n");
41340
41581
  }
41341
41582
  function runOnce(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
41342
- return new Promise((resolve11, reject) => {
41583
+ return new Promise((resolve12, reject) => {
41343
41584
  const args = [
41344
41585
  "-p",
41345
41586
  "--output-format",
@@ -41374,7 +41615,7 @@ function runOnce(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
41374
41615
  });
41375
41616
  child.on("close", (code) => {
41376
41617
  clearTimeout(timer);
41377
- resolve11({ code: code ?? -1, out, err });
41618
+ resolve12({ code: code ?? -1, out, err });
41378
41619
  });
41379
41620
  child.stdin.write(prompt);
41380
41621
  child.stdin.end();
@@ -41412,6 +41653,54 @@ async function setCommitAuthor(cwd2, machineUsername) {
41412
41653
  }
41413
41654
  }
41414
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
+
41415
41704
  // src/core/ws-client/command-dispatch.ts
41416
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:";
41417
41706
  function handleCommandChannels(ctx, message, payload) {
@@ -41665,8 +41954,28 @@ ${guardedPrompt}`;
41665
41954
  }
41666
41955
  };
41667
41956
  let result;
41957
+ let queueTimedOut = false;
41668
41958
  try {
41669
- 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
+ }
41670
41979
  } finally {
41671
41980
  ctx.bus.endBusy(plan.cmd);
41672
41981
  }
@@ -41727,7 +42036,7 @@ ${guardedPrompt}`;
41727
42036
  });
41728
42037
  ctx.bus.addTokens?.(result.usage.tokens);
41729
42038
  }
41730
- ctx.reportUsage(events, profileLabel);
42039
+ if (!queueTimedOut) ctx.reportUsage(events, profileLabel);
41731
42040
  void ctx.pollUsage();
41732
42041
  ctx.bus.push({
41733
42042
  source: origin,
@@ -42210,11 +42519,11 @@ async function allAiProfilesExhausted(ctx, cfg) {
42210
42519
 
42211
42520
  // src/core/fs-browse.ts
42212
42521
  import { readdir as readdir3 } from "fs/promises";
42213
- import { resolve as resolve3, sep as sep2 } from "path";
42522
+ import { resolve as resolve4, sep as sep2 } from "path";
42214
42523
  async function listDir(path, root) {
42215
42524
  if (!root) return { path: "", entries: [] };
42216
- const base = resolve3(root);
42217
- const requested = resolve3(base, path && path.trim() ? path : ".");
42525
+ const base = resolve4(root);
42526
+ const requested = resolve4(base, path && path.trim() ? path : ".");
42218
42527
  const target = requested === base || requested.startsWith(base + sep2) ? requested : base;
42219
42528
  try {
42220
42529
  const dirents = await readdir3(target, { withFileTypes: true });
@@ -42233,12 +42542,12 @@ async function listDir(path, root) {
42233
42542
 
42234
42543
  // src/core/fs-read.ts
42235
42544
  import { readFile as readFile6 } from "fs/promises";
42236
- import { resolve as resolve4, sep as sep3 } from "path";
42545
+ import { resolve as resolve5, sep as sep3 } from "path";
42237
42546
  var MAX_BYTES = 256 * 1024;
42238
42547
  function resolveInRoot(root, relPath) {
42239
42548
  if (!root) return null;
42240
- const base = resolve4(root);
42241
- const target = resolve4(base, relPath || ".");
42549
+ const base = resolve5(root);
42550
+ const target = resolve5(base, relPath || ".");
42242
42551
  return target === base || target.startsWith(base + sep3) ? target : null;
42243
42552
  }
42244
42553
  async function readWorkerFile(root, path) {
@@ -42258,12 +42567,12 @@ async function readWorkerFile(root, path) {
42258
42567
 
42259
42568
  // src/core/fs-write.ts
42260
42569
  import { mkdir as mkdir4, writeFile as writeFile5 } from "fs/promises";
42261
- 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";
42262
42571
  var MAX_BYTES2 = 2 * 1024 * 1024;
42263
42572
  function resolveInRoot2(root, relPath) {
42264
42573
  if (!root) return null;
42265
- const base = resolve5(root);
42266
- const target = resolve5(base, relPath || ".");
42574
+ const base = resolve6(root);
42575
+ const target = resolve6(base, relPath || ".");
42267
42576
  return target === base || target.startsWith(base + sep4) ? target : null;
42268
42577
  }
42269
42578
  async function writeWorkerFile(root, relPath, content) {
@@ -42276,7 +42585,7 @@ async function writeWorkerFile(root, relPath, content) {
42276
42585
  return { ok: false, path: target, bytes: 0, error: `content too large (max ${MAX_BYTES2} bytes)` };
42277
42586
  }
42278
42587
  try {
42279
- await mkdir4(dirname4(target), { recursive: true });
42588
+ await mkdir4(dirname5(target), { recursive: true });
42280
42589
  await writeFile5(target, content, "utf8");
42281
42590
  return { ok: true, path: target, bytes };
42282
42591
  } catch (err) {
@@ -42286,11 +42595,11 @@ async function writeWorkerFile(root, relPath, content) {
42286
42595
 
42287
42596
  // src/core/fs-mutate.ts
42288
42597
  import { mkdir as mkdir5, writeFile as writeFile6, rename, rm as rm3, stat as stat2 } from "fs/promises";
42289
- 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";
42290
42599
  var MAX_BYTES3 = 2 * 1024 * 1024;
42291
42600
  function resolveInRoot3(root, rel) {
42292
- const base = resolve6(root);
42293
- const target = resolve6(base, rel || ".");
42601
+ const base = resolve7(root);
42602
+ const target = resolve7(base, rel || ".");
42294
42603
  return target === base || target.startsWith(base + sep5) ? target : null;
42295
42604
  }
42296
42605
  async function isDir(p2) {
@@ -42315,19 +42624,19 @@ async function mutateFs(root, req) {
42315
42624
  switch (req.op) {
42316
42625
  case "mkdir": {
42317
42626
  const target = resolveInRoot3(root, req.path ?? "");
42318
- if (!target || target === resolve6(root)) return escaped(req.path ?? "");
42627
+ if (!target || target === resolve7(root)) return escaped(req.path ?? "");
42319
42628
  await mkdir5(target, { recursive: true });
42320
42629
  return { ok: true, path: target };
42321
42630
  }
42322
42631
  case "create": {
42323
42632
  const target = resolveInRoot3(root, req.path ?? "");
42324
- if (!target || target === resolve6(root)) return escaped(req.path ?? "");
42633
+ if (!target || target === resolve7(root)) return escaped(req.path ?? "");
42325
42634
  if (await exists(target)) return { ok: false, path: target, error: "already exists" };
42326
42635
  const content = req.content ?? "";
42327
42636
  if (Buffer.byteLength(content, "utf8") > MAX_BYTES3) {
42328
42637
  return { ok: false, path: target, error: `content too large (max ${MAX_BYTES3} bytes)` };
42329
42638
  }
42330
- await mkdir5(dirname5(target), { recursive: true });
42639
+ await mkdir5(dirname6(target), { recursive: true });
42331
42640
  await writeFile6(target, content, "utf8");
42332
42641
  return { ok: true, path: target };
42333
42642
  }
@@ -42335,18 +42644,18 @@ async function mutateFs(root, req) {
42335
42644
  const from = resolveInRoot3(root, req.from ?? "");
42336
42645
  let to2 = resolveInRoot3(root, req.to ?? "");
42337
42646
  if (!from || !to2) return escaped(req.from ?? req.to ?? "");
42338
- if (from === resolve6(root)) return { ok: false, path: from, error: "cannot move the project root" };
42339
- if (await isDir(to2)) to2 = resolve6(to2, basename3(from));
42340
- 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 ?? "");
42341
42650
  if (await exists(to2)) return { ok: false, path: to2, error: "destination already exists" };
42342
- await mkdir5(dirname5(to2), { recursive: true });
42651
+ await mkdir5(dirname6(to2), { recursive: true });
42343
42652
  await rename(from, to2);
42344
42653
  return { ok: true, path: to2 };
42345
42654
  }
42346
42655
  case "delete": {
42347
42656
  const target = resolveInRoot3(root, req.path ?? "");
42348
42657
  if (!target) return escaped(req.path ?? "");
42349
- 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" };
42350
42659
  await rm3(target, { recursive: true, force: true });
42351
42660
  return { ok: true, path: target };
42352
42661
  }
@@ -42360,11 +42669,11 @@ async function mutateFs(root, req) {
42360
42669
 
42361
42670
  // src/core/fs-transfer.ts
42362
42671
  import { mkdir as mkdir6, readFile as readFile7, stat as stat3, writeFile as writeFile7 } from "fs/promises";
42363
- 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";
42364
42673
  function resolveInRoot4(root, relPath) {
42365
42674
  if (!root) return null;
42366
- const base = resolve7(root);
42367
- const target = resolve7(base, relPath || ".");
42675
+ const base = resolve8(root);
42676
+ const target = resolve8(base, relPath || ".");
42368
42677
  return target === base || target.startsWith(base + sep6) ? target : null;
42369
42678
  }
42370
42679
  var MIME_BY_EXT = {
@@ -42403,7 +42712,7 @@ async function uploadBinaryFile(root, relPath, contentBase64) {
42403
42712
  return { ok: false, path: target, bytes: 0, error: `file too large (max ${FS_TRANSFER_MAX_BYTES} bytes)` };
42404
42713
  }
42405
42714
  try {
42406
- await mkdir6(dirname6(target), { recursive: true });
42715
+ await mkdir6(dirname7(target), { recursive: true });
42407
42716
  await writeFile7(target, buf);
42408
42717
  return { ok: true, path: target, bytes: buf.byteLength };
42409
42718
  } catch (err) {
@@ -42480,13 +42789,13 @@ function handleFsChannels(ctx, message, payload) {
42480
42789
  // src/core/git-diff.ts
42481
42790
  import { readFile as readFile8 } from "fs/promises";
42482
42791
  import { execFile as execFile6 } from "child_process";
42483
- 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";
42484
42793
  import { promisify as promisify6 } from "util";
42485
42794
  var run6 = promisify6(execFile6);
42486
42795
  function resolveInRoot5(root, relPath) {
42487
42796
  if (!root) return null;
42488
- const base = resolve8(root);
42489
- const target = resolve8(base, relPath || ".");
42797
+ const base = resolve9(root);
42798
+ const target = resolve9(base, relPath || ".");
42490
42799
  return target === base || target.startsWith(base + sep7) ? target : null;
42491
42800
  }
42492
42801
  function looksBinary(buf) {
@@ -42501,7 +42810,7 @@ async function gitDiff(root, path) {
42501
42810
  if (!target) {
42502
42811
  return { path, oldContent: "", newContent: "", isBinary: false };
42503
42812
  }
42504
- const dir2 = dirname7(target);
42813
+ const dir2 = dirname8(target);
42505
42814
  const base = basename5(target);
42506
42815
  let newBuf = null;
42507
42816
  try {
@@ -42640,15 +42949,15 @@ async function manageSshKey(op, profileDir2) {
42640
42949
 
42641
42950
  // src/core/git-history.ts
42642
42951
  import { execFile as execFile9 } from "child_process";
42643
- import { resolve as resolve9, sep as sep8 } from "path";
42952
+ import { resolve as resolve10, sep as sep8 } from "path";
42644
42953
  import { promisify as promisify9 } from "util";
42645
42954
  var run9 = promisify9(execFile9);
42646
42955
  var FS = "";
42647
42956
  var RS = "";
42648
42957
  function resolveRepo(root, repo) {
42649
42958
  if (!root) return null;
42650
- const base = resolve9(root);
42651
- const requested = resolve9(base, repo && repo.trim() ? repo : ".");
42959
+ const base = resolve10(root);
42960
+ const requested = resolve10(base, repo && repo.trim() ? repo : ".");
42652
42961
  return requested === base || requested.startsWith(base + sep8) ? requested : base;
42653
42962
  }
42654
42963
  async function git(cwd2, args) {
@@ -42665,7 +42974,7 @@ async function git(cwd2, args) {
42665
42974
  }
42666
42975
  async function gitRepos(root) {
42667
42976
  if (!root) return { repos: [] };
42668
- const base = resolve9(root);
42977
+ const base = resolve10(root);
42669
42978
  const top = (await git(base, ["rev-parse", "--show-toplevel"])).trim();
42670
42979
  if (!top || !(top === base || top.startsWith(base + sep8))) return { repos: [] };
42671
42980
  const repos = [];
@@ -42677,7 +42986,7 @@ async function gitRepos(root) {
42677
42986
  const m2 = line.trim().match(/^[-+U ]?[0-9a-f]{7,40}\s+(\S+)/);
42678
42987
  const subPath = m2?.[1];
42679
42988
  if (!subPath) continue;
42680
- const abs = resolve9(base, subPath);
42989
+ const abs = resolve10(base, subPath);
42681
42990
  if (!(abs === base || abs.startsWith(base + sep8))) continue;
42682
42991
  const remote = (await git(abs, ["remote", "get-url", "origin"])).trim();
42683
42992
  const branch = (await git(abs, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
@@ -42906,7 +43215,7 @@ function handleMiscChannels(ctx, message, payload) {
42906
43215
  }
42907
43216
 
42908
43217
  // src/core/ws-client/handlers/project.ts
42909
- 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";
42910
43219
  import { join as join23 } from "path";
42911
43220
  function handleProjectChannels(ctx, message, payload) {
42912
43221
  switch (message.channel) {
@@ -42919,11 +43228,11 @@ function handleProjectChannels(ctx, message, payload) {
42919
43228
  const oldRoot = ctx.physicFolderPath(oldName);
42920
43229
  if (oldRoot) void uninstallCron(oldRoot).catch(() => void 0);
42921
43230
  }
42922
- if (oldPath && oldName !== sync.newName && existsSync13(oldPath) && !existsSync13(newPath)) {
43231
+ if (oldPath && oldName !== sync.newName && existsSync14(oldPath) && !existsSync14(newPath)) {
42923
43232
  renameSync(oldPath, newPath);
42924
43233
  } else {
42925
- if (oldPath && oldName !== sync.newName && existsSync13(oldPath)) rmSync6(oldPath, { recursive: true, force: true });
42926
- mkdirSync6(newPath, { recursive: true });
43234
+ if (oldPath && oldName !== sync.newName && existsSync14(oldPath)) rmSync7(oldPath, { recursive: true, force: true });
43235
+ mkdirSync7(newPath, { recursive: true });
42927
43236
  }
42928
43237
  ctx.physicRoot = ctx.physicFolderPath(sync.newName);
42929
43238
  ctx.bus.setProject(sync.newName);
@@ -42936,7 +43245,7 @@ function handleProjectChannels(ctx, message, payload) {
42936
43245
  if (del.name) {
42937
43246
  const delRoot = ctx.physicFolderPath(del.name);
42938
43247
  if (delRoot) void uninstallCron(delRoot).catch(() => void 0);
42939
- rmSync6(join23(ctx.profileDir, del.name), { recursive: true, force: true });
43248
+ rmSync7(join23(ctx.profileDir, del.name), { recursive: true, force: true });
42940
43249
  ctx.bus.log(t("project.folderDeleted", { name: del.name }));
42941
43250
  }
42942
43251
  void manageSshKey("delete", ctx.profileDir).catch(() => void 0);
@@ -43000,7 +43309,7 @@ Extra focus from the author: ${req.prompt}` : "";
43000
43309
  ].join("\n");
43001
43310
  }
43002
43311
  function runOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
43003
- return new Promise((resolve11, reject) => {
43312
+ return new Promise((resolve12, reject) => {
43004
43313
  const args = ["-p", ...profile?.model ? ["--model", profile.model] : [], ...denySettingsArgs(cmd, [...denyDirs, profile?.dir])];
43005
43314
  const env3 = {
43006
43315
  ...process.env,
@@ -43023,7 +43332,7 @@ function runOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
43023
43332
  });
43024
43333
  child.on("close", (code) => {
43025
43334
  clearTimeout(timer);
43026
- resolve11({ code: code ?? -1, out, err });
43335
+ resolve12({ code: code ?? -1, out, err });
43027
43336
  });
43028
43337
  child.stdin.write(prompt);
43029
43338
  child.stdin.end();
@@ -43054,7 +43363,7 @@ async function runKnowledgeCompose(req, ai2, physicRoot) {
43054
43363
 
43055
43364
  // src/core/faq-compose.ts
43056
43365
  import { spawn as spawn9, execFile as execFile10 } from "child_process";
43057
- 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";
43058
43367
  import { randomUUID as randomUUID2 } from "crypto";
43059
43368
  import { join as join24 } from "path";
43060
43369
  import { tmpdir as tmpdir4 } from "os";
@@ -43124,7 +43433,7 @@ function parsePerTicket(text) {
43124
43433
  return [];
43125
43434
  }
43126
43435
  function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
43127
- return new Promise((resolve11, reject) => {
43436
+ return new Promise((resolve12, reject) => {
43128
43437
  const isClaude = cmd.includes("claude");
43129
43438
  const args = [
43130
43439
  "-p",
@@ -43157,7 +43466,7 @@ function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
43157
43466
  child.on("close", (code) => {
43158
43467
  clearTimeout(timer);
43159
43468
  out += parser.flush();
43160
- resolve11({ code: code ?? -1, out, err, usage: parser.usage() });
43469
+ resolve12({ code: code ?? -1, out, err, usage: parser.usage() });
43161
43470
  });
43162
43471
  child.stdin.write(prompt);
43163
43472
  child.stdin.end();
@@ -43181,7 +43490,7 @@ async function runFaqCompose(req, ai2) {
43181
43490
  const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
43182
43491
  const auth = gitAuthArgs("x-access-token", req.repo.token);
43183
43492
  try {
43184
- mkdirSync7(workDir, { recursive: true });
43493
+ mkdirSync8(workDir, { recursive: true });
43185
43494
  await execFileAsync2("git", [...auth, "clone", "--depth", "1", "--branch", req.repo.branch, req.repo.url, workDir], { env: gitEnv });
43186
43495
  await execFileAsync2("git", ["-C", workDir, "config", "user.name", "4PM FAQ Bot"], { env: gitEnv });
43187
43496
  await execFileAsync2("git", ["-C", workDir, "config", "user.email", "faq-bot@4pm.app"], { env: gitEnv });
@@ -43221,9 +43530,9 @@ async function runFaqCompose(req, ai2) {
43221
43530
  logger.warn("faq.compose.failed", { error: String(err) });
43222
43531
  return { prUrl: "", error: String(err) };
43223
43532
  } finally {
43224
- if (existsSync14(workDir)) {
43533
+ if (existsSync15(workDir)) {
43225
43534
  try {
43226
- rmSync7(workDir, { recursive: true, force: true });
43535
+ rmSync8(workDir, { recursive: true, force: true });
43227
43536
  } catch {
43228
43537
  }
43229
43538
  }
@@ -43547,7 +43856,7 @@ async function writeSecrets(root, req) {
43547
43856
 
43548
43857
  // src/core/agent-tools.ts
43549
43858
  import { readFile as readFile12, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
43550
- import { dirname as dirname8, join as join27 } from "path";
43859
+ import { dirname as dirname9, join as join27 } from "path";
43551
43860
  var SHARED_REL = ".claude/settings.json";
43552
43861
  var LOCAL_REL = ".claude/settings.local.json";
43553
43862
  var SECRETS_DENY = "Read(./project.secrets.json)";
@@ -43578,7 +43887,7 @@ async function writeAgentTools(root, scope, permissions) {
43578
43887
  const next = normalizePermissions(permissions);
43579
43888
  if (scope === "shared" && !next.deny.includes(SECRETS_DENY)) next.deny = [...next.deny, SECRETS_DENY];
43580
43889
  obj.permissions = next;
43581
- await mkdir9(dirname8(path), { recursive: true });
43890
+ await mkdir9(dirname9(path), { recursive: true });
43582
43891
  await writeFile10(path, JSON.stringify(obj, null, 2) + "\n", "utf8");
43583
43892
  return { ok: true };
43584
43893
  } catch (err) {
@@ -43589,7 +43898,7 @@ async function writeAgentTools(root, scope, permissions) {
43589
43898
  // src/core/packages.ts
43590
43899
  import { createHash as createHash4 } from "crypto";
43591
43900
  import { readFile as readFile13, writeFile as writeFile11, mkdir as mkdir10, readdir as readdir5, rm as rm6, stat as stat4 } from "fs/promises";
43592
- import { dirname as dirname9, join as join28, relative } from "path";
43901
+ import { dirname as dirname10, join as join28, relative } from "path";
43593
43902
  var AGENTS_REL2 = ".claude/agents";
43594
43903
  var SKILLS_REL2 = ".claude/skills";
43595
43904
  var MANIFEST_REL = ".claude/.4pm-packages.json";
@@ -43670,7 +43979,7 @@ async function readManifest(root) {
43670
43979
  }
43671
43980
  async function writeManifest(root, manifest) {
43672
43981
  const path = join28(root, MANIFEST_REL);
43673
- await mkdir10(dirname9(path), { recursive: true });
43982
+ await mkdir10(dirname10(path), { recursive: true });
43674
43983
  await writeFile11(path, JSON.stringify(manifest, null, 2), "utf8");
43675
43984
  }
43676
43985
  async function writeArtifactFiles(root, kind, slug, files) {
@@ -43696,7 +44005,7 @@ async function writeArtifactFiles(root, kind, slug, files) {
43696
44005
  for (const [rel, buf] of byPath) {
43697
44006
  if (rel.includes("..") || rel.startsWith("/")) continue;
43698
44007
  const dest = join28(base, rel);
43699
- await mkdir10(dirname9(dest), { recursive: true });
44008
+ await mkdir10(dirname10(dest), { recursive: true });
43700
44009
  await writeFile11(dest, buf);
43701
44010
  }
43702
44011
  }
@@ -43777,7 +44086,7 @@ async function removePackage(root, slug) {
43777
44086
 
43778
44087
  // src/core/graph.ts
43779
44088
  import { readFile as readFile14, readdir as readdir6 } from "fs/promises";
43780
- 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";
43781
44090
  var MAX_FILES = 2e3;
43782
44091
  var MAX_NODES = 3e3;
43783
44092
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out", "coverage", ".turbo"]);
@@ -43832,7 +44141,7 @@ async function buildDocs(root) {
43832
44141
  for (const m2 of text.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
43833
44142
  const href = (m2[1] ?? "").split("#")[0].trim();
43834
44143
  if (!href || !href.endsWith(".md") || href.startsWith("http")) continue;
43835
- const target = relative2(root, resolve10(dirname10(f2), href)).split("\\").join("/");
44144
+ const target = relative2(root, resolve11(dirname11(f2), href)).split("\\").join("/");
43836
44145
  if (ids.has(target) && target !== rel) {
43837
44146
  const key = `${rel}->${target}`;
43838
44147
  if (!seen.has(key)) {
@@ -43917,7 +44226,7 @@ async function buildGraph(root, mode) {
43917
44226
 
43918
44227
  // src/core/rag.ts
43919
44228
  import { readFile as readFile15, writeFile as writeFile12, statfs as statfs2, mkdir as mkdir11 } from "fs/promises";
43920
- import { existsSync as existsSync15 } from "fs";
44229
+ import { existsSync as existsSync16 } from "fs";
43921
44230
  import { execFile as execFile11, spawn as spawn10 } from "child_process";
43922
44231
  import { totalmem as totalmem2, cpus as cpus2 } from "os";
43923
44232
  import { join as join30 } from "path";
@@ -44010,7 +44319,7 @@ async function ragStatus(root) {
44010
44319
  const modelPresent = modelName.length > 0;
44011
44320
  return {
44012
44321
  installed: fastembed && sqliteVec && modelPresent,
44013
- installing: existsSync15(join30(root, RAG_REL, ".installing")),
44322
+ installing: existsSync16(join30(root, RAG_REL, ".installing")),
44014
44323
  python: { found: py.found, version: py.version },
44015
44324
  deps: { pip, fastembed, sqliteVec },
44016
44325
  model: { present: modelPresent, name: modelName },
@@ -44021,7 +44330,7 @@ async function ragStatus(root) {
44021
44330
  };
44022
44331
  }
44023
44332
  async function readIndexMeta(root) {
44024
- const indexing = existsSync15(join30(root, RAG_REL, ".indexing"));
44333
+ const indexing = existsSync16(join30(root, RAG_REL, ".indexing"));
44025
44334
  try {
44026
44335
  const m2 = JSON.parse(await readFile15(join30(root, RAG_REL, ".index.json"), "utf8"));
44027
44336
  return {
@@ -44096,8 +44405,8 @@ async function ensureEngine(root) {
44096
44405
  async function ragReindex(root) {
44097
44406
  const py = await detectPython();
44098
44407
  if (!py.found) return { ok: false, started: false, error: "python3 not found" };
44099
- if (!existsSync15(join30(root, RAG_REL, ".installed"))) return { ok: false, started: false, error: "RAG is not installed" };
44100
- 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 };
44101
44410
  await ensureEngine(root);
44102
44411
  const script = `
44103
44412
  set +e
@@ -44117,7 +44426,7 @@ rm -f "${RAG_REL}/.indexing"
44117
44426
  async function ragQuery(root, queryText, k2 = 8) {
44118
44427
  const py = await detectPython();
44119
44428
  if (!py.found) return { results: [], error: "python3 not found" };
44120
- 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" };
44121
44430
  await ensureEngine(root);
44122
44431
  try {
44123
44432
  const { stdout } = await run10(
@@ -44137,7 +44446,7 @@ async function ragInstall(root, model) {
44137
44446
  }
44138
44447
  const py = await detectPython();
44139
44448
  if (!py.found) return { ok: false, started: false, error: "python3 not found on the worker" };
44140
- 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 };
44141
44450
  try {
44142
44451
  await mkdir11(join30(root, RAG_REL), { recursive: true });
44143
44452
  } catch {
@@ -44166,7 +44475,7 @@ rm -f "${RAG_REL}/.installing"
44166
44475
  }
44167
44476
 
44168
44477
  // src/core/config-sync.ts
44169
- import { existsSync as existsSync16, readFileSync as readFileSync16 } from "fs";
44478
+ import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
44170
44479
  import { join as join31 } from "path";
44171
44480
  var SERVER_MANAGED_KEYS = [
44172
44481
  "physicPath",
@@ -44185,7 +44494,7 @@ var SERVER_MANAGED_KEYS = [
44185
44494
  ];
44186
44495
  function readConfigText(profileDir2) {
44187
44496
  const path = join31(profileDir2, "config.json");
44188
- if (existsSync16(path)) {
44497
+ if (existsSync17(path)) {
44189
44498
  try {
44190
44499
  return readFileSync16(path, "utf8");
44191
44500
  } catch {
@@ -44671,14 +44980,14 @@ var WsClient = class {
44671
44980
  */
44672
44981
  awaitConnected(timeoutMs) {
44673
44982
  if (this.sessionKey && this.bus.status === "connected") return Promise.resolve(true);
44674
- return new Promise((resolve11) => {
44983
+ return new Promise((resolve12) => {
44675
44984
  const waiter = () => {
44676
44985
  clearTimeout(timer);
44677
- resolve11(true);
44986
+ resolve12(true);
44678
44987
  };
44679
44988
  const timer = setTimeout(() => {
44680
44989
  this.connectWaiters = this.connectWaiters.filter((w2) => w2 !== waiter);
44681
- resolve11(false);
44990
+ resolve12(false);
44682
44991
  }, timeoutMs);
44683
44992
  this.connectWaiters.push(waiter);
44684
44993
  });
@@ -44705,16 +45014,16 @@ var WsClient = class {
44705
45014
  this.forceReconnect = false;
44706
45015
  return Promise.resolve();
44707
45016
  }
44708
- return new Promise((resolve11) => {
45017
+ return new Promise((resolve12) => {
44709
45018
  const timer = setTimeout(() => {
44710
45019
  this.wakeReconnect = null;
44711
- resolve11();
45020
+ resolve12();
44712
45021
  }, ms2);
44713
45022
  this.wakeReconnect = () => {
44714
45023
  clearTimeout(timer);
44715
45024
  this.wakeReconnect = null;
44716
45025
  this.forceReconnect = false;
44717
- resolve11();
45026
+ resolve12();
44718
45027
  };
44719
45028
  });
44720
45029
  }
@@ -44768,7 +45077,7 @@ var WsClient = class {
44768
45077
  * One WS session: token → connect → handshake → handle messages until close.
44769
45078
  */
44770
45079
  connectOnce() {
44771
- return new Promise((resolve11, reject) => {
45080
+ return new Promise((resolve12, reject) => {
44772
45081
  void (async () => {
44773
45082
  this.sessionEstablished = false;
44774
45083
  this.bus.setStatus("connecting");
@@ -44810,7 +45119,12 @@ var WsClient = class {
44810
45119
  if (token.aiMemory && !this.aiMemory) this.aiMemory = token.aiMemory.text;
44811
45120
  this.outboundReviewEnabled = token.outboundReview?.enabled === true;
44812
45121
  this.restrictToFolder = token.aiRestrictToFolder === true;
44813
- 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
+ );
44814
45128
  this.machineUsername = token.machineUsername ?? "";
44815
45129
  this.servingRepos = token.repos ?? [];
44816
45130
  void this.ensureServingReposCloned();
@@ -44864,7 +45178,7 @@ var WsClient = class {
44864
45178
  if (code === CLOSE_DRAINING) {
44865
45179
  this.bus.log(t("error.serverDraining"));
44866
45180
  }
44867
- resolve11();
45181
+ resolve12();
44868
45182
  });
44869
45183
  socket.on("error", (err) => {
44870
45184
  this.clearHeartbeat();
@@ -44990,7 +45304,7 @@ var WsClient = class {
44990
45304
  * Used for quota.check before spawning an AI cli (ADR-0020).
44991
45305
  */
44992
45306
  request(channel, data) {
44993
- return new Promise((resolve11, reject) => {
45307
+ return new Promise((resolve12, reject) => {
44994
45308
  if (!this.socket || !this.sessionKey) {
44995
45309
  reject(new Error("Not connected to the server."));
44996
45310
  return;
@@ -45010,7 +45324,7 @@ var WsClient = class {
45010
45324
  }, 15e3);
45011
45325
  timer.unref?.();
45012
45326
  this.pending.set(envelope.id, {
45013
- resolve: (value) => resolve11(value),
45327
+ resolve: (value) => resolve12(value),
45014
45328
  timer
45015
45329
  });
45016
45330
  this.socket.send(JSON.stringify(envelope));
@@ -45064,8 +45378,8 @@ var WsClient = class {
45064
45378
  /** Create the physic folder at an absolute path if missing (best-effort). */
45065
45379
  ensurePhysicFolderPath(folder) {
45066
45380
  try {
45067
- if (!existsSync17(folder)) {
45068
- mkdirSync8(folder, { recursive: true });
45381
+ if (!existsSync18(folder)) {
45382
+ mkdirSync9(folder, { recursive: true });
45069
45383
  this.bus.log(t("project.createdFolder", { folder }));
45070
45384
  }
45071
45385
  } catch (err) {
@@ -45094,13 +45408,16 @@ var WsClient = class {
45094
45408
  projects: [],
45095
45409
  version: CLI_VERSION,
45096
45410
  physicPath,
45097
- physicPathExists: physicPath ? existsSync17(physicPath) : void 0,
45411
+ physicPathExists: physicPath ? existsSync18(physicPath) : void 0,
45098
45412
  // Real coarse state (ADR-0152): !paused and a recent tick (was hard-coded false).
45099
45413
  autonomousRunning: physicPath ? isAutonomousRunning(physicPath, this.context.profileDir) : false,
45100
45414
  // The machine-user AI-run limit (0 = unlimited) + profile count so a dispatch can resolve the
45101
45415
  // effective timeout and derive the SSE reply windows server-side (ADR-0256).
45102
45416
  aiRunTimeoutSec: config.aiRunTimeoutSec ?? 0,
45103
- aiProfileCount
45417
+ aiProfileCount,
45418
+ // The configured AI accounts (emails when readable) — admin-only visibility of which AI
45419
+ // account a (rented) machine runs on + the shared-account check (ADR-0354).
45420
+ aiAccounts: aiAccountLabels(config)
45104
45421
  });
45105
45422
  }
45106
45423
  /** Heartbeat every 30s: ping + periodically re-report machine.status. */
@@ -45350,7 +45667,7 @@ var WsClient = class {
45350
45667
  };
45351
45668
 
45352
45669
  // src/core/instance-lock.ts
45353
- 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";
45354
45671
  import { join as join33 } from "path";
45355
45672
  function lockPath(profileDir2) {
45356
45673
  return join33(profileDir2, "start.lock");
@@ -45379,7 +45696,7 @@ function acquireInstanceLock(profileDir2) {
45379
45696
  function releaseInstanceLock(profileDir2) {
45380
45697
  try {
45381
45698
  const pid = Number.parseInt(readFileSync17(lockPath(profileDir2), "utf8").trim(), 10);
45382
- if (pid === process.pid) rmSync8(lockPath(profileDir2), { force: true });
45699
+ if (pid === process.pid) rmSync9(lockPath(profileDir2), { force: true });
45383
45700
  } catch {
45384
45701
  }
45385
45702
  }
@@ -45674,7 +45991,7 @@ var SessionBus = class {
45674
45991
 
45675
45992
  // src/core/control-server.ts
45676
45993
  import { createServer } from "net";
45677
- import { chmodSync as chmodSync4, rmSync as rmSync9 } from "fs";
45994
+ import { chmodSync as chmodSync5, rmSync as rmSync10 } from "fs";
45678
45995
  import { join as join35 } from "path";
45679
45996
 
45680
45997
  // src/core/control-protocol.ts
@@ -45704,7 +46021,7 @@ function createFrameParser() {
45704
46021
  }
45705
46022
 
45706
46023
  // src/core/control-token.ts
45707
- 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";
45708
46025
  import { randomBytes as randomBytes3, timingSafeEqual } from "crypto";
45709
46026
  import { join as join34 } from "path";
45710
46027
  function controlTokenPath(profileDir2) {
@@ -45715,7 +46032,7 @@ function writeControlToken(profileDir2) {
45715
46032
  const path = controlTokenPath(profileDir2);
45716
46033
  writeFileSync10(path, token, { mode: 384 });
45717
46034
  try {
45718
- chmodSync3(path, 384);
46035
+ chmodSync4(path, 384);
45719
46036
  } catch {
45720
46037
  }
45721
46038
  return token;
@@ -45739,7 +46056,7 @@ function tokenMatches(expected, given) {
45739
46056
  function startControlServer(bus, profileDir2, info) {
45740
46057
  const socketPath = join35(profileDir2, CONTROL_SOCKET_FILE);
45741
46058
  try {
45742
- rmSync9(socketPath, { force: true });
46059
+ rmSync10(socketPath, { force: true });
45743
46060
  } catch {
45744
46061
  }
45745
46062
  const clients = /* @__PURE__ */ new Set();
@@ -45792,8 +46109,10 @@ function startControlServer(bus, profileDir2, info) {
45792
46109
  if (!authed) {
45793
46110
  if (frame.t === "auth" && tokenMatches(token, frame.token)) {
45794
46111
  authed = true;
45795
- clients.add(sock);
45796
- sendSnapshot(sock);
46112
+ if (!frame.rpc) {
46113
+ clients.add(sock);
46114
+ sendSnapshot(sock);
46115
+ }
45797
46116
  } else {
45798
46117
  send(sock, { t: "authError" });
45799
46118
  sock.destroy();
@@ -45803,6 +46122,9 @@ function startControlServer(bus, profileDir2, info) {
45803
46122
  if (frame.t === "submit") bus.submitLocal(frame.input);
45804
46123
  else if (frame.t === "reconnect") bus.requestReconnect();
45805
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
+ }
45806
46128
  }
45807
46129
  });
45808
46130
  const drop = () => {
@@ -45815,7 +46137,7 @@ function startControlServer(bus, profileDir2, info) {
45815
46137
  });
45816
46138
  server.listen(socketPath, () => {
45817
46139
  try {
45818
- chmodSync4(socketPath, 384);
46140
+ chmodSync5(socketPath, 384);
45819
46141
  } catch {
45820
46142
  }
45821
46143
  });
@@ -45825,8 +46147,8 @@ function startControlServer(bus, profileDir2, info) {
45825
46147
  clients.clear();
45826
46148
  server.close();
45827
46149
  try {
45828
- rmSync9(socketPath, { force: true });
45829
- rmSync9(controlTokenPath(profileDir2), { force: true });
46150
+ rmSync10(socketPath, { force: true });
46151
+ rmSync10(controlTokenPath(profileDir2), { force: true });
45830
46152
  } catch {
45831
46153
  }
45832
46154
  };
@@ -50908,24 +51230,24 @@ var App = class extends import_react9.PureComponent {
50908
51230
  })
50909
51231
  }));
50910
51232
  };
50911
- findNextFocusable = (state) => {
50912
- const activeIndex = state.focusables.findIndex((focusable) => {
50913
- return focusable.id === state.activeFocusId;
51233
+ findNextFocusable = (state2) => {
51234
+ const activeIndex = state2.focusables.findIndex((focusable) => {
51235
+ return focusable.id === state2.activeFocusId;
50914
51236
  });
50915
- for (let index = activeIndex + 1; index < state.focusables.length; index++) {
50916
- const focusable = state.focusables[index];
51237
+ for (let index = activeIndex + 1; index < state2.focusables.length; index++) {
51238
+ const focusable = state2.focusables[index];
50917
51239
  if (focusable?.isActive) {
50918
51240
  return focusable.id;
50919
51241
  }
50920
51242
  }
50921
51243
  return void 0;
50922
51244
  };
50923
- findPreviousFocusable = (state) => {
50924
- const activeIndex = state.focusables.findIndex((focusable) => {
50925
- return focusable.id === state.activeFocusId;
51245
+ findPreviousFocusable = (state2) => {
51246
+ const activeIndex = state2.focusables.findIndex((focusable) => {
51247
+ return focusable.id === state2.activeFocusId;
50926
51248
  });
50927
51249
  for (let index = activeIndex - 1; index >= 0; index--) {
50928
- const focusable = state.focusables[index];
51250
+ const focusable = state2.focusables[index];
50929
51251
  if (focusable?.isActive) {
50930
51252
  return focusable.id;
50931
51253
  }
@@ -51121,8 +51443,8 @@ var Ink = class {
51121
51443
  }
51122
51444
  }
51123
51445
  async waitUntilExit() {
51124
- this.exitPromise ||= new Promise((resolve11, reject) => {
51125
- this.resolveExitPromise = resolve11;
51446
+ this.exitPromise ||= new Promise((resolve12, reject) => {
51447
+ this.resolveExitPromise = resolve12;
51126
51448
  this.rejectExitPromise = reject;
51127
51449
  });
51128
51450
  return this.exitPromise;
@@ -51489,7 +51811,7 @@ var import_react21 = __toESM(require_react(), 1);
51489
51811
  var import_react26 = __toESM(require_react(), 1);
51490
51812
 
51491
51813
  // src/core/input-history.ts
51492
- 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";
51493
51815
  import { join as join36 } from "path";
51494
51816
  var MAX_HISTORY = 200;
51495
51817
  function historyPath(profileDir2) {
@@ -51497,7 +51819,7 @@ function historyPath(profileDir2) {
51497
51819
  }
51498
51820
  function loadInputHistory(profileDir2) {
51499
51821
  const path = historyPath(profileDir2);
51500
- if (!existsSync19(path)) return [];
51822
+ if (!existsSync20(path)) return [];
51501
51823
  try {
51502
51824
  const data = JSON.parse(readFileSync20(path, "utf8"));
51503
51825
  return Array.isArray(data) ? data.filter((x) => typeof x === "string") : [];
@@ -52175,7 +52497,7 @@ async function runStart(profileDir2, profileName) {
52175
52497
  logger.error("ws.run.crash", { message: e.message, stack: e.stack });
52176
52498
  if (client.isStopped) return;
52177
52499
  bus.log(t("start.recovered", { message: e.message }), "error");
52178
- await new Promise((resolve11) => setTimeout(resolve11, 1e3));
52500
+ await new Promise((resolve12) => setTimeout(resolve12, 1e3));
52179
52501
  }
52180
52502
  }
52181
52503
  };
@@ -52210,6 +52532,7 @@ async function runStart(profileDir2, profileName) {
52210
52532
  }
52211
52533
  } finally {
52212
52534
  stopIdleClear?.();
52535
+ await revokeAll().catch(() => void 0);
52213
52536
  stopControl?.();
52214
52537
  release();
52215
52538
  }
@@ -52221,7 +52544,7 @@ import { join as join38 } from "path";
52221
52544
  // src/core/control-client.ts
52222
52545
  import { connect as connect2 } from "net";
52223
52546
  function connectControl(socketPath, bus, token) {
52224
- return new Promise((resolve11, reject) => {
52547
+ return new Promise((resolve12, reject) => {
52225
52548
  const socket = connect2(socketPath);
52226
52549
  socket.setEncoding("utf8");
52227
52550
  let ready = false;
@@ -52274,7 +52597,7 @@ function connectControl(socketPath, bus, token) {
52274
52597
  bus.setTokens(frame.tokens);
52275
52598
  for (const entry of frame.transcript) bus.pushEntry(entry);
52276
52599
  ready = true;
52277
- resolve11({ info: frame.info, close });
52600
+ resolve12({ info: frame.info, close });
52278
52601
  break;
52279
52602
  case "transcript":
52280
52603
  bus.pushEntry(frame.entry);
@@ -52362,7 +52685,7 @@ async function runAutoRun(profileDir2, profileName) {
52362
52685
  initI18n(readProfileConfig(profileDir2).locale);
52363
52686
  const socketPath = join39(profileDir2, CONTROL_SOCKET_FILE);
52364
52687
  const token = readControlToken(profileDir2);
52365
- const code = await new Promise((resolve11) => {
52688
+ const code = await new Promise((resolve12) => {
52366
52689
  let settled = false;
52367
52690
  const done = (c) => {
52368
52691
  if (settled) return;
@@ -52372,7 +52695,7 @@ async function runAutoRun(profileDir2, profileName) {
52372
52695
  socket.destroy();
52373
52696
  } catch {
52374
52697
  }
52375
- resolve11(c);
52698
+ resolve12(c);
52376
52699
  };
52377
52700
  const socket = connect3(socketPath);
52378
52701
  socket.setEncoding("utf8");
@@ -52415,21 +52738,91 @@ async function runAutoRun(profileDir2, profileName) {
52415
52738
  process.exitCode = code;
52416
52739
  }
52417
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
+
52418
52811
  // src/commands/ai-login.ts
52419
52812
  import { spawn as spawn12 } from "child_process";
52420
- import { mkdirSync as mkdirSync9 } from "fs";
52813
+ import { mkdirSync as mkdirSync10 } from "fs";
52421
52814
  import { basename as basename8 } from "path";
52422
52815
  function runProviderLogin(cmd, envVar, dir2) {
52423
- return new Promise((resolve11) => {
52816
+ return new Promise((resolve12) => {
52424
52817
  const child = spawn12(cmd, [], {
52425
52818
  stdio: "inherit",
52426
52819
  env: { ...process.env, [envVar]: dir2 }
52427
52820
  });
52428
52821
  child.on("error", (err) => {
52429
52822
  console.error(t("aiLogin.launchFailed", { cmd, message: err.message }));
52430
- resolve11(1);
52823
+ resolve12(1);
52431
52824
  });
52432
- child.on("exit", (code) => resolve11(code ?? 0));
52825
+ child.on("exit", (code) => resolve12(code ?? 0));
52433
52826
  });
52434
52827
  }
52435
52828
  async function runAiLogin(profileDir2, profileName, aiFilter) {
@@ -52454,7 +52847,7 @@ async function runAiLogin(profileDir2, profileName, aiFilter) {
52454
52847
  continue;
52455
52848
  }
52456
52849
  const dir2 = resolveHomePath(cred.profile);
52457
- mkdirSync9(dir2, { recursive: true });
52850
+ mkdirSync10(dir2, { recursive: true });
52458
52851
  console.log(t("aiLogin.loggingIn", { provider: cred.provider, profile: cred.profile, envVar, dir: dir2 }));
52459
52852
  console.log(t("aiLogin.completeSignIn"));
52460
52853
  const code = await runProviderLogin(cred.provider, envVar, dir2);
@@ -52466,7 +52859,7 @@ async function runAiLogin(profileDir2, profileName, aiFilter) {
52466
52859
  }
52467
52860
 
52468
52861
  // src/commands/unlink.ts
52469
- import { rmSync as rmSync10 } from "fs";
52862
+ import { rmSync as rmSync11 } from "fs";
52470
52863
  async function runUnlink(profileDir2, profileName) {
52471
52864
  initI18n(readProfileConfig(profileDir2).locale);
52472
52865
  const cred = readCredential(profileDir2, profileName);
@@ -52480,7 +52873,7 @@ async function runUnlink(profileDir2, profileName) {
52480
52873
  } catch (err) {
52481
52874
  console.log(t("unlink.revokeFailed", { message: err.message }));
52482
52875
  }
52483
- rmSync10(profileDir2, { recursive: true, force: true });
52876
+ rmSync11(profileDir2, { recursive: true, force: true });
52484
52877
  clearDefaultProfileIf(profileName);
52485
52878
  console.log(t("unlink.deleted"));
52486
52879
  }
@@ -52569,10 +52962,10 @@ function Picker({
52569
52962
  ] });
52570
52963
  }
52571
52964
  function selectFromList(items, title) {
52572
- return new Promise((resolve11) => {
52965
+ return new Promise((resolve12) => {
52573
52966
  let chosen = null;
52574
52967
  const app = render_default(/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Picker, { items, title, onDone: (v2) => chosen = v2 }));
52575
- void app.waitUntilExit().then(() => resolve11(chosen));
52968
+ void app.waitUntilExit().then(() => resolve12(chosen));
52576
52969
  });
52577
52970
  }
52578
52971
 
@@ -52639,6 +53032,14 @@ async function main() {
52639
53032
  await runAttach(profileDir(name), name);
52640
53033
  break;
52641
53034
  }
53035
+ case "git-credential": {
53036
+ await runGitCredential(rest[0]);
53037
+ break;
53038
+ }
53039
+ case "git-token": {
53040
+ await runGitToken();
53041
+ break;
53042
+ }
52642
53043
  case "auto-run": {
52643
53044
  const name = await pickLinkedProfile(explicitProfile, "auto-run");
52644
53045
  if (!name) {