@usepowerplant/cli 0.4.21 → 0.4.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="9ee35b05-7453-5338-85ff-5dc1b6010221")}catch(e){}}();
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="91b4b5e6-fdd5-5127-9372-913ad2bc7af5")}catch(e){}}();
4
4
  const __cliNodeMajor = Number(process.versions.node.split('.')[0]);
5
5
  if (__cliNodeMajor < 20) {
6
6
  console.error(`powerplant needs Node 20 or newer — this is Node ${process.versions.node}.`);
@@ -56051,7 +56051,7 @@ function visit(schema, fnOrHandlers) {
56051
56051
  return h ? h(node3, rewritten) : node3;
56052
56052
  };
56053
56053
  const cache = /* @__PURE__ */ new Map();
56054
- function run(s) {
56054
+ function run2(s) {
56055
56055
  const cached3 = cache.get(s);
56056
56056
  if (cached3 === RESOLVING) {
56057
56057
  return new $ZodLazy({
@@ -56077,21 +56077,21 @@ function visit(schema, fnOrHandlers) {
56077
56077
  let changed = false;
56078
56078
  const newShape = {};
56079
56079
  for (const k2 of keys) {
56080
- const mapped = run(oldShape[k2]);
56080
+ const mapped = run2(oldShape[k2]);
56081
56081
  if (mapped !== oldShape[k2])
56082
56082
  changed = true;
56083
56083
  newShape[k2] = mapped;
56084
56084
  }
56085
56085
  let newCatchall = def.catchall;
56086
56086
  if (def.catchall) {
56087
- newCatchall = run(def.catchall);
56087
+ newCatchall = run2(def.catchall);
56088
56088
  if (newCatchall !== def.catchall)
56089
56089
  changed = true;
56090
56090
  }
56091
56091
  return changed ? clone(s, { ...def, shape: newShape, catchall: newCatchall }) : s;
56092
56092
  }
56093
56093
  case "array": {
56094
- const mapped = run(def.element);
56094
+ const mapped = run2(def.element);
56095
56095
  return mapped === def.element ? s : clone(s, { ...def, element: mapped });
56096
56096
  }
56097
56097
  case "tuple": {
@@ -56099,14 +56099,14 @@ function visit(schema, fnOrHandlers) {
56099
56099
  let changed = false;
56100
56100
  const newItems = [];
56101
56101
  for (const item of oldItems) {
56102
- const mapped = run(item);
56102
+ const mapped = run2(item);
56103
56103
  if (mapped !== item)
56104
56104
  changed = true;
56105
56105
  newItems.push(mapped);
56106
56106
  }
56107
56107
  let newRest = def.rest;
56108
56108
  if (def.rest) {
56109
- newRest = run(def.rest);
56109
+ newRest = run2(def.rest);
56110
56110
  if (newRest !== def.rest)
56111
56111
  changed = true;
56112
56112
  }
@@ -56114,12 +56114,12 @@ function visit(schema, fnOrHandlers) {
56114
56114
  }
56115
56115
  case "record":
56116
56116
  case "map": {
56117
- const newKey = run(def.keyType);
56118
- const newVal = run(def.valueType);
56117
+ const newKey = run2(def.keyType);
56118
+ const newVal = run2(def.valueType);
56119
56119
  return newKey === def.keyType && newVal === def.valueType ? s : clone(s, { ...def, keyType: newKey, valueType: newVal });
56120
56120
  }
56121
56121
  case "set": {
56122
- const newVal = run(def.valueType);
56122
+ const newVal = run2(def.valueType);
56123
56123
  return newVal === def.valueType ? s : clone(s, { ...def, valueType: newVal });
56124
56124
  }
56125
56125
  case "union": {
@@ -56127,7 +56127,7 @@ function visit(schema, fnOrHandlers) {
56127
56127
  let changed = false;
56128
56128
  const newOptions = [];
56129
56129
  for (const opt of oldOptions) {
56130
- const mapped = run(opt);
56130
+ const mapped = run2(opt);
56131
56131
  if (mapped !== opt)
56132
56132
  changed = true;
56133
56133
  newOptions.push(mapped);
@@ -56135,8 +56135,8 @@ function visit(schema, fnOrHandlers) {
56135
56135
  return changed ? clone(s, { ...def, options: newOptions }) : s;
56136
56136
  }
56137
56137
  case "intersection": {
56138
- const newLeft = run(def.left);
56139
- const newRight = run(def.right);
56138
+ const newLeft = run2(def.left);
56139
+ const newRight = run2(def.right);
56140
56140
  return newLeft === def.left && newRight === def.right ? s : clone(s, { ...def, left: newLeft, right: newRight });
56141
56141
  }
56142
56142
  case "optional":
@@ -56148,23 +56148,23 @@ function visit(schema, fnOrHandlers) {
56148
56148
  case "nonoptional":
56149
56149
  case "promise":
56150
56150
  case "success": {
56151
- const newInner = run(def.innerType);
56151
+ const newInner = run2(def.innerType);
56152
56152
  return newInner === def.innerType ? s : clone(s, { ...def, innerType: newInner });
56153
56153
  }
56154
56154
  case "pipe": {
56155
- const newIn = run(def.in);
56156
- const newOut = run(def.out);
56155
+ const newIn = run2(def.in);
56156
+ const newOut = run2(def.out);
56157
56157
  return newIn === def.in && newOut === def.out ? s : clone(s, { ...def, in: newIn, out: newOut });
56158
56158
  }
56159
56159
  case "function": {
56160
- const newInput = run(def.input);
56161
- const newOutput = run(def.output);
56160
+ const newInput = run2(def.input);
56161
+ const newOutput = run2(def.output);
56162
56162
  return newInput === def.input && newOutput === def.output ? s : clone(s, { ...def, input: newInput, output: newOutput });
56163
56163
  }
56164
56164
  case "lazy": {
56165
56165
  const original = def.getter;
56166
56166
  const { _cachedInner, ...rest } = def;
56167
- return clone(s, { ...rest, getter: () => run(original()) });
56167
+ return clone(s, { ...rest, getter: () => run2(original()) });
56168
56168
  }
56169
56169
  // A leaf by choice: `parts` are regex fragments, not data positions.
56170
56170
  case "template_literal":
@@ -56195,7 +56195,7 @@ function visit(schema, fnOrHandlers) {
56195
56195
  }
56196
56196
  }
56197
56197
  }
56198
- return run(schema);
56198
+ return run2(schema);
56199
56199
  }
56200
56200
 
56201
56201
  // ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/deep-partial.js
@@ -56630,9 +56630,9 @@ function isImageBase64Payload(record2, key) {
56630
56630
  if (key === "base64") return isEncodedImage(data, record2.type);
56631
56631
  return false;
56632
56632
  }
56633
- function describeImageBlock(block) {
56634
- if (block.type !== "image") return null;
56635
- const source = asRecord(block.source);
56633
+ function describeImageBlock(block2) {
56634
+ if (block2.type !== "image") return null;
56635
+ const source = asRecord(block2.source);
56636
56636
  const mediaType = typeof source?.media_type === "string" ? source.media_type : null;
56637
56637
  if (source?.type === "base64" && typeof source.data === "string") {
56638
56638
  return { mediaType, bytes: base64ByteLength(source.data) };
@@ -56805,7 +56805,7 @@ function jsonTokens(text) {
56805
56805
  let backslashes = 0;
56806
56806
  while (text[index - backslashes - 1] === "\\") backslashes += 1;
56807
56807
  let level = 0;
56808
- for (let run = backslashes + 1; run % 2 === 0; run /= 2) level += 1;
56808
+ for (let run2 = backslashes + 1; run2 % 2 === 0; run2 /= 2) level += 1;
56809
56809
  tokens.push({ index, level });
56810
56810
  }
56811
56811
  return tokens;
@@ -57641,8 +57641,8 @@ function createDigestTurnParser(options = {}) {
57641
57641
  }
57642
57642
  const model = typeof message2.model === "string" ? message2.model : null;
57643
57643
  const facts = originFacts(record2);
57644
- for (const block of message2.content) {
57645
- const part = block;
57644
+ for (const block2 of message2.content) {
57645
+ const part = block2;
57646
57646
  if (part.type === "tool_use" && typeof part.name === "string") {
57647
57647
  const call = { name: part.name, ...toolFilePath(part.input) };
57648
57648
  if (options.keepToolInputs === true && isRecord(part.input)) {
@@ -57757,19 +57757,19 @@ function toolResults(record2, evidence, tasks, line) {
57757
57757
  const result = isRecord(record2.toolUseResult) ? record2.toolUseResult : {};
57758
57758
  const file2 = isRecord(result.file) ? result.file : {};
57759
57759
  const task = isRecord(result.task) ? result.task : {};
57760
- for (const block of content) {
57761
- if (!isRecord(block) || block.type !== "tool_result" || typeof block.tool_use_id !== "string") continue;
57762
- if (!evidence.watching(block.tool_use_id)) continue;
57763
- const reads = tasks.reads.get(block.tool_use_id);
57764
- tasks.reads.delete(block.tool_use_id);
57760
+ for (const block2 of content) {
57761
+ if (!isRecord(block2) || block2.type !== "tool_result" || typeof block2.tool_use_id !== "string") continue;
57762
+ if (!evidence.watching(block2.tool_use_id)) continue;
57763
+ const reads = tasks.reads.get(block2.tool_use_id);
57764
+ tasks.reads.delete(block2.tool_use_id);
57765
57765
  const started = typeof result.backgroundTaskId === "string" ? result.backgroundTaskId : null;
57766
57766
  if (started !== null) keepBounded(tasks.started, started, reads ?? started);
57767
- const failed2 = block.is_error === true || result.interrupted === true;
57767
+ const failed2 = block2.is_error === true || result.interrupted === true;
57768
57768
  const taskExited = typeof task.exitCode === "number" && task.task_id === reads;
57769
57769
  const status = reads !== void 0 ? { succeeded: taskExited ? task.exitCode === 0 : null, running: taskExited ? null : reads } : { succeeded: failed2 ? false : started !== null ? null : true, running: started };
57770
- evidence.result(block.tool_use_id, {
57770
+ evidence.result(block2.tool_use_id, {
57771
57771
  ...status,
57772
- text: [result.stdout, result.stderr, file2.content, task.output].filter((part) => typeof part === "string").concat(contentText(block.content)).join("\n"),
57772
+ text: [result.stdout, result.stderr, file2.content, task.output].filter((part) => typeof part === "string").concat(contentText(block2.content)).join("\n"),
57773
57773
  at: recordTime(record2),
57774
57774
  line
57775
57775
  });
@@ -57778,7 +57778,7 @@ function toolResults(record2, evidence, tasks, line) {
57778
57778
  function contentText(content) {
57779
57779
  if (typeof content === "string") return content;
57780
57780
  if (!Array.isArray(content)) return "";
57781
- return content.map((block) => isRecord(block) && typeof block.text === "string" ? block.text : "").join("\n");
57781
+ return content.map((block2) => isRecord(block2) && typeof block2.text === "string" ? block2.text : "").join("\n");
57782
57782
  }
57783
57783
  function messageIdOf(record2, message2) {
57784
57784
  if (typeof message2.id === "string") return message2.id;
@@ -57845,13 +57845,13 @@ function userText(record2) {
57845
57845
  if (typeof content === "string") return content.trim() === "" ? null : content;
57846
57846
  if (!Array.isArray(content)) return null;
57847
57847
  const parts = [];
57848
- for (const block of content) {
57849
- if (!isRecord(block)) continue;
57850
- if (block.type === "text" && typeof block.text === "string" && block.text.trim() !== "") {
57851
- parts.push(block.text);
57848
+ for (const block2 of content) {
57849
+ if (!isRecord(block2)) continue;
57850
+ if (block2.type === "text" && typeof block2.text === "string" && block2.text.trim() !== "") {
57851
+ parts.push(block2.text);
57852
57852
  continue;
57853
57853
  }
57854
- const image = describeImageBlock(block);
57854
+ const image = describeImageBlock(block2);
57855
57855
  if (image) parts.push(imageLabel(image));
57856
57856
  }
57857
57857
  return parts.length > 0 ? parts.join("\n") : null;
@@ -58374,6 +58374,7 @@ var MACHINE_ENROLL_CALLBACK_URL = `http://127.0.0.1:${MACHINE_ENROLL_CALLBACK_PO
58374
58374
 
58375
58375
  // ../core/src/sessionRooms.ts
58376
58376
  var ROOM_QUIET_AFTER_MS = 7 * 24 * 36e5;
58377
+ var RECENT_ACTIVITY_WINDOW_MS = 5 * 24 * 36e5;
58377
58378
  function capturedSessionEvents(turns, anchor2) {
58378
58379
  const events = [];
58379
58380
  const base = {
@@ -58459,22 +58460,22 @@ function safeJsonRecord(text) {
58459
58460
  function outputTextBlocks(content) {
58460
58461
  if (!Array.isArray(content)) return [];
58461
58462
  return content.filter(
58462
- (block) => typeof block === "object" && block !== null && block.type === "output_text" && typeof block.text === "string"
58463
- ).map((block) => block.text);
58463
+ (block2) => typeof block2 === "object" && block2 !== null && block2.type === "output_text" && typeof block2.text === "string"
58464
+ ).map((block2) => block2.text);
58464
58465
  }
58465
58466
  function promptText(content) {
58466
58467
  if (!Array.isArray(content)) return null;
58467
58468
  const kept = [];
58468
58469
  const textOrigins = [];
58469
58470
  for (const raw of content) {
58470
- const block = asRecord2(raw);
58471
- if (!block) continue;
58472
- if (block.type === "input_image") {
58473
- kept.push(imageLabel(describeImageUrl(block.image_url)));
58471
+ const block2 = asRecord2(raw);
58472
+ if (!block2) continue;
58473
+ if (block2.type === "input_image") {
58474
+ kept.push(imageLabel(describeImageUrl(block2.image_url)));
58474
58475
  continue;
58475
58476
  }
58476
- if (block.type !== "input_text" || typeof block.text !== "string") continue;
58477
- const parsed = codexPromptBlock(block.text);
58477
+ if (block2.type !== "input_text" || typeof block2.text !== "string") continue;
58478
+ const parsed = codexPromptBlock(block2.text);
58478
58479
  if (parsed.origin === "harness") continue;
58479
58480
  textOrigins.push(parsed.origin);
58480
58481
  if (parsed.ask !== "") kept.push(parsed.ask);
@@ -58689,8 +58690,8 @@ function scriptResult(evidence, script, header, blocks, at, line) {
58689
58690
  function outputBlocks(output2) {
58690
58691
  if (typeof output2 === "string") return [output2];
58691
58692
  if (!Array.isArray(output2)) return [];
58692
- return output2.map((block) => {
58693
- const text = asRecord2(block)?.text;
58693
+ return output2.map((block2) => {
58694
+ const text = asRecord2(block2)?.text;
58694
58695
  return typeof text === "string" ? text : "";
58695
58696
  });
58696
58697
  }
@@ -58818,9 +58819,9 @@ function parseCursorRunEventsDetailed(runs, options = {}) {
58818
58819
  pendingTools = [];
58819
58820
  pendingTokens = 0;
58820
58821
  };
58821
- for (const run of runs) {
58822
+ for (const run2 of runs) {
58822
58823
  const openCalls = /* @__PURE__ */ new Map();
58823
- for (const raw of run.events) {
58824
+ for (const raw of run2.events) {
58824
58825
  const evt = asRawEvent(raw);
58825
58826
  if (!evt) continue;
58826
58827
  if (evt.event === "tool_call") {
@@ -59110,9 +59111,9 @@ function envelopeRuns(transcript) {
59110
59111
  if (!parsed || !Array.isArray(parsed.runs)) return [];
59111
59112
  const runs = [];
59112
59113
  for (const item of parsed.runs) {
59113
- const run = item;
59114
- if (typeof run?.runId !== "string" || typeof run.sse !== "string") continue;
59115
- runs.push({ runId: run.runId, events: redactCursorRunEvents(parseSseEvents(run.sse), { servedOnly: true }) });
59114
+ const run2 = item;
59115
+ if (typeof run2?.runId !== "string" || typeof run2.sse !== "string") continue;
59116
+ runs.push({ runId: run2.runId, events: redactCursorRunEvents(parseSseEvents(run2.sse), { servedOnly: true }) });
59116
59117
  }
59117
59118
  return runs;
59118
59119
  }
@@ -59131,6 +59132,7 @@ var SECRET_QUERY_PARAM_PATTERN = /^(?:token|access_token|refresh_token|id_token|
59131
59132
  var BEARER_TOKEN_PATTERN = /\bBearer\s+[\w\-._~+/=]+/gi;
59132
59133
  var JWT_PATTERN = /\b[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g;
59133
59134
  var EMBEDDED_QUERY_SECRET_PATTERN = /([?&](?:token|access_token|refresh_token|id_token|api_key|apikey|key|code|signature|password|secret|client_secret|state)=)[^&\s#]+/gi;
59135
+ var URL_USER_INFO_PATTERN = /(:\/\/)(?:[^\s/?#@"<>:[]+:(?!\d*(?:[/?#\s"<>]|$))[^\s"<>]{0,256}|[^\s/?#"<>]+)@/g;
59134
59136
  var URL_FIELD_NAMES = /* @__PURE__ */ new Set([
59135
59137
  "url",
59136
59138
  "to",
@@ -59199,11 +59201,15 @@ var OUTGOING_URL_FIELD_NAMES = ["url", "url.full", "http.url"];
59199
59201
  var OUTGOING_PATH_FIELD_NAMES = ["url.path", "http.target"];
59200
59202
  var MAX_DEPTH = 10;
59201
59203
  function scrubString(value) {
59202
- return value.replace(POWERPLANT_TOKEN_PATTERN, REDACTED3).replace(BEARER_TOKEN_PATTERN, `Bearer ${REDACTED3}`).replace(JWT_PATTERN, REDACTED3).replace(EMBEDDED_QUERY_SECRET_PATTERN, `$1${REDACTED3}`);
59204
+ return scrubUserInfo(value).replace(POWERPLANT_TOKEN_PATTERN, REDACTED3).replace(BEARER_TOKEN_PATTERN, `Bearer ${REDACTED3}`).replace(JWT_PATTERN, REDACTED3).replace(EMBEDDED_QUERY_SECRET_PATTERN, `$1${REDACTED3}`);
59203
59205
  }
59204
59206
  function scrubUrl(url2) {
59205
- const queryStart = url2.search(/[?#]/);
59206
- return queryStart === -1 ? url2 : url2.slice(0, queryStart);
59207
+ const scrubbed = scrubUserInfo(url2);
59208
+ const queryStart = scrubbed.search(/[?#]/);
59209
+ return queryStart === -1 ? scrubbed : scrubbed.slice(0, queryStart);
59210
+ }
59211
+ function scrubUserInfo(value) {
59212
+ return value.replace(URL_USER_INFO_PATTERN, `$1${REDACTED3}@`);
59207
59213
  }
59208
59214
  function scrubOutgoingUrl(url2) {
59209
59215
  const parts = URL_PARTS.exec(url2);
@@ -59361,11 +59367,15 @@ function addLocalSessionEvent(evidence, event) {
59361
59367
  const writes = writesAfterTurnEndMs.includes(event.atMs) ? writesAfterTurnEndMs : [...writesAfterTurnEndMs, event.atMs].sort((a, b2) => a - b2).slice(-WRITES_KEPT_AFTER_TURN_END);
59362
59368
  return { ...evidence, lastWriteMs, writesAfterTurnEndMs: writes };
59363
59369
  }
59364
- case "turn-end": {
59370
+ case "turn-end":
59371
+ case "transcript-turn-end": {
59372
+ const inTranscript = event.kind === "transcript-turn-end";
59373
+ if (event.atMs === lastTurnEndMs && inTranscript) return { ...evidence, lastTurnEndInTranscript: true };
59365
59374
  if (lastTurnEndMs !== void 0 && event.atMs <= lastTurnEndMs) return evidence;
59366
59375
  return {
59367
59376
  ...evidence,
59368
59377
  lastTurnEndMs: event.atMs,
59378
+ lastTurnEndInTranscript: inTranscript,
59369
59379
  lastWriteByTurnEndMs: latest(evidence.lastWriteByTurnEndMs, writesAfterTurnEndMs.findLast((ms) => ms <= event.atMs)),
59370
59380
  writesAfterTurnEndMs: writesAfterTurnEndMs.filter((ms) => ms > event.atMs)
59371
59381
  };
@@ -59389,16 +59399,19 @@ function deadlineAfterLastEvent({ lastWriteMs, lastTurnEndMs, lastBoundary }) {
59389
59399
  function statusFromEvents({
59390
59400
  lastWriteMs,
59391
59401
  lastTurnEndMs,
59402
+ lastTurnEndInTranscript,
59392
59403
  lastWriteByTurnEndMs,
59393
59404
  lastBoundary
59394
59405
  }) {
59395
- const isNewWork = (writeMs, eventMs) => writeMs !== void 0 && writeMs > eventMs + END_OBSERVATION_WRITE_SLACK_MS;
59406
+ const isNewWork = (writeMs, eventMs, slackMs2 = END_OBSERVATION_WRITE_SLACK_MS) => writeMs !== void 0 && writeMs > eventMs + slackMs2;
59396
59407
  const endMs = lastBoundary?.event === "session-end" ? lastBoundary.atMs : void 0;
59397
59408
  const startMs = lastBoundary?.event === "session-start" ? lastBoundary.atMs : void 0;
59398
59409
  if (endMs !== void 0 && !isNewWork(lastWriteMs, endMs)) return "ended";
59399
59410
  const turnEndCounts = endMs === void 0 || isNewWork(lastWriteByTurnEndMs, endMs);
59400
- const waitingSinceMs = latest(turnEndCounts ? lastTurnEndMs : void 0, startMs);
59401
- if (waitingSinceMs !== void 0 && !isNewWork(lastWriteMs, waitingSinceMs)) return "waiting";
59411
+ const turnEndMs = turnEndCounts ? lastTurnEndMs : void 0;
59412
+ const waitingSinceMs = latest(turnEndMs, startMs);
59413
+ const slackMs = waitingSinceMs === turnEndMs && lastTurnEndInTranscript === true ? 0 : END_OBSERVATION_WRITE_SLACK_MS;
59414
+ if (waitingSinceMs !== void 0 && !isNewWork(lastWriteMs, waitingSinceMs, slackMs)) return "waiting";
59402
59415
  return lastWriteMs === void 0 ? "unknown" : "working";
59403
59416
  }
59404
59417
  function latest(a, b2) {
@@ -78716,10 +78729,10 @@ function dispatchEngineSpan(engineSpan, allSpans, linkIds, ignoreSpanTypes) {
78716
78729
  }
78717
78730
  );
78718
78731
  }
78719
- function endSpan2(span, run) {
78732
+ function endSpan2(span, run2) {
78720
78733
  let result;
78721
78734
  try {
78722
- result = run();
78735
+ result = run2();
78723
78736
  } catch (reason) {
78724
78737
  span.end();
78725
78738
  throw reason;
@@ -80688,7 +80701,7 @@ var KafkaJsInstrumentation = class extends import_instrumentation9.Instrumentati
80688
80701
  _getConsumerRunPatch() {
80689
80702
  const instrumentation = this;
80690
80703
  return (original) => {
80691
- return function run(...args) {
80704
+ return function run2(...args) {
80692
80705
  const config2 = args[0];
80693
80706
  if (config2?.eachMessage) {
80694
80707
  if ((0, import_instrumentation9.isWrapped)(config2.eachMessage)) {
@@ -86735,7 +86748,7 @@ import { createHmac } from "node:crypto";
86735
86748
  // ../../apps/syncservice/src/http.ts
86736
86749
  var import_undici = __toESM(require_undici(), 1);
86737
86750
  var CLI_VERSION_HEADER = "x-powerplant-cli-version";
86738
- var cliVersion = true ? "0.4.21" : "0.0.0-checkout";
86751
+ var cliVersion = true ? "0.4.22" : "0.0.0-checkout";
86739
86752
  function cliVersionHeaders() {
86740
86753
  return { [CLI_VERSION_HEADER]: cliVersion, "user-agent": `powerplant-cli/${cliVersion}` };
86741
86754
  }
@@ -91077,7 +91090,9 @@ function parseClaudeCodeTranscript(text) {
91077
91090
  let entrypoint;
91078
91091
  let anonCounter = 0;
91079
91092
  let speedMode;
91093
+ let endsWithAbortedTurn = false;
91080
91094
  for (const record2 of parseJsonl(text)) {
91095
+ endsWithAbortedTurn = abortedTurnOf(record2) ?? endsWithAbortedTurn;
91081
91096
  if (record2.type === "custom-title" && typeof record2.customTitle === "string") {
91082
91097
  title = record2.customTitle;
91083
91098
  continue;
@@ -91138,8 +91153,38 @@ function parseClaudeCodeTranscript(text) {
91138
91153
  turnCount: usageByMessage.size,
91139
91154
  cwd,
91140
91155
  gitBranch,
91141
- entrypoint
91142
- };
91156
+ entrypoint,
91157
+ endsWithAbortedTurn
91158
+ };
91159
+ }
91160
+ var STOPPED_TURN_TEXT = "[Request interrupted by user";
91161
+ var LOCAL_COMMAND_TEXT = /^\s*<(?:local-command-caveat|command-name|local-command-stdout|local-command-stderr)>/;
91162
+ var messageContentSchema = external_exports.object({ content: external_exports.union([external_exports.string(), external_exports.array(external_exports.unknown())]) });
91163
+ var textBlockSchema = external_exports.object({ type: external_exports.literal("text"), text: external_exports.string() });
91164
+ function abortedTurnOf(record2) {
91165
+ if (record2.type !== "user" && record2.type !== "assistant" || record2.isSidechain === true) return void 0;
91166
+ if (record2.type === "assistant") return record2.isApiErrorMessage === true;
91167
+ const text = firstText(record2.message);
91168
+ return LOCAL_COMMAND_TEXT.test(text) ? void 0 : text.startsWith(STOPPED_TURN_TEXT);
91169
+ }
91170
+ function claudeCodeEndsWithAbortedTurn(tail) {
91171
+ const records = [...parseJsonl(tail)];
91172
+ for (let index = records.length - 1; index >= 0; index -= 1) {
91173
+ const aborted2 = abortedTurnOf(records[index]);
91174
+ if (aborted2 !== void 0) return aborted2;
91175
+ }
91176
+ return false;
91177
+ }
91178
+ function firstText(message2) {
91179
+ const parsed = messageContentSchema.safeParse(message2);
91180
+ if (!parsed.success) return "";
91181
+ const { content } = parsed.data;
91182
+ if (typeof content === "string") return content;
91183
+ for (const block2 of content) {
91184
+ const text = textBlockSchema.safeParse(block2);
91185
+ if (text.success) return text.data.text;
91186
+ }
91187
+ return "";
91143
91188
  }
91144
91189
  var AGENT_SDK_ENTRYPOINTS = /* @__PURE__ */ new Set(["sdk-py", "sdk-ts"]);
91145
91190
  function isAutomationEntrypoint(entrypoint) {
@@ -91249,6 +91294,7 @@ async function collectClaudeCodeSessions(options = {}) {
91249
91294
  const endedAt = endObservedAt ?? laterTimestamp(summary.endedAt, usage.endedAt);
91250
91295
  items.push({
91251
91296
  mtimeMs,
91297
+ endsWithAbortedTurn: summary.endsWithAbortedTurn,
91252
91298
  transcriptPath,
91253
91299
  transcriptPrefix: transcript.prefix,
91254
91300
  subagents: {
@@ -91367,9 +91413,9 @@ function listWorkflowTranscripts(workflowsDir) {
91367
91413
  failedToRead();
91368
91414
  return [];
91369
91415
  };
91370
- for (const run of list(workflowsDir)) {
91371
- if (!run.isDirectory()) continue;
91372
- const runDir = path15.join(workflowsDir, run.name);
91416
+ for (const run2 of list(workflowsDir)) {
91417
+ if (!run2.isDirectory()) continue;
91418
+ const runDir = path15.join(workflowsDir, run2.name);
91373
91419
  for (const file2 of list(runDir)) {
91374
91420
  if (!file2.isFile() || !file2.name.endsWith(".jsonl")) continue;
91375
91421
  try {
@@ -92638,7 +92684,9 @@ var claudeLocal = {
92638
92684
  },
92639
92685
  registration: jsonServerEntry,
92640
92686
  // <project folder>/<session id>.jsonl
92641
- transcriptSession: (transcriptPath) => ({ externalId: path18.basename(transcriptPath, ".jsonl") })
92687
+ transcriptSession: (transcriptPath) => ({ externalId: path18.basename(transcriptPath, ".jsonl") }),
92688
+ // No Stop hook runs when a person stops a turn or the API fails it.
92689
+ endsWithAbortedTurn: claudeCodeEndsWithAbortedTurn
92642
92690
  };
92643
92691
  var codexLocal = {
92644
92692
  harness: "codex-local",
@@ -93373,7 +93421,7 @@ function watchHarnessConnections(deps) {
93373
93421
  }
93374
93422
  }
93375
93423
  };
93376
- const run = async (harnesses) => {
93424
+ const run2 = async (harnesses) => {
93377
93425
  running = true;
93378
93426
  try {
93379
93427
  const before = /* @__PURE__ */ new Map();
@@ -93395,7 +93443,7 @@ function watchHarnessConnections(deps) {
93395
93443
  if (queued !== null && !stopped) {
93396
93444
  const next = queued === "all" ? void 0 : [...queued];
93397
93445
  queued = null;
93398
- void run(next);
93446
+ void run2(next);
93399
93447
  }
93400
93448
  };
93401
93449
  const request2 = (harnesses) => {
@@ -93404,7 +93452,7 @@ function watchHarnessConnections(deps) {
93404
93452
  queued = harnesses === void 0 || queued === "all" ? "all" : /* @__PURE__ */ new Set([...queued ?? [], ...harnesses]);
93405
93453
  return;
93406
93454
  }
93407
- void run(harnesses);
93455
+ void run2(harnesses);
93408
93456
  };
93409
93457
  request2(void 0);
93410
93458
  const timer = setInterval(() => request2(void 0), SIGN_IN_CHECK_INTERVAL_MS);
@@ -93480,14 +93528,14 @@ function startHarnessConnectionChecks(options) {
93480
93528
  }
93481
93529
 
93482
93530
  // ../../apps/syncservice/src/recentCatchup.ts
93483
- function createRecentCatchup(run, now = Date.now) {
93531
+ function createRecentCatchup(run2, now = Date.now) {
93484
93532
  let coveredSince = now();
93485
93533
  let nextCheck = coveredSince + 15e3;
93486
93534
  return async () => {
93487
93535
  const started = now();
93488
93536
  if (started < nextCheck) return;
93489
93537
  try {
93490
- const result = await run(coveredSince - 1e3);
93538
+ const result = await run2(coveredSince - 1e3);
93491
93539
  if (result === false) return;
93492
93540
  const diagnostic = result.diagnostic;
93493
93541
  if (diagnostic?.rawFailed !== 0 || diagnostic.rawMissingMetadata !== 0 || diagnostic.deferred !== 0 || diagnostic.rawUnconfigured !== false) return;
@@ -94175,7 +94223,7 @@ function defaultStat(file2) {
94175
94223
  }
94176
94224
 
94177
94225
  // ../../apps/syncservice/src/sessionStatusFeed.ts
94178
- import { statSync as statSync5, watch as fsWatch2 } from "node:fs";
94226
+ import { closeSync as closeSync7, fstatSync as fstatSync3, openSync as openSync7, readSync as readSync5, statSync as statSync5, watch as fsWatch2 } from "node:fs";
94179
94227
 
94180
94228
  // ../../apps/syncservice/src/sessionStatusModel.ts
94181
94229
  function createSessionStatusModel(now = Date.now) {
@@ -94194,6 +94242,7 @@ function createSessionStatusModel(now = Date.now) {
94194
94242
  return {
94195
94243
  applyLifecycleEntry: (entry) => apply(entry.harness, entry.sessionId, { kind: entry.event, atMs: Date.parse(entry.at) }),
94196
94244
  applyTranscriptWrite: (write) => apply(write.harness, write.sessionId, { kind: "transcript-write", atMs: write.atMs }),
94245
+ applyTranscriptTurnEnd: (turnEnd) => apply(turnEnd.harness, turnEnd.sessionId, { kind: "transcript-turn-end", atMs: turnEnd.atMs }),
94197
94246
  expireDeadlines() {
94198
94247
  const nowMs = now();
94199
94248
  const changes = [];
@@ -94227,6 +94276,7 @@ function createSessionStatusModel(now = Date.now) {
94227
94276
  var TRANSCRIPT_WRITE_DELAY_MS = 500;
94228
94277
  var HELD_HOOK_ENTRIES_MAX = 500;
94229
94278
  var TRANSCRIPT_FILE_WATCHES_MAX = 32;
94279
+ var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
94230
94280
  var HOOK_ID_PAIRS_MAX = 1e4;
94231
94281
  var MAX_TIMER_MS = 2 ** 31 - 1;
94232
94282
  function createSessionStatusFeed(deps) {
@@ -94244,6 +94294,7 @@ function createSessionStatusFeed(deps) {
94244
94294
  let readQueued = false;
94245
94295
  let readFailing = false;
94246
94296
  let statFailing = false;
94297
+ let tailFailing = false;
94247
94298
  let watchFailing = false;
94248
94299
  const writeTimers = /* @__PURE__ */ new Map();
94249
94300
  const hookFilesRead = /* @__PURE__ */ new Set();
@@ -94301,8 +94352,20 @@ function createSessionStatusFeed(deps) {
94301
94352
  held = [];
94302
94353
  for (const entry of waiting) apply(entry);
94303
94354
  }
94304
- function applyWrite(harness, sessionId, atMs) {
94305
- if (model.applyTranscriptWrite({ harness, sessionId, atMs }) !== null) changedInBatch = true;
94355
+ function applyWrite(harness, sessionId, atMs, endsWithAbortedTurn) {
94356
+ const before = model.status(harness, sessionId);
94357
+ model.applyTranscriptWrite({ harness, sessionId, atMs });
94358
+ const status = model.status(harness, sessionId);
94359
+ if ((status === "working" || status === "unknown") && endsWithAbortedTurn()) {
94360
+ model.applyTranscriptTurnEnd({ harness, sessionId, atMs });
94361
+ }
94362
+ if (model.status(harness, sessionId) !== before) changedInBatch = true;
94363
+ }
94364
+ function fileEndsWithAbortedTurn(harness, transcriptPath) {
94365
+ const endsWithAbortedTurn = adapterOf(harness)?.endsWithAbortedTurn;
94366
+ if (endsWithAbortedTurn === void 0) return false;
94367
+ const tail = transcriptTail(transcriptPath);
94368
+ return tail !== null && endsWithAbortedTurn(tail);
94306
94369
  }
94307
94370
  function applyWatchedWrite(harness, transcriptPath) {
94308
94371
  const session = adapterOf(harness)?.transcriptSession?.(transcriptPath);
@@ -94312,7 +94375,7 @@ function createSessionStatusFeed(deps) {
94312
94375
  const mtimeMs = modifiedMs(transcriptPath);
94313
94376
  if (mtimeMs === null) return;
94314
94377
  const learned = "externalId" in session && learnPair(harness, externalId);
94315
- applyWrite(harness, externalId, mtimeMs);
94378
+ applyWrite(harness, externalId, mtimeMs, () => fileEndsWithAbortedTurn(harness, transcriptPath));
94316
94379
  reportFile(transcriptPath, { harness, externalId });
94317
94380
  if (learned) releaseHeld();
94318
94381
  }
@@ -94361,7 +94424,9 @@ function createSessionStatusFeed(deps) {
94361
94424
  watchFailing = false;
94362
94425
  fileWatches.set(file2, { ...session, close: close2 });
94363
94426
  const mtimeMs = modifiedMs(file2);
94364
- if (mtimeMs !== null) applyWrite(session.harness, session.externalId, mtimeMs);
94427
+ if (mtimeMs !== null) {
94428
+ applyWrite(session.harness, session.externalId, mtimeMs, () => fileEndsWithAbortedTurn(session.harness, file2));
94429
+ }
94365
94430
  }
94366
94431
  function closeFileWatch(file2) {
94367
94432
  fileWatches.get(file2)?.close();
@@ -94391,6 +94456,27 @@ function createSessionStatusFeed(deps) {
94391
94456
  return null;
94392
94457
  }
94393
94458
  }
94459
+ function transcriptTail(transcriptPath) {
94460
+ let fd;
94461
+ try {
94462
+ fd = openSync7(transcriptPath, "r");
94463
+ const size = fstatSync3(fd).size;
94464
+ const buffer = Buffer.alloc(Math.min(size, TRANSCRIPT_TAIL_BYTES));
94465
+ const bytes = readSync5(fd, buffer, 0, buffer.length, size - buffer.length);
94466
+ if (tailFailing) deps.log("transcript ends readable again");
94467
+ tailFailing = false;
94468
+ return buffer.toString("utf8", 0, bytes);
94469
+ } catch (error63) {
94470
+ if (errorCode(error63) === "ENOENT") return null;
94471
+ if (!tailFailing) {
94472
+ deps.log(`\u26A0 transcript end not read (${errorCode(error63)}), a stopped turn shows as working until its deadline`);
94473
+ }
94474
+ tailFailing = true;
94475
+ return null;
94476
+ } finally {
94477
+ if (fd !== void 0) closeSync7(fd);
94478
+ }
94479
+ }
94394
94480
  function noteWrite(harness, transcriptPath) {
94395
94481
  if (writeTimers.has(transcriptPath)) return;
94396
94482
  const timer = setTimeout(() => {
@@ -94451,9 +94537,9 @@ function createSessionStatusFeed(deps) {
94451
94537
  onEvent(change);
94452
94538
  },
94453
94539
  observeSessions(sessions) {
94454
- for (const { item, mtimeMs } of sessions) {
94540
+ for (const { item, mtimeMs, endsWithAbortedTurn } of sessions) {
94455
94541
  learnPair(item.harness, item.harnessSessionId);
94456
- applyWrite(item.harness, item.harnessSessionId, mtimeMs);
94542
+ applyWrite(item.harness, item.harnessSessionId, mtimeMs, () => endsWithAbortedTurn === true);
94457
94543
  }
94458
94544
  releaseHeld();
94459
94545
  settle2();
@@ -95221,7 +95307,7 @@ import os20 from "node:os";
95221
95307
  import path23 from "node:path";
95222
95308
 
95223
95309
  // ../../apps/syncservice/src/codexReviewFlags.ts
95224
- import { closeSync as closeSync7, lstatSync as lstatSync5, openSync as openSync7, readSync as readSync5, realpathSync as realpathSync4 } from "node:fs";
95310
+ import { closeSync as closeSync8, lstatSync as lstatSync5, openSync as openSync8, readSync as readSync6, realpathSync as realpathSync4 } from "node:fs";
95225
95311
  import path22 from "node:path";
95226
95312
  var pointerSchema = external_exports.object({
95227
95313
  source: external_exports.unknown().optional(),
@@ -95259,14 +95345,14 @@ function readCodexReviewFlags(row, id, home) {
95259
95345
  }
95260
95346
  }
95261
95347
  function readPrefix(file2) {
95262
- const fd = openSync7(file2, "r");
95348
+ const fd = openSync8(file2, "r");
95263
95349
  const buffer = Buffer.alloc(MAX_PREFIX_BYTES);
95264
95350
  let used = 0;
95265
95351
  let first = -1;
95266
95352
  let second = -1;
95267
95353
  try {
95268
95354
  while (used < buffer.length && second < 0) {
95269
- const bytes = readSync5(fd, buffer, used, Math.min(4096, buffer.length - used), null);
95355
+ const bytes = readSync6(fd, buffer, used, Math.min(4096, buffer.length - used), null);
95270
95356
  if (bytes === 0) break;
95271
95357
  const end = used + bytes;
95272
95358
  for (let i = used; i < end; i++) {
@@ -95280,7 +95366,7 @@ function readPrefix(file2) {
95280
95366
  used = end;
95281
95367
  }
95282
95368
  } finally {
95283
- closeSync7(fd);
95369
+ closeSync8(fd);
95284
95370
  }
95285
95371
  if (first < 0 || second < 0) return null;
95286
95372
  try {
@@ -95811,7 +95897,7 @@ import { accessSync, constants as constants2, readFileSync as readFileSync15 } f
95811
95897
  import path28 from "node:path";
95812
95898
 
95813
95899
  // src/version.ts
95814
- var CLI_VERSION = true ? "0.4.21" : "0.0.0-checkout";
95900
+ var CLI_VERSION = true ? "0.4.22" : "0.0.0-checkout";
95815
95901
  var CLI_RELEASE_BUILD = true ? true : false;
95816
95902
  var CLI_PACKAGE_NAME = "@usepowerplant/cli";
95817
95903
 
@@ -96510,7 +96596,7 @@ function syncHeader(i) {
96510
96596
  var AGENT_TITLE_MAX = 48;
96511
96597
  function recentAgentItems(i) {
96512
96598
  if (i.memberSessions === null) return null;
96513
- const recent = i.memberSessions.filter((session) => i.now - Date.parse(session.lastActivityAt) < RECENT_SESSION_WINDOW_MS).filter((session) => session.archived !== true && session.subagent !== true && session.standaloneReview !== true).sort((a, b2) => Date.parse(b2.lastActivityAt) - Date.parse(a.lastActivityAt)).map((session) => agentRow(session, i)).filter((row) => row.agent?.status !== "ended");
96599
+ const recent = i.memberSessions.filter((session) => i.now - Date.parse(session.lastActivityAt) < RECENT_SESSION_WINDOW_MS).filter((session) => session.archived !== true && session.subagent !== true && session.standaloneReview !== true).sort((a, b2) => Date.parse(b2.lastActivityAt) - Date.parse(a.lastActivityAt)).map((session) => agentRow(session, i)).filter((row) => row.agent?.status !== "ended").sort((a, b2) => Number(b2.agent?.status === "working") - Number(a.agent?.status === "working"));
96514
96600
  const rows = recent.length === 0 ? [{ type: "status", mark: "muted", title: i.memberSessionsIncomplete ? "No sessions found in the partial list" : "No eligible sessions in the latest results" }] : groupedAgentRows(recent.slice(0, RECENT_SESSION_VISIBLE_LIMIT));
96515
96601
  if (i.memberSessionsIncomplete) rows.push({
96516
96602
  type: "status",
@@ -97979,14 +98065,14 @@ function createMenuModel(deps) {
97979
98065
  renewDue ||= renew;
97980
98066
  if (stopped || sources === null || awaitingRefresh || renderPending) return;
97981
98067
  renderPending = true;
97982
- const run = () => {
98068
+ const run2 = () => {
97983
98069
  renderPending = false;
97984
98070
  renderTimer = null;
97985
98071
  if (!stopped) renderNow();
97986
98072
  };
97987
98073
  const wait = lastContentWriteAt + MENU_WRITE_INTERVAL_MS - now();
97988
- if (wait <= 0) queueMicrotask(run);
97989
- else renderTimer = setTimeout(run, wait);
98074
+ if (wait <= 0) queueMicrotask(run2);
98075
+ else renderTimer = setTimeout(run2, wait);
97990
98076
  }
97991
98077
  function renderNow() {
97992
98078
  if (sources === null || awaitingRefresh) return;
@@ -98236,12 +98322,12 @@ function startMenuModel(sources) {
98236
98322
  // src/cli.ts
98237
98323
  import { spawn as spawn4 } from "node:child_process";
98238
98324
  import { randomUUID as randomUUID6 } from "node:crypto";
98239
- import { mkdirSync as mkdirSync20, openSync as openSync10, realpathSync as realpathSync5 } from "node:fs";
98325
+ import { mkdirSync as mkdirSync20, openSync as openSync11, realpathSync as realpathSync5 } from "node:fs";
98240
98326
  import path37 from "node:path";
98241
98327
  import { parseArgs } from "node:util";
98242
98328
 
98243
98329
  // src/doctor.ts
98244
- import { closeSync as closeSync8, fstatSync as fstatSync3, openSync as openSync8, readSync as readSync6 } from "node:fs";
98330
+ import { closeSync as closeSync9, fstatSync as fstatSync4, openSync as openSync9, readSync as readSync7 } from "node:fs";
98245
98331
  var DOCTOR_LOG_TAIL_LINES = 50;
98246
98332
  var LOG_TAIL_READ_BYTES = 256 * 1024;
98247
98333
  function installLine(install) {
@@ -98257,23 +98343,23 @@ function redactedLogTail(raw) {
98257
98343
  function readDoctorLogTail(path38) {
98258
98344
  let fd;
98259
98345
  try {
98260
- fd = openSync8(path38, "r");
98346
+ fd = openSync9(path38, "r");
98261
98347
  } catch (error63) {
98262
98348
  if (error63.code === "ENOENT") return { path: path38, lines: null };
98263
98349
  return { path: path38, lines: null, readError: error63 instanceof Error ? error63.message : String(error63) };
98264
98350
  }
98265
98351
  try {
98266
- const size = fstatSync3(fd).size;
98352
+ const size = fstatSync4(fd).size;
98267
98353
  const offset = Math.max(0, size - LOG_TAIL_READ_BYTES);
98268
98354
  const buffer = Buffer.alloc(Math.min(size, LOG_TAIL_READ_BYTES));
98269
- readSync6(fd, buffer, 0, buffer.length, offset);
98355
+ readSync7(fd, buffer, 0, buffer.length, offset);
98270
98356
  let text = buffer.toString("utf8");
98271
98357
  if (offset > 0) text = text.slice(text.indexOf("\n") + 1);
98272
98358
  return { path: path38, lines: redactedLogTail(text) };
98273
98359
  } catch (error63) {
98274
98360
  return { path: path38, lines: null, readError: error63 instanceof Error ? error63.message : String(error63) };
98275
98361
  } finally {
98276
- closeSync8(fd);
98362
+ closeSync9(fd);
98277
98363
  }
98278
98364
  }
98279
98365
  function renderDoctorReport(i) {
@@ -98528,7 +98614,7 @@ function pausedSince(control, now) {
98528
98614
  }
98529
98615
  function pausedNotice(control, resumeCommand2, now) {
98530
98616
  if (!control.paused) return null;
98531
- return `! ${pausedSince(control, now)}. Nothing uploads until you run \`${resumeCommand2}\`.`;
98617
+ return `! ${pausedSince(control, now)}. To resume sync, run \`${resumeCommand2}\`.`;
98532
98618
  }
98533
98619
  function pausedSyncNotice(control, resumeCommand2, now, dryRun) {
98534
98620
  if (!control.paused || dryRun) return pausedNotice(control, resumeCommand2, now);
@@ -98548,7 +98634,7 @@ async function offerResume(deps, options) {
98548
98634
  );
98549
98635
  return "resumed";
98550
98636
  }
98551
- output2.log(`Nothing uploads until you run \`${deps.resumeCommand}\``);
98637
+ output2.log(`To resume sync, run \`${deps.resumeCommand}\``);
98552
98638
  return "still-paused";
98553
98639
  }
98554
98640
  async function confirmResume(deps, output2) {
@@ -98733,7 +98819,7 @@ async function runInit(deps) {
98733
98819
  if (pauseOffer === "still-paused") {
98734
98820
  command.log("! Syncing is paused");
98735
98821
  command.log("");
98736
- command.detail(`Nothing uploads until you run \`${deps.pause.resumeCommand}\``);
98822
+ command.detail(`To resume sync, run \`${deps.pause.resumeCommand}\``);
98737
98823
  if (connectIncomplete) command.detail(`Finish MCP setup: \`${deps.connectCommand}\``);
98738
98824
  return "paused";
98739
98825
  }
@@ -98888,113 +98974,187 @@ function runResumeSession(source, sessionId, deps = {}) {
98888
98974
  }
98889
98975
 
98890
98976
  // src/statusReport.ts
98891
- function automaticUpdatesLine(control) {
98892
- switch (automaticUpdatesChoice(control)) {
98893
- case "on":
98894
- return "Automatic updates: on, the background sync installs new releases within an hour.";
98895
- case "off":
98896
- return "Automatic updates: off, turn on with: powerplant update --automatic on";
98897
- case "not-chosen":
98898
- return "Automatic updates: not turned on yet, keep this CLI current with: powerplant update --automatic on";
98899
- }
98977
+ var block = (line, ...details) => ({ line, details });
98978
+ function run(i, args) {
98979
+ return `\`powerplant ${args}${i.config.appEnv === "production" ? "" : ` --env ${i.config.appEnv}`}\``;
98980
+ }
98981
+ function renderStatus(i, out) {
98982
+ const stopping = syncProblems(i);
98983
+ const sections = [
98984
+ ...stopping.map((problem) => [problem]),
98985
+ machineBlock(i, stopping.length === 0),
98986
+ ...warnings(i).map((warning2) => [warning2]),
98987
+ i.connections.map((connection) => mcpBlock(i, connection)),
98988
+ [cliBlock(i, stopping.length === 0)]
98989
+ ];
98990
+ sections.filter((section) => section.length > 0).forEach((section, index) => {
98991
+ if (index > 0) out.log("");
98992
+ for (const { line, details } of section) {
98993
+ out.log(line);
98994
+ details.forEach(out.detail);
98995
+ }
98996
+ });
98900
98997
  }
98901
- function renderStatus(i) {
98902
- const lines = [];
98903
- if (i.config.appEnv !== "production") lines.push(`Environment: ${i.config.appEnv} (API ${i.config.apiUrl})`);
98998
+ function daemonStatus(i) {
98999
+ return "status" in i.daemon ? i.daemon.status : null;
99000
+ }
99001
+ function syncProblems(i) {
98904
99002
  if (i.credentialsError !== void 0) {
98905
- lines.push(`\u2717 ${i.credentialsError}`);
98906
- } else if (!i.credentials) {
98907
- lines.push("Not enrolled, run: powerplant init");
98908
- } else {
98909
- const orgLine = i.credentials.orgName ? ` into org "${i.credentials.orgName}"` : " (org not recorded, re-enroll to see it)";
98910
- lines.push(
98911
- `Enrolled as "${i.credentials.machineName}" (${i.credentials.machineId})${orgLine} on ${i.credentials.enrolledAt}.`
99003
+ return [block(`\u2717 ${i.credentialsError}`, `Run ${run(i, "init")} to enroll again.`)];
99004
+ }
99005
+ if (i.credentials === null) return [block("! Not enrolled", `Run ${run(i, "init")}.`)];
99006
+ const problems = [];
99007
+ if (i.control.paused) {
99008
+ problems.push(block(`! ${pausedSince(i.control, i.now)}`, `To resume sync, run ${run(i, "resume")}.`));
99009
+ }
99010
+ const service = serviceProblem(i);
99011
+ if (service !== null) problems.push(service);
99012
+ const status = i.daemon.state === "running" && !i.control.paused ? i.daemon.status : null;
99013
+ const lastPass = status?.lastPass;
99014
+ if (status && lastPass && !lastPass.ok) {
99015
+ const failures = status.consecutiveFailures;
99016
+ problems.push(
99017
+ block(
99018
+ `\u2717 Last sync failed, ${lastPass.error ?? "unknown error"}`,
99019
+ `${failures} failure${failures === 1 ? "" : "s"} in a row. Run ${run(i, "doctor")} for diagnostics.`
99020
+ )
98912
99021
  );
98913
- lines.push(`${describeStoredCaptureSummary(i.credentials)}.`);
98914
- for (const entry of i.protectedSessions ?? []) {
98915
- const sessions = `${entry.sessionCount} recent agent session${entry.sessionCount === 1 ? "" : "s"}`;
98916
- lines.push(
98917
- protectedRootReview(i.credentials, entry.root) === "blocked" ? `! macOS is blocking Powerplant from reading ${entry.displayRoot} (${sessions}). Those sessions are staying local. ${MACOS_BLOCKED_FOLDER_REMEDY}` : `! Repository matching is not enabled for ${sessions} under ${entry.displayRoot}. Sessions outside folders you explicitly added are staying local, run \`powerplant init\` to review Git repository scanning.`
98918
- );
98919
- }
98920
99022
  }
98921
- const paused = pausedNotice(i.control, i.resumeCommand, i.now);
98922
- if (paused !== null) lines.push(paused);
98923
- lines.push(automaticUpdatesLine(i.control));
99023
+ return problems;
99024
+ }
99025
+ function unavailableProbe(i) {
99026
+ return block(
99027
+ `! Background sync status unavailable${i.serviceError ? `, ${i.serviceError}` : ""}`,
99028
+ `Run ${run(i, "doctor")} for diagnostics.`
99029
+ );
99030
+ }
99031
+ function serviceProblem(i) {
98924
99032
  switch (i.service) {
98925
99033
  case "unavailable":
98926
- lines.push(`Background sync: status unavailable${i.serviceError ? `, ${i.serviceError}` : ""}. Run powerplant doctor${i.config.appEnv === "production" ? "" : ` --env ${i.config.appEnv}`} for diagnostics.`);
98927
99034
  break;
98928
99035
  case "unsupported":
98929
- lines.push(
98930
- "Background sync: unsupported on this platform (macOS-only for now), run `powerplant sync` on a schedule instead."
99036
+ return block(
99037
+ "! Background sync is not supported on this platform yet",
99038
+ `Run ${run(i, "sync")} on a schedule instead.`
98931
99039
  );
98932
- break;
98933
99040
  case "not-installed":
98934
- lines.push("Background sync: not installed, run: powerplant init");
98935
- break;
99041
+ return block("! Background sync is not installed", `Run ${run(i, "init")}.`);
98936
99042
  case "not-loaded":
98937
- lines.push("Background sync: installed but not loaded, run: powerplant restart");
98938
- break;
99043
+ return block("! Background sync is installed but not loaded", `Run ${run(i, "restart")}.`);
98939
99044
  case "loaded":
98940
- lines.push(...renderDaemon(i));
98941
99045
  break;
98942
99046
  }
98943
- lines.push(
98944
- `CLI: powerplant ${i.cliVersion}` + (isUpdateAvailable(i.cliVersion, i.latestVersion) ? `, ${i.latestVersion} is available: powerplant update` : "")
98945
- );
98946
- const unsupported = unsupportedVersionNotice(i.cliVersion, i.oldestSupportedVersion);
98947
- if (unsupported !== null) lines.push(unsupported);
98948
- return lines;
98949
- }
98950
- function renderDaemon(i) {
98951
- if (i.daemon.state === "unreadable") {
98952
- return [
98953
- "Background sync: loaded, but its status file is unreadable (written by a different version?), run: powerplant restart"
98954
- ];
98955
- }
98956
- if (i.daemon.state === "not-running") {
98957
- return [
98958
- `Background sync: loaded, but no background sync status is available, starting up, or running a version from before status reporting. If this persists: powerplant restart. Log: ${serviceLogPath(i.config.appEnv)}`
98959
- ];
99047
+ switch (i.daemon.state) {
99048
+ case "unreadable":
99049
+ return block(
99050
+ "! Background sync status file is unreadable",
99051
+ `A different version may have written it. Run ${run(i, "restart")}.`
99052
+ );
99053
+ case "not-running":
99054
+ if (i.service === "unavailable") return unavailableProbe(i);
99055
+ return block(
99056
+ "! Background sync has not reported its status",
99057
+ `It may still be starting. If this persists, run ${run(i, "restart")}.`,
99058
+ `The log is at ${serviceLogPath(i.config.appEnv)}.`
99059
+ );
99060
+ case "stalled":
99061
+ return block(`\u2717 Background sync stalled, no heartbeat for ${duration3(i.daemon.staleMs)}`, `Run ${run(i, "restart")}.`);
99062
+ case "running":
99063
+ return null;
98960
99064
  }
98961
- const status = i.daemon.status;
98962
- const lines = [];
98963
- if (i.daemon.state === "stalled") {
98964
- lines.push(
98965
- `\u2717 Background sync stalled, no heartbeat for ${duration3(i.daemon.staleMs)} (pid ${status.pid}). Run: powerplant restart`
98966
- );
98967
- } else {
98968
- lines.push(
98969
- `Background sync: running${i.control.paused ? ", syncing paused" : ""} (version ${status.version}, pid ${status.pid}, started ${status.startedAt}).`
99065
+ }
99066
+ function machineBlock(i, syncing) {
99067
+ const credentials = i.credentials;
99068
+ if (credentials === null) return [];
99069
+ const org = credentials.orgName;
99070
+ const line = syncing ? `\u2713 Syncing ${credentials.machineName}${org ? ` to ${org}` : ""}` : `${credentials.machineName}${org ? ` in ${org}` : ""}`;
99071
+ const details = [`${describeStoredCaptureSummary(credentials)}.`];
99072
+ const status = daemonStatus(i);
99073
+ if (status !== null) {
99074
+ const signals = describeSyncSignals(
99075
+ {
99076
+ lastSessionActivityAt: status.lastUploadAt ?? null,
99077
+ lastCheckedInAt: status.lastPass?.at ?? null,
99078
+ syncPaused: null,
99079
+ lastSyncErrorAt: status.lastPass && !status.lastPass.ok ? status.lastPass.at : null
99080
+ },
99081
+ new Date(i.now),
99082
+ "last upload unmeasured"
98970
99083
  );
99084
+ details.push(signals ?? "No sync has run since background sync started.");
98971
99085
  }
98972
- if (status.version !== i.cliVersion) {
98973
- lines.push(
98974
- ` The daemon is on ${status.version}, this CLI is ${i.cliVersion}, it restarts onto the installed version within minutes, or now via: powerplant restart`
99086
+ return [block(line, ...details)];
99087
+ }
99088
+ function warnings(i) {
99089
+ const found = [];
99090
+ if (isUnsupportedVersion(i.cliVersion, i.oldestSupportedVersion)) {
99091
+ found.push(
99092
+ block(
99093
+ `! powerplant ${i.cliVersion} is no longer supported, the oldest supported is ${i.oldestSupportedVersion}`,
99094
+ `Run ${run(i, "update")} (or npm install -g @usepowerplant/cli). Unsupported versions can stop working without notice.`
99095
+ )
98975
99096
  );
98976
99097
  }
98977
- const signals = describeSyncSignals(
98978
- {
98979
- lastSessionActivityAt: status.lastUploadAt ?? null,
98980
- lastCheckedInAt: status.lastPass?.at ?? null,
98981
- syncPaused: null,
98982
- lastSyncErrorAt: status.lastPass && !status.lastPass.ok ? status.lastPass.at : null
98983
- },
98984
- new Date(i.now),
98985
- "last upload unmeasured"
98986
- );
98987
- lines.push(signals === null ? " No pass has run yet in this run." : ` ${signals}`);
98988
- if (status.lastPass && !status.lastPass.ok) {
98989
- lines.push(
98990
- ` \u2717 Last pass failed: ${status.lastPass.error ?? "unknown error"} (${status.consecutiveFailures} consecutive failure${status.consecutiveFailures === 1 ? "" : "s"}).`
99098
+ for (const entry of i.protectedSessions ?? []) {
99099
+ const sessions = `${entry.sessionCount} recent agent session${entry.sessionCount === 1 ? "" : "s"}`;
99100
+ const staying = entry.sessionCount === 1 ? "is staying local" : "are staying local";
99101
+ found.push(
99102
+ i.credentials !== null && protectedRootReview(i.credentials, entry.root) === "blocked" ? block(
99103
+ `! macOS is blocking Powerplant from reading ${entry.displayRoot}`,
99104
+ `${sessions} ${staying}. ${MACOS_BLOCKED_FOLDER_REMEDY}`
99105
+ ) : block(
99106
+ `! ${sessions} under ${entry.displayRoot} ${staying}`,
99107
+ `Run ${run(i, "init")} to review Git repository scanning.`
99108
+ )
98991
99109
  );
98992
99110
  }
98993
- for (const hook of status.hooks ?? []) {
99111
+ if (i.service === "unavailable" && i.daemon.state !== "not-running") found.push(unavailableProbe(i));
99112
+ if (i.mcpPreferencesError !== void 0) found.push(block("! MCP choices can't be read", i.mcpPreferencesError));
99113
+ for (const hook of daemonStatus(i)?.hooks ?? []) {
98994
99114
  const silence = hookWarning(hook, i.now);
98995
- if (silence) lines.push(` ! ${silence.problem}. ${silence.fix}`);
99115
+ if (silence) found.push(block(`! ${silence.problem}`, silence.fix));
98996
99116
  }
98997
- return lines;
99117
+ return found;
99118
+ }
99119
+ function mcpBlock(i, connection) {
99120
+ const name = `${AGENT_DISPLAY_NAMES[connection.client]}${connection.target ? ` (${connection.target})` : ""}`;
99121
+ const fix = `Run ${run(i, `connect --client ${connection.client}`)}.`;
99122
+ const current = connectionIsCurrent(connection, i.now);
99123
+ const checked = current ? "" : `, checked ${ago(i.now - Date.parse(connection.checkedAt))}`;
99124
+ switch (connection.state) {
99125
+ case "signed-in":
99126
+ return block(`${current ? "\u2713 " : ""}${name}: MCP connected${checked}`);
99127
+ case "sign-in-needed":
99128
+ return block(`! ${name}: MCP sign-in needed${checked}`, fix);
99129
+ case "different-url":
99130
+ return block(
99131
+ `! ${name}: MCP points at a different Powerplant${connection.registeredUrl ? `, ${connection.registeredUrl}` : ""}`,
99132
+ fix
99133
+ );
99134
+ case "not-connected":
99135
+ return i.mcpPreferences?.clients[connection.client]?.optedIn === true ? block(`! ${name}: MCP not connected`, fix) : block(`${name}: MCP not connected`);
99136
+ case "configured":
99137
+ return block(`${name}: MCP configured, sign-in unknown`);
99138
+ case "check-failed":
99139
+ return block(`${name}: MCP check failed${connection.detail ? `, ${connection.detail}` : ""}`);
99140
+ }
99141
+ }
99142
+ function cliBlock(i, syncing) {
99143
+ const details = [];
99144
+ if (i.daemon.state === "running" && i.daemon.status.version !== i.cliVersion) {
99145
+ details.push(
99146
+ `Background sync is on ${i.daemon.status.version}. It restarts onto the installed version within minutes, or now with ${run(i, "restart")}.`
99147
+ );
99148
+ }
99149
+ const automatic = automaticUpdatesChoice(i.control);
99150
+ const version2 = `powerplant ${i.cliVersion}`;
99151
+ if (isUpdateAvailable(i.cliVersion, i.latestVersion) && !isUnsupportedVersion(i.cliVersion, i.oldestSupportedVersion)) {
99152
+ return block(`${version2}, ${i.latestVersion} is available`, ...details, `Run ${run(i, "update")}.`);
99153
+ }
99154
+ if (automatic === "not-chosen" && syncing) {
99155
+ details.push(`Turn them on with ${run(i, "update --automatic on")}.`);
99156
+ }
99157
+ return block(`${version2}, automatic updates ${automatic === "on" ? "on" : "off"}`, ...details);
98998
99158
  }
98999
99159
  function statusJson(i) {
99000
99160
  return {
@@ -99032,7 +99192,9 @@ function statusJson(i) {
99032
99192
  daemon: "status" in i.daemon ? { state: i.daemon.state, ...i.daemon.status } : { state: i.daemon.state },
99033
99193
  paused: i.control.paused,
99034
99194
  automaticUpdates: automaticUpdatesChoice(i.control),
99035
- pausedAt: i.control.pausedAt ?? null
99195
+ pausedAt: i.control.pausedAt ?? null,
99196
+ // The same saved checks the human rendering reads, each with when it ran.
99197
+ mcpConnections: i.connections
99036
99198
  };
99037
99199
  }
99038
99200
  function supportedFlag(cliVersion2, oldestSupportedVersion) {
@@ -99194,7 +99356,7 @@ function createTerminalOutput(options) {
99194
99356
 
99195
99357
  // src/credentialUpgrade.ts
99196
99358
  import { spawn as spawn3 } from "node:child_process";
99197
- import { closeSync as closeSync9, mkdirSync as mkdirSync18, openSync as openSync9 } from "node:fs";
99359
+ import { closeSync as closeSync10, mkdirSync as mkdirSync18, openSync as openSync10 } from "node:fs";
99198
99360
  import path35 from "node:path";
99199
99361
  async function prepareCredentialUpgrade(deps) {
99200
99362
  if (!deps.needsMigration() || !deps.serviceExists() || deps.ready()) return "ready";
@@ -99229,7 +99391,7 @@ async function prepareCredentialUpgrade(deps) {
99229
99391
  }
99230
99392
  async function tryCredentialUpgradeLock(file2) {
99231
99393
  mkdirSync18(path35.dirname(file2), { recursive: true, mode: 448 });
99232
- closeSync9(openSync9(file2, "a", 384));
99394
+ closeSync10(openSync10(file2, "a", 384));
99233
99395
  const child = spawn3("/usr/bin/lockf", ["-k", "-s", "-t", "0", file2, "/bin/sh", "-c", "printf 'ready\\n'; exec /bin/cat >/dev/null"], {
99234
99396
  detached: true,
99235
99397
  stdio: ["pipe", "pipe", "ignore"]
@@ -99969,12 +100131,12 @@ The access you granted these agents stays on your Powerplant account until you r
99969
100131
  latestVersion,
99970
100132
  oldestSupportedVersion,
99971
100133
  protectedSessions,
99972
- resumeCommand: pause.resumeCommand,
100134
+ connections: readHarnessConnections(config2.appEnv, realReadTextFile),
100135
+ ...readMcpPreferencesForMenu(config2.appEnv),
99973
100136
  now: Date.now()
99974
100137
  };
99975
- console.log(
99976
- values.json ? JSON.stringify(statusJson(inputs), null, 2) : renderStatus(inputs).map(styledFor(process.stdout)).join("\n")
99977
- );
100138
+ if (values.json) console.log(JSON.stringify(statusJson(inputs), null, 2));
100139
+ else renderStatus(inputs, terminal.command);
99978
100140
  return;
99979
100141
  }
99980
100142
  case "doctor": {
@@ -100309,7 +100471,7 @@ function startBackgroundUpdate(config2, target) {
100309
100471
  writeProgress: updateProgress.write,
100310
100472
  spawnChild: () => {
100311
100473
  mkdirSync20(path37.dirname(updateLogPath()), { recursive: true });
100312
- const log3 = openSync10(updateLogPath(), "a");
100474
+ const log3 = openSync11(updateLogPath(), "a");
100313
100475
  const child = spawn4(process.execPath, [entryPath(), "update", ...envArgsFor(config2)], {
100314
100476
  detached: true,
100315
100477
  stdio: ["ignore", log3, log3],
@@ -100423,4 +100585,4 @@ undici/lib/web/websocket/frame.js:
100423
100585
  (*! ws. MIT License. Einar Otto Stangvik <einaros@gmail.com> *)
100424
100586
  */
100425
100587
 
100426
- //# debugId=9ee35b05-7453-5338-85ff-5dc1b6010221
100588
+ //# debugId=91b4b5e6-fdd5-5127-9372-913ad2bc7af5
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usepowerplant/cli",
3
- "version": "0.4.21",
3
+ "version": "0.4.22",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },