@join-pantheon/cli 0.29.1 → 0.31.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 (57) hide show
  1. package/README.md +142 -40
  2. package/bin/pantheon.js +12 -0
  3. package/dist/accounts.js +151 -26
  4. package/dist/agents.js +224 -17
  5. package/dist/claude-confine.js +299 -0
  6. package/dist/cli.js +1116 -137
  7. package/dist/client.js +294 -10
  8. package/dist/command-state.js +102 -0
  9. package/dist/config.js +287 -14
  10. package/dist/continued.js +42 -10
  11. package/dist/doctor.js +191 -23
  12. package/dist/file-lock.js +197 -0
  13. package/dist/grants.js +1 -1
  14. package/dist/history-view.js +2 -1
  15. package/dist/home-guard.js +565 -0
  16. package/dist/hooks-install.js +2 -1
  17. package/dist/identity.js +101 -1
  18. package/dist/install.js +76 -26
  19. package/dist/kiro-files.js +2 -2
  20. package/dist/ledger.js +111 -28
  21. package/dist/machine.js +494 -32
  22. package/dist/mcp-supervisor.js +29 -7
  23. package/dist/mcp-windows.js +47 -25
  24. package/dist/mcp.js +99 -36
  25. package/dist/network.js +39 -8
  26. package/dist/nonces.js +130 -0
  27. package/dist/notify.js +26 -9
  28. package/dist/pending.js +121 -22
  29. package/dist/persona.js +21 -0
  30. package/dist/pins.js +161 -0
  31. package/dist/profile.js +22 -1
  32. package/dist/protocol.js +40 -1
  33. package/dist/quickstart.js +173 -24
  34. package/dist/reads.js +37 -0
  35. package/dist/refusal.js +14 -1
  36. package/dist/relay.js +1341 -207
  37. package/dist/repo-find.js +127 -0
  38. package/dist/repo-route.js +70 -0
  39. package/dist/roster-file.js +190 -25
  40. package/dist/run-failure.js +99 -3
  41. package/dist/secret-guard.js +141 -0
  42. package/dist/service.js +91 -35
  43. package/dist/setup-guide.js +91 -31
  44. package/dist/spend.js +6 -2
  45. package/dist/store.js +94 -10
  46. package/dist/text-file.js +74 -0
  47. package/dist/tray.js +24 -3
  48. package/dist/untrusted.js +24 -5
  49. package/dist/update-check.js +2 -0
  50. package/dist/upgrade.js +9 -5
  51. package/dist/verify-act.js +102 -0
  52. package/dist/which.js +69 -12
  53. package/dist/worker-fleet.js +30 -9
  54. package/dist/worker-policy.js +231 -7
  55. package/dist/worker.js +1717 -130
  56. package/helper/macos/menubar.swift +63 -2
  57. package/package.json +2 -2
@@ -70,6 +70,8 @@ struct Item {
70
70
  let space: String
71
71
  /// whose human must decide this: this member for their own decision, the teammate for theirs, "" when it is not a decision
72
72
  let decider: String
73
+ /// one-click answers for a question this machine asked its own person (may a teammate wake it, may runs use commands)
74
+ var choices: [(label: String, answer: String)] = []
73
75
 
74
76
  var isDecision: Bool { kind == "decision" }
75
77
  var heading: String {
@@ -119,7 +121,7 @@ func run(_ launchPath: String, _ args: [String], timeout: Double = 10) -> (out:
119
121
 
120
122
  func fetchState() -> TrayState {
121
123
  var s = TrayState()
122
- guard let r = run(pantheonBin, ["tray-status"]), r.code == 0,
124
+ guard let r = run(pantheonBin, ["tray-status"], timeout: 20), r.code == 0,
123
125
  let data = r.out.data(using: .utf8),
124
126
  let j = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return s }
125
127
  s.reachable = true
@@ -155,7 +157,11 @@ func fetchState() -> TrayState {
155
157
  goal: i["goal"] as? String ?? "",
156
158
  ref: i["ref"] as? String ?? "",
157
159
  space: i["space"] as? String ?? "",
158
- decider: i["decider"] as? String ?? ""
160
+ decider: i["decider"] as? String ?? "",
161
+ choices: (i["choices"] as? [[String: Any]] ?? []).compactMap { c in
162
+ guard let l = c["label"] as? String, let a = c["answer"] as? String else { return nil }
163
+ return (label: l, answer: a)
164
+ }
159
165
  ))
160
166
  }
161
167
  for d in (j["decisions"] as? [[String: Any]] ?? []) {
@@ -477,12 +483,26 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
477
483
  }
478
484
  }
479
485
 
486
+ /// Consecutive checks that did not come back (timed out, or did not parse).
487
+ private var missedChecks = 0
480
488
  @objc func refresh() {
481
489
  guard !refreshing else { return }
482
490
  refreshing = true
483
491
  DispatchQueue.global().async {
484
492
  let next = fetchState()
485
493
  DispatchQueue.main.async {
494
+ // A check that did not come back is not news that the relay is down: on a
495
+ // busy machine `pantheon tray-status` can take longer than its limit, and
496
+ // the mark flipped to the slashed "unreachable" state with a "!" — while
497
+ // the relay, the team and every decision were fine. What was last known is
498
+ // kept through two misses; only a third in a row is shown as "unable to
499
+ // check". A check that did come back always wins.
500
+ if !next.reachable && self.hasLoaded && self.state.reachable && self.missedChecks < 2 {
501
+ self.missedChecks += 1
502
+ self.refreshing = false
503
+ return
504
+ }
505
+ self.missedChecks = next.reachable ? 0 : self.missedChecks + 1
486
506
  self.state = next
487
507
  self.hasLoaded = true
488
508
  self.refreshing = false
@@ -1175,6 +1195,22 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
1175
1195
  rows.spacing = 8
1176
1196
  rows.translatesAutoresizingMaskIntoConstraints = false
1177
1197
 
1198
+ // A question this machine asked its own person: answered in one click, here.
1199
+ if item.isDecision && !item.choices.isEmpty {
1200
+ let row0 = NSStackView()
1201
+ row0.orientation = .horizontal
1202
+ row0.spacing = 10
1203
+ for (n, c) in item.choices.enumerated() {
1204
+ let b = button(c.label, #selector(panelChoose(_:)), "Answers it on this machine: \(c.answer)")
1205
+ b.tag = n
1206
+ // No Return key here: a habitual Enter must never grant "Always". A
1207
+ // permission is a click on the words that give it.
1208
+ b.keyEquivalent = ""
1209
+ row0.addArrangedSubview(b)
1210
+ }
1211
+ rows.addArrangedSubview(row0)
1212
+ }
1213
+
1178
1214
  let row1 = NSStackView()
1179
1215
  row1.orientation = .horizontal
1180
1216
  row1.spacing = 10
@@ -1305,6 +1341,31 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
1305
1341
  switch title { case "Answer": return "ANSWER"; case "Ask back": return "ASK"; default: return "NOTE" }
1306
1342
  }
1307
1343
 
1344
+ /// One of the question's own answers, sent as its ANSWER — the same local path
1345
+ /// a typed reply takes, so a grant it carries takes effect on this machine.
1346
+ @objc private func panelChoose(_ sender: NSButton) {
1347
+ guard let item = panelItem, sender.tag >= 0, sender.tag < item.choices.count else { return }
1348
+ let c = item.choices[sender.tag]
1349
+ var payload: [String: Any] = ["action": "reply", "kind": "ANSWER", "body": c.answer, "goal": item.goal, "space": item.space, "decision": item.id]
1350
+ if !item.ref.isEmpty { payload["act"] = item.ref }
1351
+ guard writeAction(payload) else { setStatus("Could not write the action. Use Show the thread instead."); return }
1352
+ setStatus("Answering…")
1353
+ sender.isEnabled = false
1354
+ DispatchQueue.global().async {
1355
+ let r = run(pantheonBin, ["explain", "--queued"], timeout: 25)
1356
+ DispatchQueue.main.async {
1357
+ sender.isEnabled = true
1358
+ let out = (r?.out ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
1359
+ if r?.code == 0 {
1360
+ self.setStatus("\(c.label) — done.")
1361
+ self.refresh()
1362
+ } else {
1363
+ self.setStatus(out.hasPrefix("not sent: ") ? String(out.dropFirst("not sent: ".count)) : "the relay could not be reached")
1364
+ }
1365
+ }
1366
+ }
1367
+ }
1368
+
1308
1369
  /// Send the words in the box as the chosen act. The CLI does the sending and
1309
1370
  /// prints "sent KIND id=… seq=…" or "not sent: why" on stdout; both are shown.
1310
1371
  @objc private func panelSend(_ sender: Any?) {
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@join-pantheon/cli",
3
- "version": "0.29.1",
3
+ "version": "0.31.0",
4
4
  "description": "Let your coding agent collaborate directly with your teammates' agents — ask, agree an interface, and deliver, without a human relaying messages.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "pantheon": "bin/pantheon.js"
8
8
  },
9
9
  "engines": {
10
- "node": ">=22"
10
+ "node": ">=22.13"
11
11
  },
12
12
  "scripts": {
13
13
  "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p .",