@sealant/mend 0.30.0 → 0.32.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 (79) hide show
  1. package/dist/{chunk-BMFB2UN7.js → chunk-J3TR4UAG.js} +81 -7
  2. package/dist/{dashboard-OF5UXII2.js → dashboard-3HNFSQZD.js} +60 -7
  3. package/dist/main.js +1821 -871
  4. package/man/mend-accounts.1 +1 -1
  5. package/man/mend-adopt.1 +1 -1
  6. package/man/mend-attach.1 +1 -1
  7. package/man/mend-codex.1 +1 -1
  8. package/man/mend-completions.1 +1 -1
  9. package/man/mend-connect.1 +1 -1
  10. package/man/mend-continue.1 +1 -1
  11. package/man/mend-doctor.1 +28 -3
  12. package/man/mend-dotfiles-repo.1 +1 -1
  13. package/man/mend-dotfiles-sync.1 +1 -1
  14. package/man/mend-dotfiles.1 +1 -1
  15. package/man/mend-env-cluster.1 +1 -1
  16. package/man/mend-env-load.1 +1 -1
  17. package/man/mend-env-show.1 +1 -1
  18. package/man/mend-folder-create.1 +1 -1
  19. package/man/mend-folder-list.1 +1 -1
  20. package/man/mend-folder-push.1 +1 -1
  21. package/man/mend-folder-rm.1 +1 -1
  22. package/man/mend-git-author.1 +24 -0
  23. package/man/mend-help.1 +1 -1
  24. package/man/mend-invite.1 +1 -1
  25. package/man/mend-keys-init.1 +1 -1
  26. package/man/mend-keys-mode.1 +1 -1
  27. package/man/mend-keys-share.1 +1 -1
  28. package/man/mend-keys-show.1 +1 -1
  29. package/man/mend-land.1 +9 -2
  30. package/man/mend-login.1 +1 -1
  31. package/man/mend-logout.1 +1 -1
  32. package/man/mend-man.1 +1 -1
  33. package/man/mend-members.1 +1 -1
  34. package/man/mend-operator-exposure.1 +1 -1
  35. package/man/mend-operator-gate.1 +1 -1
  36. package/man/mend-operator-grant-owner.1 +1 -1
  37. package/man/mend-operator-org-create.1 +1 -1
  38. package/man/mend-operator-org-invite-owner.1 +1 -1
  39. package/man/mend-operator-org-list.1 +1 -1
  40. package/man/mend-operator-org-rename.1 +1 -1
  41. package/man/mend-operator-reset-link.1 +1 -1
  42. package/man/mend-pair.1 +1 -1
  43. package/man/mend-projects.1 +1 -1
  44. package/man/mend-pull.1 +1 -1
  45. package/man/mend-refresh.1 +1 -1
  46. package/man/mend-rejoin.1 +1 -1
  47. package/man/mend-resume.1 +1 -1
  48. package/man/mend-run.1 +1 -1
  49. package/man/mend-server-logs.1 +1 -1
  50. package/man/mend-server-restart.1 +1 -1
  51. package/man/mend-server-setup.1 +1 -1
  52. package/man/mend-server-start.1 +1 -1
  53. package/man/mend-server-status.1 +1 -1
  54. package/man/mend-server-stop.1 +1 -1
  55. package/man/mend-server-upgrade.1 +1 -1
  56. package/man/mend-server.1 +1 -1
  57. package/man/mend-service-add.1 +1 -1
  58. package/man/mend-service-connect.1 +1 -1
  59. package/man/mend-service-init.1 +1 -1
  60. package/man/mend-service-list.1 +1 -1
  61. package/man/mend-service-logs.1 +1 -1
  62. package/man/mend-service-restart.1 +1 -1
  63. package/man/mend-service-run.1 +1 -1
  64. package/man/mend-service-stop.1 +1 -1
  65. package/man/mend-session-share.1 +1 -1
  66. package/man/mend-sessions.1 +1 -1
  67. package/man/mend-shell.1 +1 -1
  68. package/man/mend-skills-push.1 +1 -1
  69. package/man/mend-skills.1 +1 -1
  70. package/man/mend-snake.1 +1 -1
  71. package/man/mend-ssh-setup.1 +1 -1
  72. package/man/mend-ssh.1 +1 -1
  73. package/man/mend-stop.1 +8 -1
  74. package/man/mend-ui.1 +2 -2
  75. package/man/mend-uninstall.1 +1 -1
  76. package/man/mend-version.1 +1 -1
  77. package/man/mend-worktrees.1 +1 -1
  78. package/man/mend.1 +6 -3
  79. package/package.json +3 -3
@@ -26817,7 +26817,9 @@ var AuditAction = Schema_exports.Literals([
26817
26817
  // `gh` spoke as them.
26818
26818
  "change.landed",
26819
26819
  "change.pull_request_refreshed",
26820
- "change.bundle_downloaded"
26820
+ "change.bundle_downloaded",
26821
+ // A pull request opened outside Mend was read with the owner's `gh` and recorded.
26822
+ "change.pull_request_adopted"
26821
26823
  ]);
26822
26824
  var AuditData = Schema_exports.Record(
26823
26825
  Schema_exports.String,
@@ -26841,8 +26843,14 @@ var AuditEvent = class extends Schema_exports.Class("AuditEvent")({
26841
26843
  };
26842
26844
 
26843
26845
  // ../../packages/domain/src/workbench/landing.ts
26844
- var LandingTrigger = Schema_exports.Literals(["manual", "automatic"]);
26845
- var LandingOutcome = Schema_exports.Literals(["pushed", "pull-request", "refused", "failed"]);
26846
+ var LandingTrigger = Schema_exports.Literals(["manual", "automatic", "adopted"]);
26847
+ var LandingOutcome = Schema_exports.Literals([
26848
+ "pushed",
26849
+ "pull-request",
26850
+ "refused",
26851
+ "failed",
26852
+ "adopted"
26853
+ ]);
26846
26854
  var PullRequestState = Schema_exports.Literals(["open", "closed", "merged"]);
26847
26855
  var LandedPullRequest = Schema_exports.Struct({
26848
26856
  number: Schema_exports.Int,
@@ -26868,6 +26876,19 @@ var ChangeLanding = class extends Schema_exports.Class("ChangeLanding")({
26868
26876
  pushedSha: Schema_exports.NullOr(Sha),
26869
26877
  trigger: LandingTrigger,
26870
26878
  pullRequest: Schema_exports.NullOr(LandedPullRequest),
26879
+ /**
26880
+ * The pull request's head is in another repository (a fork): Mend pushes to origin only, so it
26881
+ * never updates such a pull request. Older servers omit it.
26882
+ */
26883
+ pullRequestCrossRepository: Schema_exports.Boolean.pipe(
26884
+ Schema_exports.withDecodingDefaultKey(Effect_exports.succeed(false)),
26885
+ Schema_exports.withConstructorDefault(Effect_exports.succeed(false))
26886
+ ),
26887
+ /** Who owns the repository the pull request's head is in, as `gh` reported it; null unknown. */
26888
+ pullRequestHeadOwner: Schema_exports.NullOr(Schema_exports.String).pipe(
26889
+ Schema_exports.withDecodingDefaultKey(Effect_exports.succeed(null)),
26890
+ Schema_exports.withConstructorDefault(Effect_exports.succeed(null))
26891
+ ),
26871
26892
  outcome: LandingOutcome,
26872
26893
  /** The remote's or `gh`'s own words for a refusal or a failure; null otherwise. */
26873
26894
  message: Schema_exports.NullOr(Schema_exports.String),
@@ -26915,7 +26936,11 @@ var LandingFact = Schema_exports.Union([
26915
26936
  Schema_exports.TaggedStruct("pull-request", {
26916
26937
  number: Schema_exports.Int,
26917
26938
  state: PullRequestState,
26918
- observedAt: Timestamp
26939
+ observedAt: Timestamp,
26940
+ /** Opened outside Mend and adopted. Older servers omit it. */
26941
+ outside: Schema_exports.Boolean.pipe(Schema_exports.withDecodingDefaultKey(Effect_exports.succeed(false))),
26942
+ /** The fork's owner when its head is in another repository; null for origin's own. */
26943
+ fork: Schema_exports.NullOr(Schema_exports.String).pipe(Schema_exports.withDecodingDefaultKey(Effect_exports.succeed(null)))
26919
26944
  }),
26920
26945
  /** A fetch found commits on origin's branch that Mend's branch does not have. */
26921
26946
  Schema_exports.TaggedStruct("origin-moved", { branch: Schema_exports.String, commits: Schema_exports.Int }),
@@ -26956,7 +26981,7 @@ var landingFactLine = (fact, now2) => {
26956
26981
  case "pushed":
26957
26982
  return `pushed \xB7 ${fact.branch} \xB7 ${shortSha(fact.sha)} \xB7 observed`;
26958
26983
  case "pull-request":
26959
- return `pull request #${fact.number} \xB7 ${fact.state} \xB7 observed ${observedAgo(fact.observedAt, now2)}`;
26984
+ return `pull request #${fact.number} \xB7 ${fact.state} \xB7 observed ${observedAgo(fact.observedAt, now2)}` + (fact.outside ? " \xB7 opened outside Mend" : "") + (fact.fork === null ? "" : ` \xB7 from ${fact.fork === "" ? "a fork" : `${fact.fork}'s fork`}`);
26960
26985
  case "origin-moved":
26961
26986
  return `origin has moved \xB7 ${fact.branch} has ${plural(fact.commits, "commit", "commits")} Mend has not seen`;
26962
26987
  case "changed-since-landing":
@@ -27258,6 +27283,37 @@ var FolderFile = class extends Schema_exports.Class("FolderFile")({
27258
27283
  var FOLDER_MAX_FILE_BYTES = 1024 * 1024;
27259
27284
  var FOLDER_MAX_REQUEST_BYTES = 4 * 1024 * 1024;
27260
27285
 
27286
+ // ../../packages/domain/src/workbench/git-author.ts
27287
+ var GitAuthorSource = Schema_exports.Literals(["setting", "account"]);
27288
+ var GitAuthor = class extends Schema_exports.Class("GitAuthor")({
27289
+ name: Schema_exports.String,
27290
+ email: Schema_exports.String
27291
+ }) {
27292
+ };
27293
+ var ResolvedGitAuthor = class extends Schema_exports.Class("ResolvedGitAuthor")({
27294
+ name: Schema_exports.String,
27295
+ email: Schema_exports.String,
27296
+ source: GitAuthorSource
27297
+ }) {
27298
+ };
27299
+ var GIT_AUTHOR_MAX_LENGTH = 200;
27300
+ var normalizeGitAuthor = (author) => new GitAuthor({ name: author.name.trim(), email: author.email.trim() });
27301
+ var gitAuthorIssue = (author) => {
27302
+ const { name, email } = normalizeGitAuthor(author);
27303
+ if (name === "") return "the name is empty";
27304
+ if (email === "") return "the email is empty";
27305
+ if (name.length > GIT_AUTHOR_MAX_LENGTH) {
27306
+ return `the name is longer than ${GIT_AUTHOR_MAX_LENGTH} characters`;
27307
+ }
27308
+ if (email.length > GIT_AUTHOR_MAX_LENGTH) {
27309
+ return `the email is longer than ${GIT_AUTHOR_MAX_LENGTH} characters`;
27310
+ }
27311
+ if (/[<>\r\n]/.test(name)) return "the name contains <, > or a line break";
27312
+ if (!/^[^\s<>@]+@[^\s<>@]+$/.test(email))
27313
+ return "the email is not an address like you@example.com";
27314
+ return null;
27315
+ };
27316
+
27261
27317
  // ../../packages/domain/src/workbench/review-slice.ts
27262
27318
  var DiffDigest = Schema_exports.String.pipe(
27263
27319
  Schema_exports.check(Schema_exports.isPattern(/^[0-9a-f]{64}$/)),
@@ -27563,13 +27619,14 @@ var ProjectLink = class extends Schema_exports.Class("ProjectLink")({
27563
27619
 
27564
27620
  // ../../packages/domain/src/workbench/pass.ts
27565
27621
  var PassKind = Schema_exports.Literals(["tour", "read", "suggest"]);
27566
- var PassStatus = Schema_exports.Literals(["running", "completed", "failed"]);
27622
+ var PassStatus = Schema_exports.Literals(["queued", "running", "completed", "failed"]);
27567
27623
  var ChangePass = class extends Schema_exports.Class("ChangePass")({
27568
27624
  changeId: ChangeId,
27569
27625
  kind: PassKind,
27570
27626
  status: PassStatus,
27571
27627
  detail: Schema_exports.NullOr(Schema_exports.String),
27572
27628
  findings: Schema_exports.NullOr(Schema_exports.Int),
27629
+ /** When it began running; for a `queued` row, when it was queued. */
27573
27630
  startedAt: Timestamp,
27574
27631
  finishedAt: Schema_exports.NullOr(Timestamp)
27575
27632
  }) {
@@ -27599,7 +27656,7 @@ var ProjectClusterBindingsSnapshot = class extends Schema_exports.Class(
27599
27656
  }) {
27600
27657
  };
27601
27658
 
27602
- // ../../node_modules/.pnpm/@sealant+api-contracts@0.37.0_effect@4.0.0-beta.93/node_modules/@sealant/api-contracts/dist/workspace-environment.js
27659
+ // ../../node_modules/.pnpm/@sealant+api-contracts@0.37.1_effect@4.0.0-beta.93/node_modules/@sealant/api-contracts/dist/workspace-environment.js
27603
27660
  var RESERVED_NAMES = new Map([
27604
27661
  // Identity/process roots the daemon and shells own.
27605
27662
  ...[
@@ -27934,12 +27991,20 @@ var ServiceView = class extends Schema_exports.Class("ServiceView")({
27934
27991
  }) {
27935
27992
  };
27936
27993
 
27994
+ // ../../packages/domain/src/workbench/session-fold.ts
27995
+ var servicesHoldLine = (input) => {
27996
+ if (input.agentLive || input.liveServices <= 0) return null;
27997
+ const hold = input.liveServices === 1 ? "1 service keeps the workspace up" : `${input.liveServices} services keep the workspace up`;
27998
+ return input.agentOutcome === null ? hold : `agent ${input.agentOutcome} \xB7 ${hold}`;
27999
+ };
28000
+
27937
28001
  // ../../packages/domain/src/workbench/session-control.ts
27938
28002
  var SessionControlKind = Schema_exports.Literals([
27939
28003
  "interrupt",
27940
28004
  "terminal-attach",
27941
28005
  "shell-open",
27942
28006
  "stop",
28007
+ "services-stop",
27943
28008
  "shared-control-on",
27944
28009
  "shared-control-off"
27945
28010
  ]);
@@ -28315,6 +28380,11 @@ var agentOutcome = (currentAgent) => {
28315
28380
  if (currentAgent.harness === "shell") return "completed";
28316
28381
  return currentAgent.exitCode === null || currentAgent.exitCode === 0 ? "completed" : "failed";
28317
28382
  };
28383
+ var servicesHoldOf = (session, currentAgent, liveServices) => servicesHoldLine({
28384
+ agentLive: agentIsLive(session, currentAgent),
28385
+ agentOutcome: agentOutcome(currentAgent),
28386
+ liveServices
28387
+ });
28318
28388
  var normalizeRemoteUrl = (raw) => {
28319
28389
  if (raw === null) return null;
28320
28390
  const trimmed = raw.trim();
@@ -28454,6 +28524,9 @@ export {
28454
28524
  narrowCredential,
28455
28525
  claudeGrantFacts,
28456
28526
  sameGrant,
28527
+ normalizeGitAuthor,
28528
+ gitAuthorIssue,
28529
+ servicesHoldLine,
28457
28530
  HARNESS_COMMANDS,
28458
28531
  pendingId,
28459
28532
  isPendingId,
@@ -28463,6 +28536,7 @@ export {
28463
28536
  LIVE_STATUSES,
28464
28537
  agentIsLive,
28465
28538
  agentOutcome,
28539
+ servicesHoldOf,
28466
28540
  normalizeRemoteUrl,
28467
28541
  matchProjectByCwd,
28468
28542
  cwdFacts,
@@ -2,6 +2,8 @@ import {
2
2
  HARNESS_COMMANDS,
3
3
  LIVE_STATUSES,
4
4
  RepositoryCloneUrl,
5
+ agentIsLive,
6
+ agentOutcome,
5
7
  createSseParser,
6
8
  cwdFacts,
7
9
  eventFamilies,
@@ -9,8 +11,9 @@ import {
9
11
  matchProjectByCwd,
10
12
  normalizeProjectName,
11
13
  pendingId,
12
- repositoryCloneUrlIssue
13
- } from "./chunk-BMFB2UN7.js";
14
+ repositoryCloneUrlIssue,
15
+ servicesHoldLine
16
+ } from "./chunk-J3TR4UAG.js";
14
17
 
15
18
  // src/dashboard.tsx
16
19
  import { CliRenderEvents, createCliRenderer } from "@opentui/core";
@@ -267,17 +270,31 @@ var createLaunchGate = () => {
267
270
  var markSessionStopped = (data, sessionId) => {
268
271
  const processesBySession = new Map(data.processesBySession);
269
272
  processesBySession.delete(sessionId);
270
- const servicesBySession = new Map(data.servicesBySession);
271
- servicesBySession.delete(sessionId);
272
273
  return {
273
274
  ...mapWorkbenchSessions(
274
275
  data,
275
276
  (session) => session.id === sessionId ? { ...session, status: "stopped" } : session
276
277
  ),
277
- processesBySession,
278
- servicesBySession
278
+ processesBySession
279
279
  };
280
280
  };
281
+ var markServicesStopped = (data, sessionId) => {
282
+ const servicesBySession = new Map(data.servicesBySession);
283
+ servicesBySession.delete(sessionId);
284
+ return { ...data, servicesBySession };
285
+ };
286
+ var AGENT_WORKING = /* @__PURE__ */ new Set(["starting", "running", "waiting"]);
287
+ var sessionHold = (item) => {
288
+ const agent = item.annotation?.currentAgent ?? null;
289
+ const agentProcessLive = item.processes.some(
290
+ (process2) => process2.kind !== "shell" && process2.exitedAt === null
291
+ );
292
+ return servicesHoldLine({
293
+ agentLive: agent === null ? agentProcessLive || AGENT_WORKING.has(item.session.status) : agentIsLive(item.session, agent),
294
+ agentOutcome: agentOutcome(agent),
295
+ liveServices: Math.max(item.annotation?.liveServices ?? 0, item.services.length)
296
+ });
297
+ };
281
298
  var removeWorktreeGroup = (data, projectId, group) => {
282
299
  const memberIds = new Set(group.sessions.map((item) => item.session.id));
283
300
  const detail = data.details.get(projectId);
@@ -995,6 +1012,7 @@ var stateColor = (state) => state === "draft" || state === "open" ? INK : state
995
1012
  var anchorOf = (comment) => comment.file === null ? "change" : `${comment.file}${comment.line === null ? "" : `:${comment.line}${comment.endLine === null || comment.endLine === comment.line ? "" : `-${comment.endLine}`}`}`;
996
1013
  var passFact = (pass) => {
997
1014
  const label = pass.kind === "suggest" ? "suggestions" : pass.kind;
1015
+ if (pass.status === "queued") return { text: `${label} queued`, color: MUTED };
998
1016
  if (pass.status === "running") return { text: `${label} running`, color: INK_2 };
999
1017
  if (pass.status === "failed") return { text: `${label} failed`, color: ERROR };
1000
1018
  return {
@@ -2257,13 +2275,15 @@ var SessionRow = ({
2257
2275
  const shells = processes.filter((process2) => process2.kind === "shell").length;
2258
2276
  const nameWidth = Math.max(6, width - 5 - status.length);
2259
2277
  const factWidth = Math.max(8, width - 3);
2278
+ const hold = sessionHold(item);
2260
2279
  const facts = fitHints(
2261
2280
  [
2281
+ ...hold === null ? [] : [hold],
2262
2282
  session.harness,
2263
2283
  shortAge(session.createdAt),
2264
2284
  ...shells > 0 ? [`${shells} shell`] : [],
2265
2285
  ...agents > 0 ? [`${agents} agent`] : [],
2266
- ...services.length > 0 ? [`${services.length} service`] : []
2286
+ ...hold === null && services.length > 0 ? [`${services.length} service`] : []
2267
2287
  ],
2268
2288
  factWidth
2269
2289
  );
@@ -2315,6 +2335,7 @@ var SessionFacts = ({
2315
2335
  }) => {
2316
2336
  const { session, annotation, services } = item;
2317
2337
  const color = STATUS_COLOR[session.status] ?? MUTED;
2338
+ const hold = sessionHold(item);
2318
2339
  const summary = session.summary?.split("\n")[0] ?? null;
2319
2340
  const change = annotation ?? group?.annotation;
2320
2341
  const lines = [
@@ -2326,6 +2347,10 @@ var SessionFacts = ({
2326
2347
  /* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
2327
2348
  /* @__PURE__ */ jsx3("span", { children: " " }),
2328
2349
  /* @__PURE__ */ jsx3("span", { fg: color, children: session.status }),
2350
+ hold === null ? null : /* @__PURE__ */ jsxs3(Fragment3, { children: [
2351
+ /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \xB7 " }),
2352
+ /* @__PURE__ */ jsx3("span", { fg: INK_2, children: hold })
2353
+ ] }),
2329
2354
  /* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \xB7 " }),
2330
2355
  /* @__PURE__ */ jsx3("span", { fg: MUTED, children: session.branch }),
2331
2356
  /* @__PURE__ */ jsx3("span", { fg: FAINT, children: session.baseSha === "" ? "" : ` vs ${session.baseRef ?? session.baseSha.slice(0, 12)}` })
@@ -2779,6 +2804,23 @@ ${tunneled}
2779
2804
  },
2780
2805
  onSettled: settleRefetch
2781
2806
  });
2807
+ const stopServicesMutation = useMutation2({
2808
+ mutationFn: (session) => ctx.api("POST", `/sessions/${session.id}/services/stop`),
2809
+ onMutate: async (session) => {
2810
+ await queryClient.cancelQueries({ queryKey: WORKBENCH_KEY });
2811
+ patchWorkbench((current) => markServicesStopped(current, session.id));
2812
+ },
2813
+ onSuccess: (result, session) => {
2814
+ say(
2815
+ `stopped ${result.stopped} service${result.stopped === 1 ? "" : "s"} \xB7 ${sessionDisplayName(session)} \u2014 the workspace ends once nothing is live`
2816
+ );
2817
+ },
2818
+ onError: (error) => {
2819
+ say(errorText(error));
2820
+ refetch();
2821
+ },
2822
+ onSettled: settleRefetch
2823
+ });
2782
2824
  const stopMutation = useMutation2({
2783
2825
  mutationFn: (session) => ctx.api("POST", `/sessions/${session.id}/stop`),
2784
2826
  onMutate: async (session) => {
@@ -2943,6 +2985,17 @@ ${tunneled}
2943
2985
  }
2944
2986
  const session = selectedSession;
2945
2987
  if (session === null || isPendingId(session.id)) return;
2988
+ const hold = selectedItem === null ? null : sessionHold(selectedItem);
2989
+ if (hold !== null) {
2990
+ if (stopArmed === `svc:${session.id}` && confirmationVisible("press \u21E7K again")) {
2991
+ setStopArmed(null);
2992
+ stopServicesMutation.mutate(session);
2993
+ return;
2994
+ }
2995
+ setStopArmed(`svc:${session.id}`);
2996
+ say(`press \u21E7K again to stop the services \xB7 ${sessionDisplayName(session)} \u2014 ${hold}`);
2997
+ return;
2998
+ }
2946
2999
  if (!LIVE_STATUSES.has(session.status)) {
2947
3000
  say("nothing to stop \u2014 the session is settled");
2948
3001
  return;