@usepowerplant/cli 0.3.2 → 0.4.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/cli.js +432 -104
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -50833,6 +50833,17 @@ function capturedSessionEvents(turns, anchor) {
50833
50833
  return events;
50834
50834
  }
50835
50835
 
50836
+ // ../core/src/workspaceClaims.ts
50837
+ function workspaceClaimsRepo(repoFullName, policy) {
50838
+ const repo = repoFullName.toLowerCase();
50839
+ if (policy.repos !== null) return policy.repos.includes(repo);
50840
+ const owner = repo.split("/")[0] ?? "";
50841
+ return policy.owners.length > 0 && policy.owners.includes(owner);
50842
+ }
50843
+ function workspaceClaimsAnyRepo(policy) {
50844
+ return policy.repos !== null ? policy.repos.length > 0 : policy.owners.length > 0;
50845
+ }
50846
+
50836
50847
  // ../core/src/transcriptImages.ts
50837
50848
  var IMAGE_DATA_URI_RE = /^data:(image\/[\w.+-]+);base64,([A-Za-z0-9+/=_-]*)$/;
50838
50849
  function isImageDataUri(value) {
@@ -51693,6 +51704,8 @@ var SESSION_SOURCES = [
51693
51704
  "cursor-cloud",
51694
51705
  "github-pr"
51695
51706
  ];
51707
+ var SESSION_SPEED_MODES = ["standard", "fast"];
51708
+ var SESSION_SPEED_OBSERVATIONS = [...SESSION_SPEED_MODES, "unknown"];
51696
51709
  function isSessionSource(source) {
51697
51710
  return typeof source === "string" && SESSION_SOURCES.includes(source);
51698
51711
  }
@@ -78558,6 +78571,23 @@ var storedCredentialsSchema = external_exports.object({
78558
78571
  * alongside the owners. Absent = the org has no list — the owner check
78559
78572
  * decides (and pre-list enrollments read the same way). */
78560
78573
  allowedRepos: external_exports.array(external_exports.string()).optional(),
78574
+ /** org-repos mode: per-workspace claim policies across every workspace
78575
+ * the member belongs to (home first), refreshed every sync — the union
78576
+ * an offline pass still filters with. Absent = cached before routing;
78577
+ * the flat fields above are the whole boundary. */
78578
+ workspacePolicies: external_exports.array(
78579
+ external_exports.object({
78580
+ orgId: external_exports.string().min(1),
78581
+ name: external_exports.string(),
78582
+ owners: external_exports.array(external_exports.string()),
78583
+ repos: external_exports.array(external_exports.string()).nullable(),
78584
+ /** This entry's claims were UNRESOLVED when cached (a linked
78585
+ * installation whose account failed to resolve, nothing usable to
78586
+ * stand in) — unknown, never "claims nothing". A pass reading only
78587
+ * the cache must not stamp a deep sweep done over it. */
78588
+ unresolved: external_exports.literal(true).optional()
78589
+ })
78590
+ ).optional(),
78561
78591
  /** Folders this member explicitly chose. Under scope mode they are the
78562
78592
  * whole boundary (never empty — enrollment requires at least one); under
78563
78593
  * org-repos they are additions on top of the org policy
@@ -78652,7 +78682,7 @@ import os6 from "node:os";
78652
78682
  // ../../apps/collector/src/http.ts
78653
78683
  var import_undici = __toESM(require_undici(), 1);
78654
78684
  var CLI_VERSION_HEADER = "x-powerplant-cli-version";
78655
- var cliVersion = true ? "0.3.2" : "0.0.0-checkout";
78685
+ var cliVersion = true ? "0.4.0" : "0.0.0-checkout";
78656
78686
  function cliVersionHeaders() {
78657
78687
  return { [CLI_VERSION_HEADER]: cliVersion, "user-agent": `powerplant-cli/${cliVersion}` };
78658
78688
  }
@@ -78917,9 +78947,10 @@ function captureDecision(cwd, policy) {
78917
78947
  }
78918
78948
  const repoFullName = policy.repoOf(cwd);
78919
78949
  if (repoFullName === null) return { capture: false, reason: "no-github-repo" };
78920
- const repo = repoFullName.toLowerCase();
78921
- const admitted = policy.allowedRepos !== null ? policy.allowedRepos.includes(repo) : policy.allowedOwners.length > 0 && policy.allowedOwners.includes(ownerOfRepo(repo));
78922
- return admitted ? { capture: true, via: "org-policy", repoFullName } : { capture: false, reason: "outside-policy" };
78950
+ const claims = policy.workspaceClaims ?? [
78951
+ { owners: policy.allowedOwners, repos: policy.allowedRepos }
78952
+ ];
78953
+ return claims.some((workspace) => workspaceClaimsRepo(repoFullName, workspace)) ? { capture: true, via: "org-policy", repoFullName } : { capture: false, reason: "outside-policy" };
78923
78954
  }
78924
78955
  }
78925
78956
  }
@@ -78931,9 +78962,6 @@ function memberChoice(cwd, policy) {
78931
78962
  ...repoFullName !== null && { repoFullName }
78932
78963
  };
78933
78964
  }
78934
- function ownerOfRepo(repoFullName) {
78935
- return repoFullName.split("/")[0] ?? "";
78936
- }
78937
78965
  function listSome(values, max = 6) {
78938
78966
  return values.length <= max ? values.join(", ") : `${values.slice(0, max).join(", ")} and ${values.length - max} more`;
78939
78967
  }
@@ -78943,7 +78971,10 @@ function describeCapturePolicy(policy) {
78943
78971
  return `sessions under ${policy.scopePaths.join(", ")}`;
78944
78972
  case "org-repos": {
78945
78973
  const repoPart = policy.allowedRepos !== null ? policy.allowedRepos.length > 0 ? `sessions in the org's allowlisted repos: ${listSome(policy.allowedRepos)}` : "the org's repo allowlist is empty \u2014 no repo-based uploads" : policy.allowedOwners.length > 0 ? `sessions in GitHub repos owned by ${policy.allowedOwners.join(", ")}` : "org-owned GitHub repos \u2014 but the org has no GitHub integration, so nothing uploads";
78946
- return policy.memberFolders.length > 0 ? `${repoPart}, plus folders you added: ${policy.memberFolders.join(", ")}` : repoPart;
78974
+ const claiming = (policy.workspaceClaims ?? []).filter(workspaceClaimsAnyRepo);
78975
+ const severalWorkspaces = (policy.workspaceClaims?.length ?? 0) > 1;
78976
+ const withOthers = severalWorkspaces && claiming.length > 0 ? `${repoPart}, routing each repo to the workspace that syncs it (${listSome(claiming.map((workspace) => workspace.name))})` : repoPart;
78977
+ return policy.memberFolders.length > 0 ? `${withOthers}, plus folders you added: ${policy.memberFolders.join(", ")}` : withOthers;
78947
78978
  }
78948
78979
  }
78949
78980
  }
@@ -78959,6 +78990,9 @@ function describeStoredCapture(credentials) {
78959
78990
  mode: "org-repos",
78960
78991
  allowedOwners: credentials.allowedOwners ?? [],
78961
78992
  allowedRepos: credentials.allowedRepos ?? null,
78993
+ ...credentials.workspacePolicies !== void 0 && {
78994
+ workspaceClaims: credentials.workspacePolicies
78995
+ },
78962
78996
  memberFolders: credentials.scopePaths ?? [],
78963
78997
  repoOf: () => null
78964
78998
  });
@@ -79083,7 +79117,16 @@ async function fetchCapturePolicy(apiUrl, machineToken) {
79083
79117
  data: external_exports.object({
79084
79118
  owners: external_exports.array(external_exports.string()),
79085
79119
  repos: external_exports.array(external_exports.string()).nullable(),
79086
- integration: external_exports.enum(["none", "linked"])
79120
+ integration: external_exports.enum(["none", "linked"]),
79121
+ workspaces: external_exports.array(
79122
+ external_exports.object({
79123
+ orgId: external_exports.string().min(1),
79124
+ name: external_exports.string(),
79125
+ owners: external_exports.array(external_exports.string()),
79126
+ repos: external_exports.array(external_exports.string()).nullable(),
79127
+ integration: external_exports.enum(["none", "linked"])
79128
+ })
79129
+ ).optional()
79087
79130
  })
79088
79131
  }),
79089
79132
  await response.json().catch(() => null),
@@ -79095,6 +79138,7 @@ async function uploadSessions(apiUrl, machineToken, items) {
79095
79138
  let upserted = 0;
79096
79139
  let unchanged = 0;
79097
79140
  let outOfPolicy = 0;
79141
+ let deferred = 0;
79098
79142
  for (let offset = 0; offset < items.length; offset += MAX_INGEST_BATCH) {
79099
79143
  const batch = items.slice(offset, offset + MAX_INGEST_BATCH);
79100
79144
  const response = await fetchWithRetry("uploading session metadata", `${apiUrl}/api/ingest/sessions`, {
@@ -79113,7 +79157,8 @@ async function uploadSessions(apiUrl, machineToken, items) {
79113
79157
  data: external_exports.object({
79114
79158
  upserted: external_exports.number(),
79115
79159
  unchanged: external_exports.number(),
79116
- outOfPolicy: external_exports.number()
79160
+ outOfPolicy: external_exports.number(),
79161
+ deferred: external_exports.number().optional()
79117
79162
  })
79118
79163
  }),
79119
79164
  await response.json().catch(() => null),
@@ -79122,8 +79167,9 @@ async function uploadSessions(apiUrl, machineToken, items) {
79122
79167
  upserted += result.data.upserted;
79123
79168
  unchanged += result.data.unchanged;
79124
79169
  outOfPolicy += result.data.outOfPolicy;
79170
+ deferred += result.data.deferred ?? 0;
79125
79171
  }
79126
- return { upserted, unchanged, outOfPolicy };
79172
+ return { upserted, unchanged, outOfPolicy, deferred };
79127
79173
  }
79128
79174
  async function uploadBranchFacts(apiUrl, machineToken, facts) {
79129
79175
  let recorded = 0;
@@ -79173,6 +79219,7 @@ async function uploadRawSessions(apiUrl, machineToken, collected, readTranscript
79173
79219
  uploaded: 0,
79174
79220
  skippedTooLarge: 0,
79175
79221
  skippedUnchanged: 0,
79222
+ missingMetadata: 0,
79176
79223
  failed: [],
79177
79224
  unconfigured: false
79178
79225
  };
@@ -79205,6 +79252,10 @@ async function uploadRawSessions(apiUrl, machineToken, collected, readTranscript
79205
79252
  summary.unconfigured = true;
79206
79253
  return summary;
79207
79254
  }
79255
+ if (response.status === 404) {
79256
+ summary.missingMetadata += 1;
79257
+ continue;
79258
+ }
79208
79259
  if (!response.ok) {
79209
79260
  throw new Error(`${response.status}: ${await errorDetail(response)}`);
79210
79261
  }
@@ -79288,7 +79339,8 @@ async function runEnrollment(config2, options) {
79288
79339
  log: log3
79289
79340
  });
79290
79341
  let orgPolicy = captureMode === "org-repos" ? await fetchCapturePolicy(config2.apiUrl, result.token) : void 0;
79291
- if (orgPolicy !== void 0 && orgPolicy.owners.length === 0 && orgPolicy.repos === null) {
79342
+ const anyWorkspaceClaims = (orgPolicy?.workspaces ?? []).some(workspaceClaimsAnyRepo);
79343
+ if (orgPolicy !== void 0 && orgPolicy.owners.length === 0 && orgPolicy.repos === null && !anyWorkspaceClaims) {
79292
79344
  if (config2.appEnv === "development") {
79293
79345
  const home = normalizeScopePath(os6.homedir());
79294
79346
  log3(
@@ -79320,10 +79372,20 @@ async function runEnrollment(config2, options) {
79320
79372
  );
79321
79373
  }
79322
79374
  }
79375
+ const workspacePolicies = orgPolicy?.workspaces?.map((workspace) => ({
79376
+ orgId: workspace.orgId,
79377
+ name: workspace.name,
79378
+ owners: workspace.owners,
79379
+ repos: workspace.repos,
79380
+ // Cached unknownness survives enrollment: an unresolved workspace's
79381
+ // claims are unknown, and the first sweep must not stamp over them.
79382
+ ...workspace.integration === "linked" && workspace.owners.length === 0 && workspace.repos === null && { unresolved: true }
79383
+ }));
79323
79384
  const policy = captureMode === "org-repos" ? {
79324
79385
  mode: "org-repos",
79325
79386
  allowedOwners: orgPolicy?.owners ?? [],
79326
79387
  allowedRepos: orgPolicy?.repos ?? null,
79388
+ ...workspacePolicies !== void 0 && { workspaceClaims: workspacePolicies },
79327
79389
  memberFolders: [],
79328
79390
  repoOf: () => null
79329
79391
  } : { mode: "scope", scopePaths, repoOf: () => null };
@@ -79338,6 +79400,7 @@ async function runEnrollment(config2, options) {
79338
79400
  captureMode,
79339
79401
  ...orgPolicy && { allowedOwners: orgPolicy.owners },
79340
79402
  ...orgPolicy?.repos != null && { allowedRepos: orgPolicy.repos },
79403
+ ...captureMode === "org-repos" && workspacePolicies !== void 0 && { workspacePolicies },
79341
79404
  scopePaths
79342
79405
  };
79343
79406
  const location = saveCredentials(config2.appEnv, credentials);
@@ -79705,6 +79768,7 @@ function parseClaudeCodeTranscript(text) {
79705
79768
  let cwd;
79706
79769
  let gitBranch;
79707
79770
  let anonCounter = 0;
79771
+ let speedMode;
79708
79772
  for (const record2 of parseJsonl(text)) {
79709
79773
  if (record2.type === "custom-title" && typeof record2.customTitle === "string") {
79710
79774
  title = record2.customTitle;
@@ -79723,25 +79787,42 @@ function parseClaudeCodeTranscript(text) {
79723
79787
  const message = record2.message;
79724
79788
  if (!message || message.model === "<synthetic>") continue;
79725
79789
  const id = message.id ?? (typeof record2.requestId === "string" ? record2.requestId : void 0) ?? (typeof record2.uuid === "string" ? record2.uuid : void 0) ?? `anon-${anonCounter++}`;
79726
- const isNewMessage = !usageByMessage.has(id);
79727
- if (message.usage) usageByMessage.set(id, toUsage(message.usage));
79728
- else if (isNewMessage) usageByMessage.set(id, { input: 0, cacheRead: 0, cacheCreation: 0, output: 0 });
79790
+ const previousMessage = usageByMessage.get(id);
79791
+ const isNewMessage = previousMessage === void 0;
79792
+ const speedValue = message.usage?.speed;
79793
+ const observedSpeed = speedModeOf(speedValue);
79794
+ const messageSpeed = observedSpeed ?? previousMessage?.speedMode;
79795
+ if (speedValue !== void 0) speedMode = observedSpeed ?? "unknown";
79796
+ if (message.usage) {
79797
+ usageByMessage.set(id, {
79798
+ usage: toUsage(message.usage),
79799
+ ...messageSpeed && { speedMode: messageSpeed }
79800
+ });
79801
+ } else if (isNewMessage) {
79802
+ usageByMessage.set(id, {
79803
+ usage: { input: 0, cacheRead: 0, cacheCreation: 0, output: 0 }
79804
+ });
79805
+ }
79729
79806
  if (isNewMessage && typeof message.model === "string") {
79730
79807
  modelCounts.set(message.model, (modelCounts.get(message.model) ?? 0) + 1);
79731
79808
  }
79732
79809
  }
79733
79810
  if (usageByMessage.size === 0 && timestamps.length === 0) return null;
79734
- const summed = [...usageByMessage.values()].reduce(addUsage, {
79811
+ const summed = [...usageByMessage.values()].map((entry) => entry.usage).reduce(addUsage, {
79735
79812
  input: 0,
79736
79813
  cacheRead: 0,
79737
79814
  cacheCreation: 0,
79738
79815
  output: 0
79739
79816
  });
79817
+ const speedFullyMeasured = usageByMessage.size > 0 && [...usageByMessage.values()].every((entry) => entry.speedMode !== void 0);
79818
+ const fastUsage = speedFullyMeasured ? [...usageByMessage.values()].filter((entry) => entry.speedMode === "fast").map((entry) => entry.usage).reduce(addUsage, { input: 0, cacheRead: 0, cacheCreation: 0, output: 0 }) : null;
79740
79819
  return {
79741
79820
  startedAt: timestamps[0],
79742
79821
  endedAt: timestamps[timestamps.length - 1],
79743
79822
  model: dominantModel(modelCounts),
79744
79823
  usage: usageByMessage.size > 0 ? summed : null,
79824
+ fastUsage,
79825
+ ...speedMode && { speedMode },
79745
79826
  title,
79746
79827
  turnCount: usageByMessage.size,
79747
79828
  cwd,
@@ -79752,9 +79833,13 @@ async function collectClaudeCodeSessions(options = {}) {
79752
79833
  const claudeDir = options.claudeDir ?? path5.join(os7.homedir(), ".claude");
79753
79834
  const now = options.now ?? Date.now();
79754
79835
  const capture = options.capture ?? captureEverythingUnresolved;
79836
+ const maxBytes = options.maxTranscriptBytes ?? MAX_TRANSCRIPT_FILE_BYTES;
79755
79837
  const projectsDir = path5.join(claudeDir, "projects");
79756
79838
  const items = [];
79757
79839
  const skips = { skippedOutOfScope: 0, skippedNoRepo: 0 };
79840
+ const candidates = [];
79841
+ const unreadable = [];
79842
+ let skippedTooLarge = 0;
79758
79843
  const home = path5.dirname(claudeDir);
79759
79844
  const lifecycle = options.lifecycle ?? readLifecycleJournal(home);
79760
79845
  for (const projectDir of listDirs(projectsDir)) {
@@ -79764,39 +79849,76 @@ async function collectClaudeCodeSessions(options = {}) {
79764
79849
  const stat = statIfExists(transcriptPath);
79765
79850
  if (!stat) continue;
79766
79851
  if (options.sinceMs !== void 0 && stat.mtimeMs < options.sinceMs) continue;
79852
+ if (stat.size > maxBytes) {
79853
+ skippedTooLarge += 1;
79854
+ continue;
79855
+ }
79767
79856
  const sessionId = path5.basename(entry, ".jsonl");
79768
79857
  const endObservedAt = observedSessionEnd(lifecycle.get(sessionId), stat.mtimeMs);
79769
79858
  const status = endObservedAt === null && now - stat.mtimeMs < ACTIVE_GRACE_MS ? "active" : "inactive";
79770
- if (options.locationsOnly) {
79859
+ try {
79771
79860
  const cwd = transcriptCwd(transcriptPath);
79772
- const decision2 = captureDecision(cwd, capture);
79773
- if (!decision2.capture) {
79774
- countCaptureSkip(skips, decision2.reason);
79861
+ const decision = captureDecision(cwd, capture);
79862
+ if (!decision.capture) {
79863
+ countCaptureSkip(skips, decision.reason);
79775
79864
  continue;
79776
79865
  }
79777
- items.push({
79778
- mtimeMs: stat.mtimeMs,
79866
+ if (options.locationsOnly) {
79867
+ items.push({
79868
+ mtimeMs: stat.mtimeMs,
79869
+ transcriptPath,
79870
+ ...cwd && { cwd },
79871
+ item: { source: "claude-code", externalId: sessionId, status, capturedVia: decision.via }
79872
+ });
79873
+ continue;
79874
+ }
79875
+ candidates.push({
79779
79876
  transcriptPath,
79877
+ projectDir,
79878
+ sessionId,
79879
+ mtimeMs: stat.mtimeMs,
79880
+ status,
79881
+ endObservedAt,
79780
79882
  ...cwd && { cwd },
79781
- item: { source: "claude-code", externalId: sessionId, status, capturedVia: decision2.via }
79883
+ decision
79782
79884
  });
79783
- continue;
79885
+ } catch (error52) {
79886
+ unreadable.push(`${sessionId}: ${describeReadError(error52)}`);
79784
79887
  }
79888
+ }
79889
+ }
79890
+ if (options.locationsOnly) {
79891
+ return {
79892
+ sessions: items.sort((a, b2) => b2.mtimeMs - a.mtimeMs),
79893
+ ...skips,
79894
+ skippedTooLarge,
79895
+ unreadable,
79896
+ heldBack: 0
79897
+ };
79898
+ }
79899
+ candidates.sort((a, b2) => b2.mtimeMs - a.mtimeMs);
79900
+ const chosen = options.maxSessions === void 0 ? candidates : candidates.slice(0, options.maxSessions);
79901
+ for (const candidate of chosen) {
79902
+ await yieldToEventLoop();
79903
+ const { transcriptPath, projectDir, sessionId, mtimeMs, status, endObservedAt, cwd, decision } = candidate;
79904
+ try {
79785
79905
  const text = readIfExists2(transcriptPath);
79786
79906
  if (text === null) continue;
79787
79907
  const summary = parseClaudeCodeTranscript(text);
79788
79908
  if (!summary) continue;
79789
- const decision = captureDecision(summary.cwd, capture);
79790
- if (!decision.capture) {
79791
- countCaptureSkip(skips, decision.reason);
79792
- continue;
79793
- }
79794
- const usage = addSubagentUsage(summary.usage, path5.join(projectDir, sessionId, "subagents"));
79909
+ const usage = addSubagentUsage(
79910
+ summary.usage,
79911
+ summary.fastUsage,
79912
+ path5.join(projectDir, sessionId, "subagents"),
79913
+ { maxBytes, sessionId }
79914
+ );
79915
+ skippedTooLarge += usage.skippedTooLarge;
79916
+ unreadable.push(...usage.unreadable);
79795
79917
  const endedAt = endObservedAt ?? summary.endedAt;
79796
79918
  items.push({
79797
- mtimeMs: stat.mtimeMs,
79919
+ mtimeMs,
79798
79920
  transcriptPath,
79799
- ...summary.cwd && { cwd: summary.cwd },
79921
+ ...cwd && { cwd },
79800
79922
  item: {
79801
79923
  source: "claude-code",
79802
79924
  externalId: sessionId,
@@ -79804,28 +79926,42 @@ async function collectClaudeCodeSessions(options = {}) {
79804
79926
  ...summary.startedAt && { startedAt: summary.startedAt },
79805
79927
  ...endedAt && { endedAt },
79806
79928
  ...summary.model && { model: summary.model },
79929
+ ...summary.speedMode && { speedMode: summary.speedMode },
79807
79930
  // Total input-class tokens the source reported (uncached + cache
79808
79931
  // reads + cache writes) — a real reported total, not an estimate.
79809
79932
  // The cache breakdown rides along: pricing bills reads at 0.1×
79810
79933
  // and writes at 1.25×, so collapsing it would inflate estimates.
79811
- ...usage && { inputTokens: usage.input + usage.cacheRead + usage.cacheCreation },
79812
- ...usage && { outputTokens: usage.output },
79813
- ...usage && { cacheReadTokens: usage.cacheRead },
79814
- ...usage && { cacheWriteTokens: usage.cacheCreation },
79934
+ ...usage.total && {
79935
+ inputTokens: usage.total.input + usage.total.cacheRead + usage.total.cacheCreation
79936
+ },
79937
+ ...usage.total && { outputTokens: usage.total.output },
79938
+ ...usage.total && { cacheReadTokens: usage.total.cacheRead },
79939
+ ...usage.total && { cacheWriteTokens: usage.total.cacheCreation },
79940
+ ...usage.fast && {
79941
+ fastInputTokens: usage.fast.input + usage.fast.cacheRead + usage.fast.cacheCreation,
79942
+ fastOutputTokens: usage.fast.output,
79943
+ fastCacheReadTokens: usage.fast.cacheRead,
79944
+ fastCacheWriteTokens: usage.fast.cacheCreation
79945
+ },
79815
79946
  ...summary.title && { title: redactText(summary.title).slice(0, 500) },
79816
79947
  // The PR-join coordinates: the checkout's directory name (bare
79817
79948
  // repo, not the local path) and the branch the work started on.
79818
- ...summary.cwd && { repoDir: path5.basename(summary.cwd) },
79949
+ ...cwd && { repoDir: path5.basename(cwd) },
79819
79950
  ...summary.gitBranch && { gitBranch: summary.gitBranch },
79820
79951
  ...decision.repoFullName && { repoFullName: decision.repoFullName },
79821
79952
  capturedVia: decision.via
79822
79953
  }
79823
79954
  });
79955
+ } catch (error52) {
79956
+ unreadable.push(`${sessionId}: ${describeReadError(error52)}`);
79824
79957
  }
79825
79958
  }
79826
79959
  return {
79827
79960
  sessions: items.sort((a, b2) => b2.mtimeMs - a.mtimeMs),
79828
- ...skips
79961
+ ...skips,
79962
+ skippedTooLarge,
79963
+ unreadable,
79964
+ heldBack: candidates.length - chosen.length
79829
79965
  };
79830
79966
  }
79831
79967
  function transcriptCwd(transcriptPath) {
@@ -79844,6 +79980,9 @@ function toUsage(u) {
79844
79980
  output: n(u.output_tokens)
79845
79981
  };
79846
79982
  }
79983
+ function speedModeOf(value) {
79984
+ return value === "fast" || value === "standard" ? value : void 0;
79985
+ }
79847
79986
  function addUsage(a, b2) {
79848
79987
  return {
79849
79988
  input: a.input + b2.input,
@@ -79852,15 +79991,36 @@ function addUsage(a, b2) {
79852
79991
  output: a.output + b2.output
79853
79992
  };
79854
79993
  }
79855
- function addSubagentUsage(main2, subagentsDir) {
79856
- let usage = main2;
79994
+ function addSubagentUsage(main2, mainFast, subagentsDir, bounds) {
79995
+ let total = main2;
79996
+ let fast = mainFast;
79997
+ let fastCoverageComplete = main2 === null || mainFast !== null;
79998
+ let skippedTooLarge = 0;
79999
+ const unreadable = [];
79857
80000
  for (const entry of listFiles(subagentsDir, ".jsonl")) {
79858
- const text = readIfExists2(path5.join(subagentsDir, entry));
79859
- if (text === null) continue;
79860
- const summary = parseClaudeCodeTranscript(text);
79861
- if (summary?.usage) usage = addUsage(usage ?? { input: 0, cacheRead: 0, cacheCreation: 0, output: 0 }, summary.usage);
80001
+ const sidecarPath = path5.join(subagentsDir, entry);
80002
+ try {
80003
+ const stat = statIfExists(sidecarPath);
80004
+ if (stat === null) continue;
80005
+ if (stat.size > bounds.maxBytes) {
80006
+ skippedTooLarge += 1;
80007
+ continue;
80008
+ }
80009
+ const text = readIfExists2(sidecarPath);
80010
+ if (text === null) continue;
80011
+ const summary = parseClaudeCodeTranscript(text);
80012
+ if (summary?.usage) {
80013
+ total = addUsage(total ?? { input: 0, cacheRead: 0, cacheCreation: 0, output: 0 }, summary.usage);
80014
+ if (summary.fastUsage === null) fastCoverageComplete = false;
80015
+ }
80016
+ if (summary?.fastUsage) {
80017
+ fast = addUsage(fast ?? { input: 0, cacheRead: 0, cacheCreation: 0, output: 0 }, summary.fastUsage);
80018
+ }
80019
+ } catch (error52) {
80020
+ unreadable.push(`${bounds.sessionId} (subagent ${entry}): ${describeReadError(error52)}`);
80021
+ }
79862
80022
  }
79863
- return usage;
80023
+ return { total, fast: fastCoverageComplete ? fast : null, skippedTooLarge, unreadable };
79864
80024
  }
79865
80025
  function dominantModel(counts) {
79866
80026
  let best;
@@ -79879,6 +80039,10 @@ function parseCodexRollout(text) {
79879
80039
  let meta3 = null;
79880
80040
  let model;
79881
80041
  let totals = null;
80042
+ let fastTotals = { input: 0, cached: 0, output: 0 };
80043
+ let speedMode;
80044
+ let fastCoverageComplete = true;
80045
+ let classifiedUsage = false;
79882
80046
  const timestamps = [];
79883
80047
  for (const line of parseJsonl(text)) {
79884
80048
  if (typeof line.timestamp === "string") timestamps.push(line.timestamp);
@@ -79889,15 +80053,38 @@ function parseCodexRollout(text) {
79889
80053
  meta3 ??= record2;
79890
80054
  } else if (line.type === "turn_context" && typeof record2.model === "string") {
79891
80055
  model = record2.model;
80056
+ } else if (line.type === "event_msg" && record2.type === "thread_settings_applied") {
80057
+ const settings = record2.thread_settings;
80058
+ if (settings && typeof settings === "object" && !Array.isArray(settings)) {
80059
+ const tier = settings.service_tier;
80060
+ if (tier !== void 0) speedMode = codexSpeedMode(tier) ?? "unknown";
80061
+ }
79892
80062
  } else if (line.type === "event_msg" && record2.type === "token_count") {
79893
80063
  const info = record2.info;
79894
80064
  const usage = info?.total_token_usage;
79895
80065
  if (usage) {
79896
80066
  const n = (value) => typeof value === "number" ? value : 0;
79897
- totals ??= { input: 0, cached: 0, output: 0 };
79898
- totals.input = Math.max(totals.input, n(usage.input_tokens));
79899
- totals.cached = Math.max(totals.cached, n(usage.cached_input_tokens));
79900
- totals.output = Math.max(totals.output, n(usage.output_tokens));
80067
+ const next = {
80068
+ input: n(usage.input_tokens),
80069
+ cached: n(usage.cached_input_tokens),
80070
+ output: n(usage.output_tokens)
80071
+ };
80072
+ const previous = totals ?? { input: 0, cached: 0, output: 0 };
80073
+ const delta = {
80074
+ input: Math.max(0, next.input - previous.input),
80075
+ cached: Math.max(0, next.cached - previous.cached),
80076
+ output: Math.max(0, next.output - previous.output)
80077
+ };
80078
+ if (delta.input > 0 || delta.cached > 0 || delta.output > 0) {
80079
+ if (speedMode === "fast" || speedMode === "standard") classifiedUsage = true;
80080
+ else fastCoverageComplete = false;
80081
+ if (speedMode === "fast") fastTotals = addCodexTotals(fastTotals, delta);
80082
+ }
80083
+ totals = {
80084
+ input: Math.max(previous.input, next.input),
80085
+ cached: Math.max(previous.cached, next.cached),
80086
+ output: Math.max(previous.output, next.output)
80087
+ };
79901
80088
  }
79902
80089
  }
79903
80090
  }
@@ -79911,6 +80098,8 @@ function parseCodexRollout(text) {
79911
80098
  endedAt: timestamps[timestamps.length - 1],
79912
80099
  ...model && { model },
79913
80100
  totals,
80101
+ fastTotals: fastCoverageComplete && classifiedUsage ? fastTotals : null,
80102
+ ...speedMode && { speedMode },
79914
80103
  ...typeof meta3?.cwd === "string" && meta3.cwd !== "" && { cwd: meta3.cwd },
79915
80104
  ...typeof git?.branch === "string" && git.branch !== "" && { gitBranch: git.branch },
79916
80105
  ...repoNameFromUrl(git?.repository_url) ?? {},
@@ -79924,14 +80113,23 @@ function parseRolloutFile(rolloutPath) {
79924
80113
  function rolloutLocation(rolloutPath) {
79925
80114
  const record2 = readFirstRecord(rolloutPath, (candidate) => candidate.type === "session_meta");
79926
80115
  const meta3 = record2?.payload;
79927
- if (!meta3 || typeof meta3 !== "object" || Array.isArray(meta3)) return { kind: "session", totals: null };
80116
+ if (!meta3 || typeof meta3 !== "object" || Array.isArray(meta3)) return { kind: "session", totals: null, fastTotals: null };
79928
80117
  const { thread_source: threadSource, cwd } = meta3;
79929
80118
  return {
79930
80119
  kind: threadSource === "subagent" ? "subagent" : "session",
79931
80120
  totals: null,
80121
+ fastTotals: null,
79932
80122
  ...typeof cwd === "string" && cwd !== "" && { cwd }
79933
80123
  };
79934
80124
  }
80125
+ function codexSpeedMode(value) {
80126
+ if (value === "priority" || value === "fast") return "fast";
80127
+ if (value === "default") return "standard";
80128
+ return void 0;
80129
+ }
80130
+ function addCodexTotals(a, b2) {
80131
+ return { input: a.input + b2.input, cached: a.cached + b2.cached, output: a.output + b2.output };
80132
+ }
79935
80133
  function repoFullNameFrom(url2) {
79936
80134
  const fullName = parseGithubRepo(url2);
79937
80135
  return fullName ? { repoFullName: fullName } : {};
@@ -80062,6 +80260,7 @@ async function collectCodexSessions(options = {}) {
80062
80260
  ...summary.startedAt && { startedAt: summary.startedAt },
80063
80261
  ...summary.endedAt && { endedAt: summary.endedAt },
80064
80262
  ...summary.model && { model: summary.model },
80263
+ ...summary.speedMode && { speedMode: summary.speedMode },
80065
80264
  ...title && { title: redactText(title).slice(0, 500) },
80066
80265
  // input_tokens already includes the cached subset — it IS the
80067
80266
  // all-in input-class total. No cache-write figure exists, so
@@ -80069,6 +80268,12 @@ async function collectCodexSessions(options = {}) {
80069
80268
  ...summary.totals && { inputTokens: summary.totals.input },
80070
80269
  ...summary.totals && { outputTokens: summary.totals.output },
80071
80270
  ...summary.totals && { cacheReadTokens: summary.totals.cached },
80271
+ ...summary.fastTotals && { fastInputTokens: summary.fastTotals.input },
80272
+ ...summary.fastTotals && { fastOutputTokens: summary.fastTotals.output },
80273
+ ...summary.fastTotals && { fastCacheReadTokens: summary.fastTotals.cached },
80274
+ // OpenAI prompt-cache writes have no separate price in the current
80275
+ // session cost model; zero is measured here, not an invented count.
80276
+ ...summary.fastTotals && { fastCacheWriteTokens: 0 },
80072
80277
  ...repoDir && { repoDir },
80073
80278
  ...summary.gitBranch && { gitBranch: summary.gitBranch },
80074
80279
  ...repoFullName && { repoFullName },
@@ -80117,6 +80322,8 @@ var emptyResult = () => ({
80117
80322
  skippedSubagentComposers: 0,
80118
80323
  skippedNonLocalComposers: 0,
80119
80324
  skippedUnreadable: 0,
80325
+ skippedTooLarge: 0,
80326
+ truncatedTranscripts: 0,
80120
80327
  unavailable: null
80121
80328
  });
80122
80329
  var SQLITE_EXPERIMENTAL_WARNING = "SQLite is an experimental feature and might change at any time";
@@ -80144,9 +80351,6 @@ async function openCursorDatabase(dbPath) {
80144
80351
  const sqlite = await loadSqlite();
80145
80352
  return new sqlite.DatabaseSync(dbPath, { readOnly: true });
80146
80353
  }
80147
- function describeError(error52) {
80148
- return error52 instanceof Error ? error52.message : String(error52);
80149
- }
80150
80354
  function isRecord3(value) {
80151
80355
  return value !== null && typeof value === "object" && !Array.isArray(value);
80152
80356
  }
@@ -80221,6 +80425,8 @@ function toolFilePathOf(args) {
80221
80425
  }
80222
80426
  return void 0;
80223
80427
  }
80428
+ var SPOOL_META_RESERVE_BYTES = 64 * 1024;
80429
+ var lineBytes = (line) => Buffer.byteLength(JSON.stringify(line)) + 1;
80224
80430
  function bubbleLines(bubble) {
80225
80431
  const lines = [];
80226
80432
  const createdAt = typeof bubble.createdAt === "string" ? { createdAt: bubble.createdAt } : {};
@@ -80260,43 +80466,50 @@ function bubbleLines(bubble) {
80260
80466
  }
80261
80467
  return lines;
80262
80468
  }
80263
- function extractTranscript(db, composerId) {
80469
+ function extractTranscript(db, composerId, maxBytes) {
80264
80470
  const valueByKey = db.prepare("select value from cursorDiskKV where key = ?");
80265
80471
  const dataRow = valueByKey.get(`composerData:${composerId}`);
80266
80472
  const dataText = isRecord3(dataRow) && typeof dataRow.value === "string" ? dataRow.value : null;
80267
80473
  const data = dataText === null ? null : safeJsonRecord2(dataText);
80268
80474
  const spine = Array.isArray(data?.fullConversationHeadersOnly) ? data.fullConversationHeadersOnly : [];
80269
80475
  if (spine.length === 0) return null;
80476
+ const budget = maxBytes - SPOOL_META_RESERVE_BYTES;
80270
80477
  const lines = [];
80271
80478
  const modelCounts = /* @__PURE__ */ new Map();
80479
+ let bytes = 0;
80272
80480
  let bubbleCount = 0;
80273
- for (const entry of spine) {
80481
+ let bubblesOmitted = 0;
80482
+ for (const [index, entry] of spine.entries()) {
80274
80483
  if (!isRecord3(entry) || typeof entry.bubbleId !== "string") continue;
80275
80484
  const row = valueByKey.get(`bubbleId:${composerId}:${entry.bubbleId}`);
80276
80485
  const text = isRecord3(row) && typeof row.value === "string" ? row.value : null;
80277
80486
  const bubble = text === null ? null : safeJsonRecord2(text);
80278
80487
  if (bubble === null) continue;
80488
+ const bubbleLinesOut = bubbleLines(bubble);
80489
+ const bubbleBytes = bubbleLinesOut.reduce((sum2, line) => sum2 + lineBytes(line), 0);
80490
+ if (bytes + bubbleBytes > budget) {
80491
+ bubblesOmitted = spine.length - index;
80492
+ break;
80493
+ }
80494
+ bytes += bubbleBytes;
80279
80495
  bubbleCount += 1;
80280
- for (const line of bubbleLines(bubble)) {
80496
+ for (const line of bubbleLinesOut) {
80281
80497
  lines.push(line);
80282
80498
  if (line.type === "agent" && line.model !== void 0) {
80283
80499
  modelCounts.set(line.model, (modelCounts.get(line.model) ?? 0) + 1);
80284
80500
  }
80285
80501
  }
80286
80502
  }
80287
- if (bubbleCount === 0) return null;
80503
+ if (bubbleCount === 0 && bubblesOmitted === 0) return null;
80288
80504
  let model;
80289
80505
  let best = 0;
80290
80506
  for (const [name, count] of modelCounts) {
80291
80507
  if (count > best) [model, best] = [name, count];
80292
80508
  }
80293
- return { lines, ...model && { model }, bubbleCount };
80509
+ return { lines, ...model && { model }, bubbleCount, bubblesOmitted };
80294
80510
  }
80295
80511
  function readSpoolModel(spoolPath) {
80296
- const text = readIfExists2(spoolPath);
80297
- if (text === null) return void 0;
80298
- const newline = text.indexOf("\n");
80299
- const record2 = safeJsonRecord2(newline === -1 ? text : text.slice(0, newline));
80512
+ const record2 = readFirstRecord(spoolPath, () => true);
80300
80513
  if (record2 === null || record2.type !== "meta" || record2.format !== "cursor-local-transcript") {
80301
80514
  return void 0;
80302
80515
  }
@@ -80309,20 +80522,20 @@ async function collectCursorSessions(options = {}) {
80309
80522
  try {
80310
80523
  if (statIfExists(dbPath) === null) return result;
80311
80524
  } catch (error52) {
80312
- result.unavailable = `Cursor's session store isn't readable (${describeError(error52)})`;
80525
+ result.unavailable = `Cursor's session store isn't readable (${describeReadError(error52)})`;
80313
80526
  return result;
80314
80527
  }
80315
80528
  let db;
80316
80529
  try {
80317
80530
  db = await openCursorDatabase(dbPath);
80318
80531
  } catch (error52) {
80319
- result.unavailable = `Cursor's session store couldn't be opened (${describeError(error52)})`;
80532
+ result.unavailable = `Cursor's session store couldn't be opened (${describeReadError(error52)})`;
80320
80533
  return result;
80321
80534
  }
80322
80535
  try {
80323
80536
  collectFromStore(db, userDir, options, result);
80324
80537
  } catch (error52) {
80325
- result.unavailable = `reading Cursor's session store failed (${describeError(error52)})`;
80538
+ result.unavailable = `reading Cursor's session store failed (${describeReadError(error52)})`;
80326
80539
  } finally {
80327
80540
  try {
80328
80541
  db.close();
@@ -80336,6 +80549,7 @@ function collectFromStore(db, userDir, options, result) {
80336
80549
  const now = options.now ?? Date.now();
80337
80550
  const capture = options.capture ?? captureEverythingUnresolved;
80338
80551
  const spoolDir = options.spoolDir ?? cursorSpoolDir(os9.homedir());
80552
+ const maxBytes = options.maxTranscriptBytes ?? MAX_TRANSCRIPT_FILE_BYTES;
80339
80553
  let spoolDirReady = false;
80340
80554
  const headerRows = db.prepare(
80341
80555
  "select composerId, workspaceId, createdAt, lastUpdatedAt, isArchived, isSubagent, value from composerHeaders where lastUpdatedAt >= ? order by lastUpdatedAt desc"
@@ -80385,10 +80599,15 @@ function collectFromStore(db, userDir, options, result) {
80385
80599
  const spoolStat = statIfExists(spoolPath);
80386
80600
  let model;
80387
80601
  if (spoolStat !== null && Math.abs(spoolStat.mtimeMs - lastUpdatedAt) < 1) {
80602
+ if (spoolStat.size > maxBytes) {
80603
+ result.skippedTooLarge += 1;
80604
+ continue;
80605
+ }
80388
80606
  model = readSpoolModel(spoolPath);
80389
80607
  } else {
80390
- const transcript = extractTranscript(db, raw.composerId);
80608
+ const transcript = extractTranscript(db, raw.composerId, maxBytes);
80391
80609
  if (transcript === null) continue;
80610
+ if (transcript.bubblesOmitted > 0) result.truncatedTranscripts += 1;
80392
80611
  model = transcript.model;
80393
80612
  const meta3 = {
80394
80613
  type: "meta",
@@ -80400,7 +80619,8 @@ function collectFromStore(db, userDir, options, result) {
80400
80619
  ...model && { model },
80401
80620
  ...typeof raw.createdAt === "number" && { createdAt: raw.createdAt },
80402
80621
  lastUpdatedAt,
80403
- ...cwd && { workspacePath: cwd }
80622
+ ...cwd && { workspacePath: cwd },
80623
+ ...transcript.bubblesOmitted > 0 && { bubblesOmitted: transcript.bubblesOmitted }
80404
80624
  };
80405
80625
  if (!spoolDirReady) {
80406
80626
  mkdirSync2(spoolDir, { recursive: true });
@@ -81093,12 +81313,33 @@ async function resolveCapturePolicy(config2, credentials, log3, deps = defaultPo
81093
81313
  }
81094
81314
  let owners = credentials.allowedOwners ?? [];
81095
81315
  let repos = credentials.allowedRepos ?? null;
81316
+ let workspacePolicies = credentials.workspacePolicies;
81096
81317
  let ownersUnresolved = false;
81318
+ let claimsUnresolved = false;
81097
81319
  try {
81098
81320
  const fresh = await deps.fetchPolicy(credentials.apiUrl, credentials.token);
81099
81321
  ownersUnresolved = fresh.integration === "linked" && fresh.owners.length === 0;
81100
81322
  const nextOwners = ownersUnresolved ? owners : fresh.owners;
81101
- const changed = nextOwners.join(",") !== owners.join(",") || (fresh.repos?.join(",") ?? null) !== (repos?.join(",") ?? null);
81323
+ const nextWorkspaces = fresh.workspaces?.map((workspace) => {
81324
+ const unresolved = workspace.integration === "linked" && workspace.owners.length === 0 && workspace.repos === null;
81325
+ const cached3 = workspacePolicies?.find((entry) => entry.orgId === workspace.orgId) ?? (credentials.orgId !== void 0 && workspace.orgId === credentials.orgId ? { orgId: workspace.orgId, name: workspace.name, owners, repos } : void 0);
81326
+ const usableCache = cached3 !== void 0 && workspaceClaimsAnyRepo(cached3);
81327
+ const effective = unresolved && usableCache ? cached3 : unresolved ? {
81328
+ orgId: workspace.orgId,
81329
+ name: workspace.name,
81330
+ owners: workspace.owners,
81331
+ repos: workspace.repos,
81332
+ unresolved: true
81333
+ } : {
81334
+ orgId: workspace.orgId,
81335
+ name: workspace.name,
81336
+ owners: workspace.owners,
81337
+ repos: workspace.repos
81338
+ };
81339
+ if (effective.unresolved) claimsUnresolved = true;
81340
+ return effective;
81341
+ });
81342
+ const changed = nextOwners.join(",") !== owners.join(",") || (fresh.repos?.join(",") ?? null) !== (repos?.join(",") ?? null) || JSON.stringify(nextWorkspaces ?? null) !== JSON.stringify(workspacePolicies ?? null);
81102
81343
  if (changed) {
81103
81344
  const next = {
81104
81345
  ...credentials,
@@ -81106,34 +81347,48 @@ async function resolveCapturePolicy(config2, credentials, log3, deps = defaultPo
81106
81347
  };
81107
81348
  if (fresh.repos !== null) next.allowedRepos = fresh.repos;
81108
81349
  else delete next.allowedRepos;
81350
+ if (nextWorkspaces !== void 0) next.workspacePolicies = nextWorkspaces;
81351
+ else delete next.workspacePolicies;
81109
81352
  deps.persist(config2.appEnv, next);
81110
81353
  }
81111
81354
  owners = nextOwners;
81112
81355
  repos = fresh.repos;
81356
+ workspacePolicies = nextWorkspaces;
81113
81357
  } catch (cause) {
81358
+ const cachedCoversSomething = owners.length > 0 || repos !== null || (workspacePolicies ?? []).some((entry) => workspaceClaimsAnyRepo(entry));
81359
+ if (!cachedCoversSomething || (workspacePolicies ?? []).some((entry) => entry.unresolved === true)) {
81360
+ claimsUnresolved = true;
81361
+ }
81114
81362
  log3(
81115
- `\u26A0 Couldn't refresh the org's repo sync policy (${describeSyncError(cause)}) \u2014 ` + (owners.length > 0 || repos !== null ? "using the cached policy." : "nothing will upload this pass.")
81363
+ `\u26A0 Couldn't refresh the org's repo sync policy (${describeSyncError(cause)}) \u2014 ` + (cachedCoversSomething ? "using the cached policy." : "nothing will upload this pass.")
81116
81364
  );
81117
81365
  }
81118
81366
  const memberFolders = credentials.scopePaths ?? [];
81367
+ if (ownersUnresolved && owners.length === 0 && repos === null) claimsUnresolved = true;
81119
81368
  if (ownersUnresolved) {
81120
81369
  log3(
81121
81370
  "\u26A0 The org's GitHub is connected, but its account couldn't be resolved just now \u2014 " + (owners.length > 0 ? `using the cached owner list: ${owners.join(", ")}.` : "no owner-based uploads this pass. This is usually temporary; syncing resumes when it resolves.")
81122
81371
  );
81123
81372
  }
81373
+ const homeOrgId = credentials.orgId ?? workspacePolicies?.[0]?.orgId;
81374
+ const foreignClaims = (workspacePolicies ?? []).filter(
81375
+ (entry) => entry.orgId !== homeOrgId && workspaceClaimsAnyRepo(entry)
81376
+ );
81124
81377
  if (repos !== null && repos.length === 0) {
81125
81378
  log3(
81126
- "\u26A0 The org's repo allowlist is empty \u2014 no repo-based uploads" + (memberFolders.length > 0 ? "; only folders you added upload." : ".")
81379
+ "\u26A0 The org's repo allowlist is empty \u2014 no repo-based uploads for it" + (foreignClaims.length > 0 ? `; repos synced by ${foreignClaims.map((entry) => entry.name).join(", ")} still upload.` : memberFolders.length > 0 ? "; only folders you added upload." : ".")
81127
81380
  );
81128
81381
  } else if (repos === null && owners.length === 0 && !ownersUnresolved) {
81129
81382
  log3(
81130
- "\u26A0 Syncing is gated on org-owned GitHub repos, but the org has no GitHub integration \u2014 nothing uploads. Connect GitHub in Settings, or add folders with `powerplant folders add`."
81383
+ foreignClaims.length > 0 ? `Your home workspace has no GitHub integration \u2014 syncing repos for ${foreignClaims.map((entry) => entry.name).join(", ")}.` : "\u26A0 Syncing is gated on org-owned GitHub repos, but the org has no GitHub integration \u2014 nothing uploads. Connect GitHub in Settings, or add folders with `powerplant folders add`."
81131
81384
  );
81132
81385
  }
81133
81386
  return {
81134
81387
  mode: "org-repos",
81135
81388
  allowedOwners: owners,
81136
81389
  allowedRepos: repos,
81390
+ ...workspacePolicies !== void 0 && { workspaceClaims: workspacePolicies },
81391
+ ...claimsUnresolved && { claimsUnresolved },
81137
81392
  memberFolders,
81138
81393
  repoOf,
81139
81394
  environment
@@ -81156,7 +81411,7 @@ async function runSyncPass(config2, options = {}) {
81156
81411
  const capture = await resolveCapturePolicy(config2, credentials, log3);
81157
81412
  const sinceMs = resolveScanFloor(window2, Date.now(), options.narrowSinceMs);
81158
81413
  const maxSessions = window2.deepSweep ? DEEP_FIRST_SYNC_MAX_SESSIONS : void 0;
81159
- const claude = await collectClaudeCodeSessions({ sinceMs, capture });
81414
+ const claude = await collectClaudeCodeSessions({ sinceMs, capture, maxSessions });
81160
81415
  const codex = await collectCodexSessions({ sinceMs, capture, maxSessions });
81161
81416
  const cursor = await collectCursorSessions({ sinceMs, capture });
81162
81417
  if (cursor.unavailable !== null) {
@@ -81166,14 +81421,16 @@ async function runSyncPass(config2, options = {}) {
81166
81421
  (a, b2) => b2.mtimeMs - a.mtimeMs
81167
81422
  );
81168
81423
  const collected = maxSessions === void 0 ? inWindow : inWindow.slice(0, maxSessions);
81169
- const heldBack = inWindow.length - collected.length + codex.heldBack;
81424
+ const heldBack = inWindow.length - collected.length + claude.heldBack + codex.heldBack;
81170
81425
  const skippedOutOfScope = claude.skippedOutOfScope + codex.skippedOutOfScope + cursor.skippedOutOfScope;
81171
81426
  const skippedNoRepo = claude.skippedNoRepo + codex.skippedNoRepo + cursor.skippedNoRepo;
81172
81427
  const items = collected.map((c) => c.item);
81173
81428
  const active = items.filter((s) => s.status === "active").length;
81429
+ const tooLarge = (count, what) => count > 0 ? ` \xB7 ${count} ${what} skipped (over ${MAX_TRANSCRIPT_FILE_BYTES / (1024 * 1024)}MB \u2014 too large to read)` : "";
81174
81430
  log3(
81175
- `Found ${inWindow.length} local session(s) (${claude.sessions.length} claude, ${codex.sessions.length} codex, ${cursor.sessions.length} cursor) ` + describeScanReach(window2, options.narrowSinceMs) + (active > 0 ? ` (${active} still active)` : "") + (skippedOutOfScope > 0 ? ` \xB7 ${skippedOutOfScope} ${describeSkipReason(capture)}` : "") + (skippedNoRepo > 0 ? ` \xB7 ${skippedNoRepo} ${describeNoRepoSkipReason(capture)}` : "") + (codex.skippedSubagentThreads > 0 ? ` \xB7 ${codex.skippedSubagentThreads} codex subagent thread(s) skipped (spend unmeasured)` : "") + (cursor.skippedSubagentComposers > 0 ? ` \xB7 ${cursor.skippedSubagentComposers} cursor subagent thread(s) skipped` : "") + (cursor.skippedNonLocalComposers > 0 ? ` \xB7 ${cursor.skippedNonLocalComposers} cursor session(s) skipped (not local to this machine)` : "") + (cursor.skippedUnreadable > 0 ? ` \xB7 ${cursor.skippedUnreadable} cursor session(s) unreadable, skipped` : "") + (codex.skippedTooLarge > 0 ? ` \xB7 ${codex.skippedTooLarge} codex rollout(s) skipped (over ${MAX_TRANSCRIPT_FILE_BYTES / (1024 * 1024)}MB \u2014 too large to read)` : "") + (codex.unreadable.length > 0 ? ` \xB7 ${codex.unreadable.length} codex rollout(s) unreadable, skipped` : "") + (heldBack > 0 ? ` \xB7 ${heldBack} older session(s) held back \u2014 the first sync backfills the ${DEEP_FIRST_SYNC_MAX_SESSIONS} most recently active` : "") + "."
81431
+ `Found ${inWindow.length} local session(s) (${claude.sessions.length} claude, ${codex.sessions.length} codex, ${cursor.sessions.length} cursor) ` + describeScanReach(window2, options.narrowSinceMs) + (active > 0 ? ` (${active} still active)` : "") + (skippedOutOfScope > 0 ? ` \xB7 ${skippedOutOfScope} ${describeSkipReason(capture)}` : "") + (skippedNoRepo > 0 ? ` \xB7 ${skippedNoRepo} ${describeNoRepoSkipReason(capture)}` : "") + (codex.skippedSubagentThreads > 0 ? ` \xB7 ${codex.skippedSubagentThreads} codex subagent thread(s) skipped (spend unmeasured)` : "") + (cursor.skippedSubagentComposers > 0 ? ` \xB7 ${cursor.skippedSubagentComposers} cursor subagent thread(s) skipped` : "") + (cursor.skippedNonLocalComposers > 0 ? ` \xB7 ${cursor.skippedNonLocalComposers} cursor session(s) skipped (not local to this machine)` : "") + (cursor.skippedUnreadable > 0 ? ` \xB7 ${cursor.skippedUnreadable} cursor session(s) unreadable, skipped` : "") + tooLarge(claude.skippedTooLarge, "claude transcript(s)") + (claude.unreadable.length > 0 ? ` \xB7 ${claude.unreadable.length} claude transcript(s) unreadable, skipped` : "") + tooLarge(codex.skippedTooLarge, "codex rollout(s)") + (codex.unreadable.length > 0 ? ` \xB7 ${codex.unreadable.length} codex rollout(s) unreadable, skipped` : "") + tooLarge(cursor.skippedTooLarge, "cursor transcript(s)") + (cursor.truncatedTranscripts > 0 ? ` \xB7 ${cursor.truncatedTranscripts} cursor transcript(s) cut at ${MAX_TRANSCRIPT_FILE_BYTES / (1024 * 1024)}MB (the rest of the conversation stays local)` : "") + (heldBack > 0 ? ` \xB7 ${heldBack} older session(s) held back \u2014 the first sync backfills the ${DEEP_FIRST_SYNC_MAX_SESSIONS} most recently active` : "") + "."
81176
81432
  );
81433
+ for (const failure of claude.unreadable) log3(` claude transcript skipped \u2014 ${failure}`);
81177
81434
  for (const failure of codex.unreadable) log3(` codex rollout skipped \u2014 ${failure}`);
81178
81435
  if (options.dryRun) {
81179
81436
  for (const s of items.slice(0, 10)) {
@@ -81199,11 +81456,18 @@ async function runSyncPass(config2, options = {}) {
81199
81456
  saveCredentials(config2.appEnv, { ...current, deepSyncedAt: (/* @__PURE__ */ new Date()).toISOString() });
81200
81457
  log3(`Deep sync complete \u2014 future syncs cover the last ${STEADY_SYNC_WINDOW_DAYS} days.`);
81201
81458
  };
81459
+ const claimsUnresolved = capture.mode === "org-repos" && capture.claimsUnresolved === true;
81202
81460
  if (items.length === 0) {
81203
- markDeepSynced();
81461
+ if (claimsUnresolved) {
81462
+ if (window2.deepSweep) {
81463
+ log3("Deep sync not stamped \u2014 a workspace's claims are unresolved; the sweep re-runs next pass.");
81464
+ }
81465
+ } else {
81466
+ markDeepSynced();
81467
+ }
81204
81468
  return { sessions: collected, upserted: 0 };
81205
81469
  }
81206
- const { upserted, unchanged, outOfPolicy } = await uploadSessions(
81470
+ const { upserted, unchanged, outOfPolicy, deferred } = await uploadSessions(
81207
81471
  credentials.apiUrl,
81208
81472
  credentials.token,
81209
81473
  items
@@ -81211,6 +81475,11 @@ async function runSyncPass(config2, options = {}) {
81211
81475
  log3(
81212
81476
  `Uploaded: ${upserted} session(s) upserted` + (unchanged > 0 ? `, ${unchanged} unchanged (no writes issued)` : "") + "."
81213
81477
  );
81478
+ if (deferred > 0) {
81479
+ log3(
81480
+ `\u26A0 ${deferred} session(s) deferred \u2014 a workspace's GitHub owners couldn't be resolved just now, so routing waited rather than guessing a workspace. They upload on a later pass.`
81481
+ );
81482
+ }
81214
81483
  if (outOfPolicy > 0) {
81215
81484
  log3(
81216
81485
  `\u26A0 ${outOfPolicy} session(s) refused by the org's repo sync policy \u2014 they stayed out of Powerplant. The next pass refreshes the policy and stops offering them.`
@@ -81266,11 +81535,24 @@ async function runSyncPass(config2, options = {}) {
81266
81535
  if (raw.failed.length > 0) parts.push(`${raw.failed.length} failed`);
81267
81536
  log3(`Raw content: ${parts.join(", ")}.`);
81268
81537
  for (const failure of raw.failed) log3(` raw upload failed \u2014 ${failure}`);
81269
- const nothingElseAccountedFor = raw.skippedUnchanged === 0 && raw.skippedTooLarge === 0 && rawWindowSkipped === 0;
81538
+ if (raw.missingMetadata > 0) {
81539
+ log3(
81540
+ `${raw.missingMetadata} raw transcript(s) waiting on their session metadata \u2014 they re-send once it lands.`
81541
+ );
81542
+ }
81543
+ const nothingElseAccountedFor = raw.skippedUnchanged === 0 && raw.skippedTooLarge === 0 && rawWindowSkipped === 0 && raw.missingMetadata === 0;
81270
81544
  if (raw.uploaded === 0 && (raw.failed.length > 1 || raw.failed.length === 1 && nothingElseAccountedFor)) {
81271
81545
  throw new Error(`all ${raw.failed.length} raw upload(s) failed this pass \u2014 ${raw.failed[0]}`);
81272
81546
  }
81273
- markDeepSynced();
81547
+ if (deferred > 0 || claimsUnresolved) {
81548
+ if (window2.deepSweep) {
81549
+ log3(
81550
+ deferred > 0 ? "Deep sync not stamped \u2014 deferred sessions still owe their upload; the sweep re-runs next pass." : "Deep sync not stamped \u2014 a workspace's claims are unresolved; the sweep re-runs next pass."
81551
+ );
81552
+ }
81553
+ } else {
81554
+ markDeepSynced();
81555
+ }
81274
81556
  }
81275
81557
  return { sessions: collected, upserted };
81276
81558
  }
@@ -81324,14 +81606,11 @@ function findClaudeTranscript(homeDir, externalId) {
81324
81606
  const transcript = path13.join(projectDir, `${externalId}.jsonl`);
81325
81607
  const stat = statIfExists(transcript);
81326
81608
  if (stat === null) continue;
81327
- const text = readIfExists2(transcript);
81328
- if (text === null) continue;
81329
- for (const record2 of parseJsonl(text)) {
81330
- if (typeof record2.cwd === "string" && record2.cwd !== "") {
81331
- return { cwd: record2.cwd, mtimeMs: stat.mtimeMs };
81332
- }
81333
- }
81334
- return null;
81609
+ const record2 = readFirstRecord(
81610
+ transcript,
81611
+ (candidate) => typeof candidate.cwd === "string" && candidate.cwd !== ""
81612
+ );
81613
+ return typeof record2?.cwd === "string" ? { cwd: record2.cwd, mtimeMs: stat.mtimeMs } : null;
81335
81614
  }
81336
81615
  return null;
81337
81616
  }
@@ -81356,7 +81635,7 @@ function createWakePoller(deps) {
81356
81635
  try {
81357
81636
  for (const target of await deps.listPending()) {
81358
81637
  if (inFlight.has(target.externalId)) continue;
81359
- const home = deps.inspect(target);
81638
+ const home = inspectTarget(deps, target);
81360
81639
  if (home === null) continue;
81361
81640
  if (deps.now() - home.mtimeMs < WAKE_QUIET_MS) continue;
81362
81641
  const messages = await deps.claim(target);
@@ -81366,13 +81645,13 @@ function createWakePoller(deps) {
81366
81645
  `waking session ${target.externalId} with ${messages.length} message(s) in ${home.cwd}`
81367
81646
  );
81368
81647
  void deps.runClaude(target, formatWakePrompt(messages), home.cwd).then(() => deps.log(`wake finished for session ${target.externalId}`)).catch(
81369
- (error52) => reportFailedWake(deps, target, messages, describeError2(error52))
81648
+ (error52) => reportFailedWake(deps, target, messages, describeError(error52))
81370
81649
  ).finally(() => inFlight.delete(target.externalId));
81371
81650
  }
81372
81651
  consecutivePollFailures = 0;
81373
81652
  } catch (error52) {
81374
81653
  consecutivePollFailures += 1;
81375
- deps.log(`\u26A0 wake poll failed (${describeError2(error52)}) \u2014 next poll retries`);
81654
+ deps.log(`\u26A0 wake poll failed (${describeError(error52)}) \u2014 next poll retries`);
81376
81655
  if (consecutivePollFailures === REPORT_AFTER_CONSECUTIVE_FAILURES) {
81377
81656
  deps.reportError?.(error52, "wake-poll", { consecutivePollFailures });
81378
81657
  }
@@ -81382,6 +81661,16 @@ function createWakePoller(deps) {
81382
81661
  }
81383
81662
  };
81384
81663
  }
81664
+ function inspectTarget(deps, target) {
81665
+ try {
81666
+ return deps.inspect(target);
81667
+ } catch (error52) {
81668
+ deps.log(
81669
+ `\u26A0 wake skipped for session ${target.externalId} \u2014 its transcript could not be read (${describeError(error52)}); next poll retries`
81670
+ );
81671
+ return null;
81672
+ }
81673
+ }
81385
81674
  async function reportFailedWake(deps, target, messages, reason) {
81386
81675
  deps.log(
81387
81676
  `\u26A0 wake failed for session ${target.externalId} AFTER its messages were claimed (${reason})`
@@ -81394,7 +81683,7 @@ async function reportFailedWake(deps, target, messages, reason) {
81394
81683
  );
81395
81684
  } catch (reportError) {
81396
81685
  deps.log(
81397
- `\u26A0 wake failure report for session ${target.externalId} ALSO failed \u2014 its messages read Delivered but no agent ran (${describeError2(reportError)})`
81686
+ `\u26A0 wake failure report for session ${target.externalId} ALSO failed \u2014 its messages read Delivered but no agent ran (${describeError(reportError)})`
81398
81687
  );
81399
81688
  deps.reportError?.(reportError, "wake-failure-report", {
81400
81689
  externalId: target.externalId,
@@ -81446,7 +81735,7 @@ function preflightClaudeVersion(claudeBin, readVersionOutput = execClaudeVersion
81446
81735
  try {
81447
81736
  output = readVersionOutput(claudeBin);
81448
81737
  } catch (error52) {
81449
- return { ok: false, problem: `\`${claudeBin} --version\` failed (${describeError2(error52)})` };
81738
+ return { ok: false, problem: `\`${claudeBin} --version\` failed (${describeError(error52)})` };
81450
81739
  }
81451
81740
  const version2 = /\d+\.\d+\.\d+/.exec(output)?.[0] ?? null;
81452
81741
  if (version2 === null) {
@@ -81591,7 +81880,7 @@ function startWakeService(config2, homeDir, log3) {
81591
81880
  log3(`wake-on-message armed: polling every ${WAKE_POLL_MS / 1e3}s (${config2.appEnv})`);
81592
81881
  return () => clearInterval(timer);
81593
81882
  }
81594
- function describeError2(error52) {
81883
+ function describeError(error52) {
81595
81884
  return error52 instanceof Error ? error52.message : String(error52);
81596
81885
  }
81597
81886
 
@@ -81631,6 +81920,8 @@ var SAFETY_NET_INTERVAL_MS = 60 * 60 * 1e3;
81631
81920
  var NARROW_OVERLAP_MS = 6e4;
81632
81921
  var WATCHER_RETRY_MS = 6e4;
81633
81922
  var FAILED_PASS_RETRY_MS = 6e4;
81923
+ var FAILED_PASS_BACKOFF_CAP_MS = 15 * 6e4;
81924
+ var STREAK_REREPORT_INTERVAL_MS = 60 * 6e4;
81634
81925
  var VERSION_NOTICE_INTERVAL_MS = 60 * 6e4;
81635
81926
  var COMPLETION_SLACK_MS = 3e4;
81636
81927
  function createWatchLoop(deps) {
@@ -81643,6 +81934,10 @@ function createWatchLoop(deps) {
81643
81934
  let completionTimer = null;
81644
81935
  let running = false;
81645
81936
  let consecutiveFailures = 0;
81937
+ let streakSignature = null;
81938
+ let streakCount = 0;
81939
+ let streakStartedAt = 0;
81940
+ let streakReportedAt = 0;
81646
81941
  let pausedLogged = false;
81647
81942
  let queued = null;
81648
81943
  let retryNotBefore = 0;
@@ -81669,7 +81964,7 @@ function createWatchLoop(deps) {
81669
81964
  if (stopped) return;
81670
81965
  try {
81671
81966
  closeWatcher = deps.watch(onEvent, (error52) => {
81672
- deps.log(`watcher lost (${describeError3(error52)}) \u2014 re-arming and re-scanning`);
81967
+ deps.log(`watcher lost (${describeError2(error52)}) \u2014 re-arming and re-scanning`);
81673
81968
  closeWatcher?.();
81674
81969
  closeWatcher = null;
81675
81970
  arm();
@@ -81677,7 +81972,7 @@ function createWatchLoop(deps) {
81677
81972
  });
81678
81973
  } catch (error52) {
81679
81974
  deps.log(
81680
- `cannot watch yet (${describeError3(error52)}) \u2014 retrying in ${WATCHER_RETRY_MS / 1e3}s`
81975
+ `cannot watch yet (${describeError2(error52)}) \u2014 retrying in ${WATCHER_RETRY_MS / 1e3}s`
81681
81976
  );
81682
81977
  watcherRetry = setTimeout(arm, WATCHER_RETRY_MS);
81683
81978
  }
@@ -81734,6 +82029,8 @@ function createWatchLoop(deps) {
81734
82029
  }
81735
82030
  }
81736
82031
  consecutiveFailures = 0;
82032
+ streakSignature = null;
82033
+ streakCount = 0;
81737
82034
  deps.onPassResult?.({
81738
82035
  kind,
81739
82036
  at: startedAt,
@@ -81743,17 +82040,33 @@ function createWatchLoop(deps) {
81743
82040
  });
81744
82041
  } catch (error52) {
81745
82042
  consecutiveFailures += 1;
81746
- retryNotBefore = now() + Math.max(FAILED_PASS_RETRY_MS, retryAfterMs(error52) ?? 0);
82043
+ const signature = errorSignature(error52);
82044
+ if (signature === streakSignature) {
82045
+ streakCount += 1;
82046
+ } else {
82047
+ streakSignature = signature;
82048
+ streakCount = 1;
82049
+ streakStartedAt = startedAt;
82050
+ streakReportedAt = 0;
82051
+ }
82052
+ const holdMs = Math.max(
82053
+ Math.min(FAILED_PASS_RETRY_MS * 2 ** (streakCount - 1), FAILED_PASS_BACKOFF_CAP_MS),
82054
+ retryAfterMs(error52) ?? 0
82055
+ );
82056
+ retryNotBefore = now() + holdMs;
81747
82057
  lifecycleChanged ||= consultLifecycle;
81748
82058
  deps.onPassResult?.({
81749
82059
  kind,
81750
82060
  at: startedAt,
81751
82061
  ok: false,
81752
- error: describeError3(error52),
82062
+ error: describeError2(error52),
81753
82063
  sessions: 0,
81754
82064
  upserted: 0
81755
82065
  });
81756
- deps.log(`${kind} pass failed (${describeError3(error52)}) \u2014 next pass re-covers this window`);
82066
+ const failLine = `${kind} pass failed (${describeError2(error52)})`;
82067
+ deps.log(
82068
+ streakCount > 1 ? `${failLine}: same failure ${streakCount} times in a row, next attempt in ${describeDelay(holdMs)}` : `${failLine}, next attempt in ${describeDelay(holdMs)} re-covers this window`
82069
+ );
81757
82070
  if (error52 instanceof Error && !(error52 instanceof ExpectedCliError) && error52.stack) {
81758
82071
  deps.log(error52.stack);
81759
82072
  }
@@ -81764,10 +82077,13 @@ function createWatchLoop(deps) {
81764
82077
  );
81765
82078
  return;
81766
82079
  }
81767
- if (consecutiveFailures >= REPORT_AFTER_CONSECUTIVE_FAILURES) {
82080
+ if (consecutiveFailures >= REPORT_AFTER_CONSECUTIVE_FAILURES && (streakReportedAt === 0 || now() - streakReportedAt >= STREAK_REREPORT_INTERVAL_MS)) {
82081
+ streakReportedAt = now();
81768
82082
  const report2 = error52 instanceof ExpectedCliError ? new Error(`${consecutiveFailures} consecutive failed passes: ${error52.message}`) : error52;
81769
82083
  deps.reportError?.(report2, `watch-pass-${kind}`, {
81770
82084
  consecutiveFailures,
82085
+ sameFailureCount: streakCount,
82086
+ streakMinutes: Math.round((now() - streakStartedAt) / 6e4),
81771
82087
  pendingCompletions: pendingCompletions.size
81772
82088
  });
81773
82089
  }
@@ -81855,7 +82171,7 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
81855
82171
  try {
81856
82172
  return loadCredentials(config2.appEnv);
81857
82173
  } catch (error52) {
81858
- log3(`\u26A0 credential store unreadable (${describeError3(error52)}) \u2014 identity omitted from status`);
82174
+ log3(`\u26A0 credential store unreadable (${describeError2(error52)}) \u2014 identity omitted from status`);
81859
82175
  return null;
81860
82176
  }
81861
82177
  })();
@@ -81869,14 +82185,14 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
81869
82185
  );
81870
82186
  } catch (error52) {
81871
82187
  log3(
81872
- `\u26A0 Claude Code hook install failed (${describeError3(error52)}) \u2014 syncing continues without them`
82188
+ `\u26A0 Claude Code hook install failed (${describeError2(error52)}) \u2014 syncing continues without them`
81873
82189
  );
81874
82190
  reportCollectorError(error52, "watch-hook-install");
81875
82191
  }
81876
82192
  try {
81877
82193
  startWakeService(config2, home, log3);
81878
82194
  } catch (error52) {
81879
- log3(`\u26A0 wake-on-message failed to start (${describeError3(error52)}) \u2014 syncing continues`);
82195
+ log3(`\u26A0 wake-on-message failed to start (${describeError2(error52)}) \u2014 syncing continues`);
81880
82196
  reportCollectorError(error52, "watch-wake-start");
81881
82197
  }
81882
82198
  const presentRoots = storeRoots.filter(({ dir }) => existsSync4(dir)).map(({ dir }) => dir);
@@ -81907,7 +82223,7 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
81907
82223
  statusWriteFailed = false;
81908
82224
  } catch (error52) {
81909
82225
  if (!statusWriteFailed) {
81910
- log3(`\u26A0 status file write failed (${describeError3(error52)}) \u2014 syncing continues`);
82226
+ log3(`\u26A0 status file write failed (${describeError2(error52)}) \u2014 syncing continues`);
81911
82227
  reportCollectorError(error52, "watch-status-write");
81912
82228
  }
81913
82229
  statusWriteFailed = true;
@@ -82005,9 +82321,21 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
82005
82321
  return new Promise(() => {
82006
82322
  });
82007
82323
  }
82008
- function describeError3(error52) {
82324
+ function describeError2(error52) {
82009
82325
  return error52 instanceof Error ? error52.message : String(error52);
82010
82326
  }
82327
+ function errorSignature(error52) {
82328
+ const parts = [];
82329
+ for (let cause = error52; cause instanceof Error; cause = cause.cause) {
82330
+ parts.push(`${cause.name}: ${cause.message}`);
82331
+ }
82332
+ if (parts.length === 0) parts.push(String(error52));
82333
+ return parts.join(" | ").replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "#").replace(/[0-9a-f]{8,}/gi, "#").replace(/\d+/g, "#");
82334
+ }
82335
+ function describeDelay(ms) {
82336
+ const seconds = Math.round(ms / 1e3);
82337
+ return seconds < 120 ? `${seconds}s` : `${Math.round(seconds / 60)} minutes`;
82338
+ }
82011
82339
 
82012
82340
  // src/connect.ts
82013
82341
  import { execFileSync as execFileSync5, spawnSync } from "node:child_process";
@@ -84284,7 +84612,7 @@ ${RESTART_NOTE}`;
84284
84612
  }
84285
84613
 
84286
84614
  // src/version.ts
84287
- var CLI_VERSION = true ? "0.3.2" : "0.0.0-checkout";
84615
+ var CLI_VERSION = true ? "0.4.0" : "0.0.0-checkout";
84288
84616
  var CLI_PACKAGE_NAME = "@usepowerplant/cli";
84289
84617
 
84290
84618
  // src/workspace.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usepowerplant/cli",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },