@brainervirus/workit-cli 8.40.7 → 8.41.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 +357 -106
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -6537,6 +6537,15 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
6537
6537
  return cached;
6538
6538
  let dir = path2.resolve(cwd);
6539
6539
  while (!existsSync2(path2.join(dir, ".git"))) {
6540
+ if (["HEAD", "objects", "refs"].every((name) => existsSync2(path2.join(dir, name)))) {
6541
+ const real = realpathOf(dir);
6542
+ const owner = ownInternals(real) === true ? gitDirOwner(real) : null;
6543
+ if (owner === null)
6544
+ return Error(`${cwd} is in a repository with no work tree at ${real}: no grants apply`);
6545
+ const probe = { main: owner, superproject: null };
6546
+ probes.set(cwd, probe);
6547
+ return probe;
6548
+ }
6540
6549
  const up = path2.dirname(dir);
6541
6550
  if (up === dir)
6542
6551
  return null;
@@ -6575,6 +6584,14 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
6575
6584
  if (owner !== realpathOf(dir))
6576
6585
  main = owner;
6577
6586
  }
6587
+ const lender = ownInternals(real);
6588
+ if (lender === false)
6589
+ return Error(`${cwd} borrows git internals from outside ${real} that no checkout owns`);
6590
+ if (lender !== true && lender !== realpathOf(dir)) {
6591
+ if (main !== null && main !== lender)
6592
+ return Error(`${cwd} mixes the repositories of ${main} and ${lender}`);
6593
+ main = lender;
6594
+ }
6578
6595
  } else {
6579
6596
  const list = git(["worktree", "list", "--porcelain"]);
6580
6597
  if (list === null)
@@ -6590,6 +6607,30 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
6590
6607
  } catch {
6591
6608
  return false;
6592
6609
  }
6610
+ }, INTERNALS, ownInternals = (real) => {
6611
+ let lender = null;
6612
+ for (const name of INTERNALS) {
6613
+ const at = path2.join(real, name);
6614
+ try {
6615
+ lstatSync(at);
6616
+ } catch {
6617
+ continue;
6618
+ }
6619
+ let resolved;
6620
+ try {
6621
+ resolved = (realpathSync.native ?? realpathSync)(at);
6622
+ } catch {
6623
+ return false;
6624
+ }
6625
+ const rel = path2.relative(real, resolved);
6626
+ if (rel && !rel.startsWith("..") && !path2.isAbsolute(rel))
6627
+ continue;
6628
+ const owner = path2.basename(resolved) === name ? gitDirOwner(path2.dirname(resolved)) : null;
6629
+ if (owner === null || lender !== null && lender !== owner)
6630
+ return false;
6631
+ lender = owner;
6632
+ }
6633
+ return lender ?? true;
6593
6634
  }, gitDirOwner = (real) => {
6594
6635
  const parts = real.split(/[\\/]/u);
6595
6636
  const at = parts.lastIndexOf(".git");
@@ -7058,6 +7099,7 @@ var init_workspaces = __esm(() => {
7058
7099
  name = "WorkspaceConfigError";
7059
7100
  };
7060
7101
  probes = new Map;
7102
+ INTERNALS = ["refs", "objects", "config", "packed-refs", "commondir"];
7061
7103
  runtimeWorkspaceIndexSchema = object({
7062
7104
  workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
7063
7105
  }).passthrough();
@@ -9375,7 +9417,7 @@ var package_default;
9375
9417
  var init_package = __esm(() => {
9376
9418
  package_default = {
9377
9419
  name: "@brainervirus/workit-cli",
9378
- version: "8.40.7",
9420
+ version: "8.41.0",
9379
9421
  private: false,
9380
9422
  description: "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
9381
9423
  keywords: [
@@ -9413,13 +9455,13 @@ var init_package = __esm(() => {
9413
9455
  },
9414
9456
  dependencies: {
9415
9457
  "@babel/parser": "7.29.9",
9416
- "@brainervirus/workit-cursor": "^8.40.7",
9458
+ "@brainervirus/workit-cursor": "^8.41.0",
9417
9459
  "@inkjs/ui": "2.0.0",
9418
9460
  ink: "7.1.1",
9419
9461
  react: "19.2.8"
9420
9462
  },
9421
9463
  devDependencies: {
9422
- "@brainervirus/workit-core": "^8.40.7"
9464
+ "@brainervirus/workit-core": "^8.41.0"
9423
9465
  },
9424
9466
  engines: {
9425
9467
  node: ">=24"
@@ -75176,7 +75218,124 @@ var init_setup2 = __esm(() => {
75176
75218
  ALL_HOSTS = ["claude-code", "codex", "cursor", "opencode", "pi"];
75177
75219
  });
75178
75220
 
75221
+ // packages/workit-cli/src/progress.ts
75222
+ function createProgress(options = {}) {
75223
+ const env = options.env ?? process.env;
75224
+ const mode = modeFor(options, env);
75225
+ const stream = options.stream;
75226
+ const now = options.now ?? Date.now;
75227
+ const every = options.setInterval ?? ((tick, ms) => setInterval(tick, ms));
75228
+ const stop = options.clearInterval ?? ((timer) => clearInterval(timer));
75229
+ const proc = options.proc ?? process;
75230
+ const ascii = env.TERM === "dumb";
75231
+ const glyphs = mode === "spinner" ? { done: "\x1B[32m✓\x1B[0m", failed: "\x1B[31m✗\x1B[0m", skipped: "–" } : ascii ? { done: "ok", failed: "x", skipped: "-" } : { done: "✓", failed: "✗", skipped: "–" };
75232
+ const start = (initial) => {
75233
+ if (mode === "off" || !stream)
75234
+ return NOOP_STEP;
75235
+ let text = initial;
75236
+ let finished = false;
75237
+ const began = now();
75238
+ let frame = 0;
75239
+ const draw = () => {
75240
+ const seconds = Math.floor((now() - began) / 1000);
75241
+ let line = `${FRAMES[frame % FRAMES.length]} ${text} (${seconds}s)`;
75242
+ if (stream.columns && line.length >= stream.columns)
75243
+ line = `${line.slice(0, Math.max(1, stream.columns - 2))}…`;
75244
+ stream.write(`${CLEAR}${line}`);
75245
+ };
75246
+ let timer;
75247
+ if (mode === "spinner") {
75248
+ draw();
75249
+ timer = every(() => {
75250
+ frame += 1;
75251
+ draw();
75252
+ }, FRAME_MS);
75253
+ }
75254
+ const handlers = [];
75255
+ const release = () => {
75256
+ for (const [event, handler] of handlers)
75257
+ proc.off(event, handler);
75258
+ handlers.length = 0;
75259
+ };
75260
+ if (mode === "spinner") {
75261
+ const onSignal = (signal) => () => {
75262
+ if (!finished) {
75263
+ finished = true;
75264
+ if (timer !== undefined)
75265
+ stop(timer);
75266
+ stream.write(CLEAR);
75267
+ }
75268
+ release();
75269
+ proc.kill(proc.pid, signal);
75270
+ };
75271
+ const onExit = () => {
75272
+ if (!finished)
75273
+ stream.write(CLEAR);
75274
+ };
75275
+ handlers.push(["SIGINT", onSignal("SIGINT")], ["SIGTERM", onSignal("SIGTERM")]);
75276
+ handlers.push(["exit", onExit]);
75277
+ for (const [event, handler] of handlers)
75278
+ proc.on(event, handler);
75279
+ }
75280
+ return {
75281
+ label: (next) => {
75282
+ text = next;
75283
+ if (mode === "spinner" && !finished)
75284
+ draw();
75285
+ },
75286
+ end: (status, note) => {
75287
+ if (finished)
75288
+ return;
75289
+ finished = true;
75290
+ if (timer !== undefined)
75291
+ stop(timer);
75292
+ release();
75293
+ stream.write(`${mode === "spinner" ? CLEAR : ""}${glyphs[status]} ${text}${note ? `: ${note}` : ""}
75294
+ `);
75295
+ }
75296
+ };
75297
+ };
75298
+ return {
75299
+ mode,
75300
+ start,
75301
+ async run(label, fn, classify) {
75302
+ const step = start(label);
75303
+ try {
75304
+ const value = await fn();
75305
+ step.end(classify ? classify(value) : "done");
75306
+ return value;
75307
+ } catch (error) {
75308
+ step.end("failed");
75309
+ throw error;
75310
+ }
75311
+ },
75312
+ runSync(label, fn, classify) {
75313
+ const step = start(label);
75314
+ try {
75315
+ const value = fn();
75316
+ step.end(classify ? classify(value) : "done");
75317
+ return value;
75318
+ } catch (error) {
75319
+ step.end("failed");
75320
+ throw error;
75321
+ }
75322
+ }
75323
+ };
75324
+ }
75325
+ var FRAMES, FRAME_MS = 120, CLEAR = "\r\x1B[2K", truthy = (value) => value !== undefined && value !== "" && value !== "0" && value.toLowerCase() !== "false", modeFor = (options, env) => {
75326
+ if (options.json || !options.stream?.isTTY || truthy(env.CI))
75327
+ return "off";
75328
+ if (truthy(env.NO_COLOR) || env.TERM === "dumb")
75329
+ return "plain";
75330
+ return "spinner";
75331
+ }, NOOP_STEP;
75332
+ var init_progress = __esm(() => {
75333
+ FRAMES = ["·", "✢", "✳", "∗", "✻", "✽"];
75334
+ NOOP_STEP = { label: () => {}, end: () => {} };
75335
+ });
75336
+
75179
75337
  // packages/workit-cli/src/upgrade/shared.ts
75338
+ import { spawn } from "node:child_process";
75180
75339
  import { existsSync as existsSync21, readFileSync as readFileSync22 } from "node:fs";
75181
75340
  import path44 from "node:path";
75182
75341
  import os16 from "node:os";
@@ -75237,7 +75396,61 @@ var LAUNCH_HOSTS, HOSTS2, LIVE_HOSTS, OPENCODE_PACKAGE = "@brainervirus/workit-o
75237
75396
  }
75238
75397
  const result = runHostCommand(step, { home, env });
75239
75398
  return { status: result.exitCode, stdout: result.stdout };
75240
- }), errorText = (error) => error instanceof Error ? error.message : "installed source could not be read", piInstalledVersion = (listing, selector) => {
75399
+ }), COMMAND_TIMEOUT_MS = 60000, asyncRunner = (deps) => {
75400
+ if (deps.runAsync)
75401
+ return deps.runAsync;
75402
+ const injected = deps.run;
75403
+ if (injected)
75404
+ return async (command, args) => injected(command, args);
75405
+ return (command, args) => {
75406
+ const { home, env } = upgradePaths(deps);
75407
+ let step;
75408
+ try {
75409
+ step = hostCommand(command, args, { home, env });
75410
+ } catch {
75411
+ return Promise.resolve({ status: 1, stdout: "" });
75412
+ }
75413
+ if (process.platform === "win32" && /\.(?:cmd|bat)$/i.test(step.command))
75414
+ return Promise.resolve({ status: 1, stdout: "" });
75415
+ const childEnv = { ...process.env, ...env, HOME: home };
75416
+ childEnv.CODEX_HOME = env.CODEX_HOME ?? path44.join(home, ".codex");
75417
+ childEnv.PI_CODING_AGENT_DIR = env.PI_CODING_AGENT_DIR ?? path44.join(home, ".pi", "agent");
75418
+ childEnv.CLAUDE_CONFIG_DIR = env.CLAUDE_CONFIG_DIR ?? path44.join(home, ".claude");
75419
+ childEnv.XDG_CONFIG_HOME = env.XDG_CONFIG_HOME ?? path44.join(home, ".config");
75420
+ return new Promise((resolve) => {
75421
+ let stdout = "";
75422
+ let settled = false;
75423
+ const finish = (status) => {
75424
+ if (settled)
75425
+ return;
75426
+ settled = true;
75427
+ clearTimeout(timer);
75428
+ resolve({ status, stdout });
75429
+ };
75430
+ let child;
75431
+ try {
75432
+ child = spawn(step.command, step.args, {
75433
+ cwd: step.cwd,
75434
+ env: childEnv,
75435
+ stdio: ["ignore", "pipe", "ignore"],
75436
+ windowsHide: true,
75437
+ shell: false
75438
+ });
75439
+ } catch {
75440
+ resolve({ status: 1, stdout: "" });
75441
+ return;
75442
+ }
75443
+ const timer = setTimeout(() => {
75444
+ child.kill("SIGKILL");
75445
+ finish(1);
75446
+ }, COMMAND_TIMEOUT_MS);
75447
+ child.stdout?.setEncoding("utf8");
75448
+ child.stdout?.on("data", (chunk) => stdout += chunk);
75449
+ child.on("error", () => finish(1));
75450
+ child.on("close", (code) => finish(code ?? 1));
75451
+ });
75452
+ };
75453
+ }, errorText = (error) => error instanceof Error ? error.message : "installed source could not be read", piInstalledVersion = (listing, selector) => {
75241
75454
  const lines = listing.split(/\r?\n/).map((line) => line.trim());
75242
75455
  const index = lines.indexOf(selector);
75243
75456
  if (index < 0 || !lines[index + 1] || !path44.isAbsolute(lines[index + 1]))
@@ -75326,7 +75539,7 @@ var init_processes = __esm(() => {
75326
75539
  import { existsSync as existsSync22, readdirSync as readdirSync12, mkdirSync as mkdirSync11, copyFileSync as copyFileSync6, chmodSync as chmodSync2, cpSync as cpSync5 } from "node:fs";
75327
75540
  import path46 from "node:path";
75328
75541
  import { createHash as createHash16 } from "node:crypto";
75329
- function applyUpgrade(plan, deps = {}) {
75542
+ async function applyUpgrade(plan, deps = {}) {
75330
75543
  const hosts = [...plan.failed];
75331
75544
  const done = (backup) => {
75332
75545
  const failures = hosts.filter((entry) => entry.status === "failed");
@@ -75381,6 +75594,9 @@ function applyUpgrade(plan, deps = {}) {
75381
75594
  `, 384);
75382
75595
  }
75383
75596
  const run = runner(deps);
75597
+ const runAsync = asyncRunner(deps);
75598
+ const progress = deps.progress ?? createProgress({ json: true });
75599
+ const statusOf = (outcome) => outcome?.status === "failed" ? "failed" : outcome?.status === "updated" ? "done" : "skipped";
75384
75600
  let cursorBackupError = null;
75385
75601
  if (plan.entries.some((entry) => entry.host === "cursor") && existsSync22(paths.cursorRoot))
75386
75602
  try {
@@ -75393,96 +75609,104 @@ function applyUpgrade(plan, deps = {}) {
75393
75609
  const listed = plan.entries.length ? listProcesses(deps) : [];
75394
75610
  const running = (listed ?? []).flatMap((entry) => classifyProcess(entry) ?? []);
75395
75611
  for (const entry of plan.entries) {
75396
- if (entry.host === "cursor" && cursorBackupError) {
75397
- hosts.push({
75398
- host: "cursor",
75399
- status: "failed",
75400
- reason: cursorBackupError,
75401
- fix: entry.fix
75402
- });
75403
- continue;
75404
- }
75405
- if (!LIVE_HOSTS.has(entry.host)) {
75406
- if (listed === null) {
75612
+ const before = hosts.length;
75613
+ const step = progress.start(`Updating ${entry.host}`);
75614
+ try {
75615
+ if (entry.host === "cursor" && cursorBackupError) {
75407
75616
  hosts.push({
75408
- host: entry.host,
75409
- status: "skipped",
75410
- reason: `running processes could not be listed; close ${entry.host} and rerun`,
75411
- fix: `workit upgrade --hosts=${entry.host}`
75617
+ host: "cursor",
75618
+ status: "failed",
75619
+ reason: cursorBackupError,
75620
+ fix: entry.fix
75412
75621
  });
75413
75622
  continue;
75414
75623
  }
75415
- const sessions = running.filter((proc) => proc.host === entry.host && proc.kind === "session");
75416
- if (sessions.length) {
75624
+ if (!LIVE_HOSTS.has(entry.host)) {
75625
+ if (listed === null) {
75626
+ hosts.push({
75627
+ host: entry.host,
75628
+ status: "skipped",
75629
+ reason: `running processes could not be listed; close ${entry.host} and rerun`,
75630
+ fix: `workit upgrade --hosts=${entry.host}`
75631
+ });
75632
+ continue;
75633
+ }
75634
+ const sessions = running.filter((proc) => proc.host === entry.host && proc.kind === "session");
75635
+ if (sessions.length) {
75636
+ hosts.push({
75637
+ host: entry.host,
75638
+ status: "skipped",
75639
+ reason: `close ${entry.host} (${sessions.map(describe2).join("; ")}), then rerun`,
75640
+ fix: `workit upgrade --hosts=${entry.host}`
75641
+ });
75642
+ continue;
75643
+ }
75644
+ }
75645
+ const daemons = running.filter((proc) => proc.host === "codex" && proc.kind === "daemon");
75646
+ const stopDaemon = entry.host === "codex" && daemons.length > 0;
75647
+ const stopped = stopDaemon ? await progress.run("Stopping the Codex app-server daemon", () => runAsync("codex", ["app-server", "daemon", "stop"]), (result) => result.status === 0 ? "done" : "failed") : null;
75648
+ if (stopped && stopped.status !== 0) {
75417
75649
  hosts.push({
75418
- host: entry.host,
75419
- status: "skipped",
75420
- reason: `close ${entry.host} (${sessions.map(describe2).join("; ")}), then rerun`,
75421
- fix: `workit upgrade --hosts=${entry.host}`
75650
+ host: "codex",
75651
+ status: "failed",
75652
+ reason: `could not stop the Codex app-server daemon (${daemons.map(describe2).join("; ")})`,
75653
+ fix: "codex app-server daemon stop && workit upgrade --hosts=codex"
75422
75654
  });
75423
75655
  continue;
75424
75656
  }
75425
- }
75426
- const daemons = running.filter((proc) => proc.host === "codex" && proc.kind === "daemon");
75427
- const stopDaemon = entry.host === "codex" && daemons.length > 0;
75428
- if (stopDaemon && run("codex", ["app-server", "daemon", "stop"]).status !== 0) {
75429
- hosts.push({
75430
- host: "codex",
75431
- status: "failed",
75432
- reason: `could not stop the Codex app-server daemon (${daemons.map(describe2).join("; ")})`,
75433
- fix: "codex app-server daemon stop && workit upgrade --hosts=codex"
75434
- });
75435
- continue;
75436
- }
75437
- let outcome;
75438
- try {
75439
- outcome = applyEntry(entry, { run, paths, configDir });
75440
- if (entry.host === "codex" && outcome.status === "updated") {
75441
- const agents = refreshCodexAgents(run, paths.codexHome);
75442
- if (!agents.ok)
75657
+ let outcome;
75658
+ try {
75659
+ outcome = await applyEntry(entry, { run, runAsync, paths, configDir });
75660
+ if (entry.host === "codex" && outcome.status === "updated") {
75661
+ const agents = await refreshCodexAgents(runAsync, paths.codexHome);
75662
+ if (!agents.ok)
75663
+ outcome = {
75664
+ ...outcome,
75665
+ status: "failed",
75666
+ reason: `${outcome.reason}; ${agents.note}`,
75667
+ fix: agents.fix
75668
+ };
75669
+ else if (agents.note)
75670
+ outcome = { ...outcome, reason: `${outcome.reason}; ${agents.note}` };
75671
+ }
75672
+ } catch (error) {
75673
+ outcome = {
75674
+ host: entry.host,
75675
+ status: "failed",
75676
+ reason: errorText(error),
75677
+ fix: entry.fix
75678
+ };
75679
+ }
75680
+ if (stopDaemon) {
75681
+ const started = (await progress.run("Restarting the Codex app-server daemon", () => runAsync("codex", ["app-server", "daemon", "start"]), (result) => result.status === 0 ? "done" : "failed")).status === 0;
75682
+ outcome = started ? {
75683
+ ...outcome,
75684
+ reason: `${outcome.reason}; app-server daemon stopped and started again`
75685
+ } : {
75686
+ ...outcome,
75687
+ status: "failed",
75688
+ reason: `${outcome.reason}; the app-server daemon did not start again`,
75689
+ fix: "codex app-server daemon start"
75690
+ };
75691
+ }
75692
+ if (entry.host === "opencode") {
75693
+ const servers = running.filter((proc) => proc.host === "opencode" && proc.kind === "daemon");
75694
+ if (servers.length && outcome.status === "updated")
75443
75695
  outcome = {
75444
75696
  ...outcome,
75445
- status: "failed",
75446
- reason: `${outcome.reason}; ${agents.note}`,
75447
- fix: agents.fix
75697
+ reason: `${outcome.reason}; restart opencode serve (${servers.map(describe2).join("; ")}) to load it`
75448
75698
  };
75449
- else if (agents.note)
75450
- outcome = { ...outcome, reason: `${outcome.reason}; ${agents.note}` };
75451
75699
  }
75452
- } catch (error) {
75453
- outcome = {
75454
- host: entry.host,
75455
- status: "failed",
75456
- reason: errorText(error),
75457
- fix: entry.fix
75458
- };
75459
- }
75460
- if (stopDaemon) {
75461
- const started = run("codex", ["app-server", "daemon", "start"]).status === 0;
75462
- outcome = started ? {
75463
- ...outcome,
75464
- reason: `${outcome.reason}; app-server daemon stopped and started again`
75465
- } : {
75466
- ...outcome,
75467
- status: "failed",
75468
- reason: `${outcome.reason}; the app-server daemon did not start again`,
75469
- fix: "codex app-server daemon start"
75470
- };
75471
- }
75472
- if (entry.host === "opencode") {
75473
- const servers = running.filter((proc) => proc.host === "opencode" && proc.kind === "daemon");
75474
- if (servers.length && outcome.status === "updated")
75475
- outcome = {
75476
- ...outcome,
75477
- reason: `${outcome.reason}; restart opencode serve (${servers.map(describe2).join("; ")}) to load it`
75478
- };
75700
+ hosts.push(outcome);
75701
+ } finally {
75702
+ const added = hosts.length > before ? hosts[hosts.length - 1] : undefined;
75703
+ step.end(statusOf(added), added && added.status !== "updated" ? added.reason : undefined);
75479
75704
  }
75480
- hosts.push(outcome);
75481
75705
  }
75482
75706
  if (hosts.some((entry) => entry.host === "codex" && entry.status === "current")) {
75483
75707
  let agents;
75484
75708
  try {
75485
- agents = refreshCodexAgents(run, paths.codexHome);
75709
+ agents = await refreshCodexAgents(runAsync, paths.codexHome);
75486
75710
  } catch (error) {
75487
75711
  agents = {
75488
75712
  ok: false,
@@ -75506,7 +75730,7 @@ function applyUpgrade(plan, deps = {}) {
75506
75730
  }
75507
75731
  if (plan.cli)
75508
75732
  try {
75509
- hosts.push(applyCli(plan.cli, run, backup));
75733
+ hosts.push(await progress.run("Updating the CLI", () => applyCli(plan.cli, runAsync, backup), statusOf));
75510
75734
  } catch (error) {
75511
75735
  hosts.push({
75512
75736
  host: "cli",
@@ -75527,8 +75751,8 @@ var codexPluginRoot = (codexHome) => {
75527
75751
  }
75528
75752
  const newest = versions.reduce((best, version) => best && versionAtLeast(best, version) ? best : version, null);
75529
75753
  return newest ? path46.join(dir, newest) : null;
75530
- }, opencodeVersion = (run) => {
75531
- const listing = run("opencode", ["plugin", "list"]);
75754
+ }, opencodeVersion = async (run) => {
75755
+ const listing = await run("opencode", ["plugin", "list"]);
75532
75756
  if (listing.status !== 0)
75533
75757
  return;
75534
75758
  for (const line of listing.stdout.split(/\r?\n/)) {
@@ -75541,8 +75765,8 @@ var codexPluginRoot = (codexHome) => {
75541
75765
  return version;
75542
75766
  }
75543
75767
  return;
75544
- }, applyEntry = (entry, context) => {
75545
- const { run, paths, configDir } = context;
75768
+ }, applyEntry = async (entry, context) => {
75769
+ const { run, runAsync, paths, configDir } = context;
75546
75770
  const { home, env } = paths;
75547
75771
  const fail = (reason) => ({
75548
75772
  host: entry.host,
@@ -75594,12 +75818,12 @@ var codexPluginRoot = (codexHome) => {
75594
75818
  reason: entry.repair ? `reinstalled missing plugin files (${entry.latest})` : `${entry.installedVersion ?? "?"} -> ${entry.latest}`
75595
75819
  };
75596
75820
  }
75597
- const before = entry.host === "opencode" ? opencodeVersion(run) : undefined;
75821
+ const before = entry.host === "opencode" ? await opencodeVersion(runAsync) : undefined;
75598
75822
  for (const command of entry.commands)
75599
- if (run(command.command, command.args).status !== 0)
75823
+ if ((await runAsync(command.command, command.args)).status !== 0)
75600
75824
  return fail(`\`${command.command} ${command.args.join(" ")}\` failed`);
75601
75825
  if (entry.host === "opencode") {
75602
- const after = opencodeVersion(run);
75826
+ const after = await opencodeVersion(runAsync);
75603
75827
  if (before && after)
75604
75828
  return after === before ? {
75605
75829
  host: "opencode",
@@ -75628,7 +75852,7 @@ var codexPluginRoot = (codexHome) => {
75628
75852
  reason: `${entry.installedVersion ?? "?"} -> ${after ?? "?"}; new sessions load it`
75629
75853
  };
75630
75854
  }
75631
- const verify = entry.host === "codex" ? run("codex", ["plugin", "list", "--json"]) : run("pi", ["list"]);
75855
+ const verify = entry.host === "codex" ? await runAsync("codex", ["plugin", "list", "--json"]) : await runAsync("pi", ["list"]);
75632
75856
  if (verify.status !== 0)
75633
75857
  return fail("update verification failed");
75634
75858
  if (entry.host === "codex") {
@@ -75646,28 +75870,28 @@ var codexPluginRoot = (codexHome) => {
75646
75870
  status: "updated",
75647
75871
  reason: entry.repair ? `reinstalled missing plugin files (${entry.latest})` : `${entry.installedVersion ?? "?"} -> ${entry.latest}`
75648
75872
  };
75649
- }, refreshCodexAgents = (run, codexHome) => {
75873
+ }, refreshCodexAgents = async (run, codexHome) => {
75650
75874
  const root = codexPluginRoot(codexHome);
75651
75875
  const launcher = root && path46.join(root, "dist", "launch-mcp.js");
75652
75876
  if (!launcher || !existsSync22(launcher))
75653
75877
  return { ok: true, note: "" };
75654
75878
  const fix = `node ${JSON.stringify(launcher)} --install-agents`;
75655
- return run("node", [launcher, "--install-agents"]).status === 0 ? { ok: true, note: "agents refreshed" } : { ok: false, note: "agent refresh failed", fix };
75656
- }, applyCli = (cli, run, backup) => {
75879
+ return (await run("node", [launcher, "--install-agents"])).status === 0 ? { ok: true, note: "agents refreshed" } : { ok: false, note: "agent refresh failed", fix };
75880
+ }, applyCli = async (cli, run, backup) => {
75657
75881
  const fail = (reason) => ({
75658
75882
  host: "cli",
75659
75883
  status: "failed",
75660
75884
  reason,
75661
75885
  fix: CLI_FIX
75662
75886
  });
75663
- const currentRoot = run("npm", ["root", "--global"]);
75887
+ const currentRoot = await run("npm", ["root", "--global"]);
75664
75888
  if (currentRoot.status !== 0 || currentRoot.stdout.trim() !== cli.root)
75665
75889
  return fail("Global npm location changed; preview again");
75666
75890
  const cliBackup = path46.join(backup, "cli-package");
75667
75891
  cpSync5(path46.join(cli.root, "@brainervirus/workit-cli"), cliBackup, {
75668
75892
  recursive: true
75669
75893
  });
75670
- if (run("npm", ["install", "--global", `@brainervirus/workit-cli@${cli.latest}`]).status !== 0)
75894
+ if ((await run("npm", ["install", "--global", `@brainervirus/workit-cli@${cli.latest}`])).status !== 0)
75671
75895
  return fail(`CLI update failed; preserved package at ${cliBackup}; use npm to repair or run node ${path46.join(cliBackup, "dist/index.js")}`);
75672
75896
  const installed = objectOrNull(path46.join(cli.root, "@brainervirus/workit-cli/package.json"));
75673
75897
  if (installed?.name !== "@brainervirus/workit-cli" || installed.version !== cli.latest)
@@ -75683,6 +75907,7 @@ var init_apply = __esm(() => {
75683
75907
  init_setup_state();
75684
75908
  init_setup();
75685
75909
  init_safe_write();
75910
+ init_progress();
75686
75911
  init_processes();
75687
75912
  init_shared();
75688
75913
  });
@@ -76103,9 +76328,13 @@ async function runUpgradeCommand(argv, deps = {}) {
76103
76328
  const legacy = flags.has("--apply");
76104
76329
  const json = flags.has("--json") || legacy;
76105
76330
  const run = runner(deps);
76106
- const plan = previewUpgrade(raw, deps);
76107
- if (hostFlag === undefined || flags.has("--cli"))
76108
- planCli(plan, run);
76331
+ const progress = deps.progress ?? createProgress({ stream: process.stderr, json });
76332
+ const plan = progress.runSync("Planning the upgrade", () => {
76333
+ const planned = previewUpgrade(raw, deps);
76334
+ if (hostFlag === undefined || flags.has("--cli"))
76335
+ planCli(planned, run);
76336
+ return planned;
76337
+ });
76109
76338
  plan.ok = plan.blocked.length === 0 && plan.failed.length === 0;
76110
76339
  const { home, env } = upgradePaths(deps);
76111
76340
  const permissions = planHostPermissions({
@@ -76156,11 +76385,11 @@ Nothing changed.
76156
76385
  for (const warning of plan.warnings ?? [])
76157
76386
  out.write(`warning: ${warning}
76158
76387
  `);
76159
- const result = applyUpgrade(plan, deps);
76388
+ const result = await applyUpgrade(plan, { ...deps, progress });
76160
76389
  let permissionsOk = true;
76161
76390
  let written;
76162
76391
  if (permissions.changes.length && !permissions.refused && !result.error?.startsWith("configuration changed")) {
76163
- written = applyHostPermissions(permissions, { home, env });
76392
+ written = progress.runSync("Refreshing the permission block", () => applyHostPermissions(permissions, { home, env }), (entries) => entries.every((entry) => entry.status === "written") ? "done" : "failed");
76164
76393
  permissionsOk = written.every((entry) => entry.status === "written");
76165
76394
  for (const entry of written)
76166
76395
  result.hosts.push({
@@ -76172,7 +76401,7 @@ Nothing changed.
76172
76401
  }
76173
76402
  const failed = !result.ok || !permissionsOk;
76174
76403
  const cliVersion = result.hosts.some((h) => h.host === "cli" && h.status === "updated") ? plan.cli.latest : package_default.version;
76175
- const report = (deps.doctor ?? ((version) => runDoctor({ home, env, cliVersion: version, only: "upgrade" })))(cliVersion);
76404
+ const report = progress.runSync("Checking the result with workit doctor", () => (deps.doctor ?? ((version) => runDoctor({ home, env, cliVersion: version, only: "upgrade" })))(cliVersion));
76176
76405
  const left = report.checks.filter((check) => check.status === "warn" || check.status === "fail");
76177
76406
  if (json) {
76178
76407
  out.write(JSON.stringify({
@@ -76230,7 +76459,7 @@ async function runLaunchCommand(argv, deps = {}) {
76230
76459
  for (const skipped of plan.skipped)
76231
76460
  (deps.out ?? process.stderr).write(`${skipped.host}: ${skipped.reason}${skipped.fix ? ` (run: ${skipped.fix})` : ""}
76232
76461
  `);
76233
- const result = applyUpgrade(plan, deps);
76462
+ const result = await applyUpgrade(plan, deps);
76234
76463
  if (!result.ok) {
76235
76464
  (deps.out ?? process.stdout).write(`${result.error}
76236
76465
  `);
@@ -76259,6 +76488,7 @@ var init_upgrade = __esm(() => {
76259
76488
  init_host_install();
76260
76489
  init_doctor();
76261
76490
  init_host_permissions();
76491
+ init_progress();
76262
76492
  init_apply();
76263
76493
  init_plan();
76264
76494
  init_report();
@@ -76789,7 +77019,7 @@ var init_redact = __esm(() => {
76789
77019
  });
76790
77020
 
76791
77021
  // packages/workit-core/src/checks.ts
76792
- import { spawn, spawnSync as spawnSync19 } from "node:child_process";
77022
+ import { spawn as spawn2, spawnSync as spawnSync19 } from "node:child_process";
76793
77023
  import { createHash as createHash17 } from "node:crypto";
76794
77024
  import fs21 from "node:fs";
76795
77025
  import os18 from "node:os";
@@ -77018,7 +77248,7 @@ function runCheckCommand(argv, options) {
77018
77248
  shell: plan.shell
77019
77249
  };
77020
77250
  try {
77021
- child = spawn(plan.file, plan.args, spawnOptions);
77251
+ child = spawn2(plan.file, plan.args, spawnOptions);
77022
77252
  } catch (error) {
77023
77253
  finish(null, null, error.code ?? error.message);
77024
77254
  return;
@@ -79036,6 +79266,8 @@ async function prepareHeavy(io, settings, label, runCwd, timeoutMs) {
79036
79266
  wrapper: null,
79037
79267
  env: io.env
79038
79268
  };
79269
+ const progress = createProgress({ stream: process.stderr, json: io.json });
79270
+ let waiting = null;
79039
79271
  const acquired = await acquireLease({
79040
79272
  dir,
79041
79273
  slots: settings.slots,
@@ -79048,11 +79280,20 @@ async function prepareHeavy(io, settings, label, runCwd, timeoutMs) {
79048
79280
  onStatus: (status) => {
79049
79281
  const slots = `${settings.slots} slot${settings.slots === 1 ? "" : "s"}`;
79050
79282
  const holders = status.holders.map((holder) => describeHolder(holder)).join("; ");
79283
+ if (progress.mode === "spinner") {
79284
+ const text = `Waiting for the heavy-check lease: held by ${holders || "nobody; waiters ahead"}, queue position ${status.position}`;
79285
+ if (waiting)
79286
+ waiting.label(text);
79287
+ else
79288
+ waiting = progress.start(text);
79289
+ return;
79290
+ }
79051
79291
  io.stderr(status.first ? `workit: waiting for the heavy-check lease (${slots}): held by ${holders || "nobody; waiters ahead"}; queued at position ${status.position}, waiting up to ${formatWait(settings.waitMs)} (--wait <dur> sets it)
79052
79292
  ` : `workit: still queued for the heavy-check lease: position ${status.position}, ${formatWait(status.waitedMs)} of ${formatWait(settings.waitMs)} waited; held by ${holders || "nobody; waiters ahead"}
79053
79293
  `);
79054
79294
  }
79055
79295
  });
79296
+ waiting?.end(acquired.ok ? "done" : "failed");
79056
79297
  if (!acquired.ok && acquired.reason === "timeout") {
79057
79298
  const holders = acquired.holders.map((holder) => describeHolder(holder)).join("; ");
79058
79299
  const waited = formatWait(acquired.waitedMs);
@@ -79334,6 +79575,7 @@ var init_check = __esm(() => {
79334
79575
  init_package();
79335
79576
  init_ledger();
79336
79577
  init_output();
79578
+ init_progress();
79337
79579
  init_host_session();
79338
79580
  init_resources();
79339
79581
  MAX_UNVERIFIED_RUN_MS = 6 * 3600000;
@@ -87888,6 +88130,11 @@ async function wait2(argv, io) {
87888
88130
  let verdict = { state: "waiting", reason: "not_polled" };
87889
88131
  let baseContexts = null;
87890
88132
  let expectedMissing = [];
88133
+ const waiting = createProgress({ stream: process.stderr, json: io.json }).start("Waiting for CI");
88134
+ const abort = (...args) => {
88135
+ waiting.end("failed");
88136
+ return forgeFail(io, ...args);
88137
+ };
87891
88138
  for (;; ) {
87892
88139
  const read = resolved.forge.prStatus(number.data);
87893
88140
  polls += 1;
@@ -87897,7 +88144,7 @@ async function wait2(argv, io) {
87897
88144
  status = read.data;
87898
88145
  const doc = buildStatusDoc(io.cwd, resolved, status, { logLines: 0, behind: false });
87899
88146
  if (!doc.ok)
87900
- return forgeFail(io, doc);
88147
+ return abort(doc);
87901
88148
  recordPrStatus(io.cwd, doc.data, actor);
87902
88149
  if (status.requiredContexts === null && baseContexts === null) {
87903
88150
  const base = resolved.forge.branchChecks(status.base);
@@ -87908,12 +88155,14 @@ async function wait2(argv, io) {
87908
88155
  verdict = waitVerdict(doc.data, { head, elapsedMs, expectedMissing });
87909
88156
  if (verdict.state !== "waiting")
87910
88157
  break;
88158
+ const pending = doc.data.checks.pending;
88159
+ waiting.label(`Waiting for CI: ${pending.length} check${pending.length === 1 ? "" : "s"} pending${pending.length ? ` (${pending.slice(0, 3).join(", ")}${pending.length > 3 ? ", …" : ""})` : ""}`);
87911
88160
  } else {
87912
88161
  errors += 1;
87913
88162
  if (read.code !== "unavailable" && read.code !== "failed")
87914
- return forgeFail(io, read);
88163
+ return abort(read);
87915
88164
  if (errors >= MAX_POLL_ERRORS)
87916
- return forgeFail(io, read, { polls });
88165
+ return abort(read, { polls });
87917
88166
  }
87918
88167
  const remaining = deadline - forgeDeps.now();
87919
88168
  if (remaining <= 0)
@@ -87921,6 +88170,7 @@ async function wait2(argv, io) {
87921
88170
  await forgeDeps.sleep(Math.min(pollDelay(intervalMs, polls - 1), remaining));
87922
88171
  }
87923
88172
  const elapsedMs = forgeDeps.now() - started;
88173
+ waiting.end(verdict.state === "ready" ? "done" : verdict.state === "waiting" ? "skipped" : "failed");
87924
88174
  if (!status)
87925
88175
  return emit(io, fail2("unavailable", "no successful poll before the timeout", { data: { polls } }));
87926
88176
  const left = deadline - forgeDeps.now();
@@ -88087,6 +88337,7 @@ var init_ci = __esm(() => {
88087
88337
  init_ledger();
88088
88338
  init_autonomy();
88089
88339
  init_output();
88340
+ init_progress();
88090
88341
  init_forge_common();
88091
88342
  });
88092
88343
 
@@ -106974,7 +107225,7 @@ var init_audit = __esm(() => {
106974
107225
  });
106975
107226
 
106976
107227
  // packages/workit-cli/src/test-audit/mutate.ts
106977
- import { spawn as spawn2 } from "node:child_process";
107228
+ import { spawn as spawn3 } from "node:child_process";
106978
107229
  import {
106979
107230
  existsSync as existsSync29,
106980
107231
  lstatSync as lstatSync9,
@@ -107213,7 +107464,7 @@ function copyWorkingTree(repo) {
107213
107464
  function runCommand(command, cwd, timeoutMs, env) {
107214
107465
  const started = performance.now();
107215
107466
  return new Promise((resolve) => {
107216
- const child = spawn2(command, {
107467
+ const child = spawn3(command, {
107217
107468
  cwd,
107218
107469
  env,
107219
107470
  shell: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-cli",
3
- "version": "8.40.7",
3
+ "version": "8.41.0",
4
4
  "private": false,
5
5
  "description": "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
6
6
  "keywords": [
@@ -38,13 +38,13 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@babel/parser": "7.29.9",
41
- "@brainervirus/workit-cursor": "^8.40.7",
41
+ "@brainervirus/workit-cursor": "^8.41.0",
42
42
  "@inkjs/ui": "2.0.0",
43
43
  "ink": "7.1.1",
44
44
  "react": "19.2.8"
45
45
  },
46
46
  "devDependencies": {
47
- "@brainervirus/workit-core": "^8.40.7"
47
+ "@brainervirus/workit-core": "^8.41.0"
48
48
  },
49
49
  "engines": {
50
50
  "node": ">=24"