youmd 0.10.2 → 0.12.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 (205) hide show
  1. package/dist/commands/brain.d.ts.map +1 -1
  2. package/dist/commands/brain.js +91 -28
  3. package/dist/commands/brain.js.map +1 -1
  4. package/dist/commands/chat.d.ts.map +1 -1
  5. package/dist/commands/chat.js +6 -1
  6. package/dist/commands/chat.js.map +1 -1
  7. package/dist/commands/conversations.d.ts +5 -0
  8. package/dist/commands/conversations.d.ts.map +1 -0
  9. package/dist/commands/conversations.js +116 -0
  10. package/dist/commands/conversations.js.map +1 -0
  11. package/dist/commands/doctor.d.ts +1 -0
  12. package/dist/commands/doctor.d.ts.map +1 -1
  13. package/dist/commands/doctor.js +16 -1
  14. package/dist/commands/doctor.js.map +1 -1
  15. package/dist/commands/founder.d.ts +13 -0
  16. package/dist/commands/founder.d.ts.map +1 -0
  17. package/dist/commands/founder.js +189 -0
  18. package/dist/commands/founder.js.map +1 -0
  19. package/dist/commands/handoff.d.ts +5 -0
  20. package/dist/commands/handoff.d.ts.map +1 -0
  21. package/dist/commands/handoff.js +432 -0
  22. package/dist/commands/handoff.js.map +1 -0
  23. package/dist/commands/orchestrate.d.ts +15 -0
  24. package/dist/commands/orchestrate.d.ts.map +1 -1
  25. package/dist/commands/orchestrate.js +294 -0
  26. package/dist/commands/orchestrate.js.map +1 -1
  27. package/dist/commands/project.d.ts.map +1 -1
  28. package/dist/commands/project.js +8 -1
  29. package/dist/commands/project.js.map +1 -1
  30. package/dist/commands/reconcile.d.ts +15 -0
  31. package/dist/commands/reconcile.d.ts.map +1 -0
  32. package/dist/commands/reconcile.js +260 -0
  33. package/dist/commands/reconcile.js.map +1 -0
  34. package/dist/commands/session.d.ts +28 -0
  35. package/dist/commands/session.d.ts.map +1 -0
  36. package/dist/commands/session.js +895 -0
  37. package/dist/commands/session.js.map +1 -0
  38. package/dist/commands/stack.d.ts +2 -0
  39. package/dist/commands/stack.d.ts.map +1 -1
  40. package/dist/commands/stack.js +26 -0
  41. package/dist/commands/stack.js.map +1 -1
  42. package/dist/commands/sync.d.ts.map +1 -1
  43. package/dist/commands/sync.js +11 -0
  44. package/dist/commands/sync.js.map +1 -1
  45. package/dist/commands/tasks.d.ts +57 -0
  46. package/dist/commands/tasks.d.ts.map +1 -1
  47. package/dist/commands/tasks.js +236 -31
  48. package/dist/commands/tasks.js.map +1 -1
  49. package/dist/generated/capability-registry.d.ts +311 -71
  50. package/dist/generated/capability-registry.d.ts.map +1 -1
  51. package/dist/generated/capability-registry.js +346 -71
  52. package/dist/generated/capability-registry.js.map +1 -1
  53. package/dist/index.js +113 -2
  54. package/dist/index.js.map +1 -1
  55. package/dist/lib/api.d.ts +362 -1
  56. package/dist/lib/api.d.ts.map +1 -1
  57. package/dist/lib/api.js +171 -0
  58. package/dist/lib/api.js.map +1 -1
  59. package/dist/lib/brain/adapters.d.ts +20 -1
  60. package/dist/lib/brain/adapters.d.ts.map +1 -1
  61. package/dist/lib/brain/adapters.js +113 -2
  62. package/dist/lib/brain/adapters.js.map +1 -1
  63. package/dist/lib/brain/approval-key-trust.d.ts +29 -0
  64. package/dist/lib/brain/approval-key-trust.d.ts.map +1 -0
  65. package/dist/lib/brain/approval-key-trust.js +139 -0
  66. package/dist/lib/brain/approval-key-trust.js.map +1 -0
  67. package/dist/lib/brain/cache.d.ts +2 -2
  68. package/dist/lib/brain/cache.d.ts.map +1 -1
  69. package/dist/lib/brain/cache.js +4 -4
  70. package/dist/lib/brain/cache.js.map +1 -1
  71. package/dist/lib/brain/content.d.ts +56 -0
  72. package/dist/lib/brain/content.d.ts.map +1 -0
  73. package/dist/lib/brain/content.js +362 -0
  74. package/dist/lib/brain/content.js.map +1 -0
  75. package/dist/lib/brain/descriptor-validator.d.ts +11 -0
  76. package/dist/lib/brain/descriptor-validator.d.ts.map +1 -0
  77. package/dist/lib/brain/descriptor-validator.js +185 -0
  78. package/dist/lib/brain/descriptor-validator.js.map +1 -0
  79. package/dist/lib/brain/discovery.d.ts.map +1 -1
  80. package/dist/lib/brain/discovery.js +103 -60
  81. package/dist/lib/brain/discovery.js.map +1 -1
  82. package/dist/lib/brain/index.d.ts +2 -0
  83. package/dist/lib/brain/index.d.ts.map +1 -1
  84. package/dist/lib/brain/index.js +8 -2
  85. package/dist/lib/brain/index.js.map +1 -1
  86. package/dist/lib/brain/proposal.d.ts +53 -5
  87. package/dist/lib/brain/proposal.d.ts.map +1 -1
  88. package/dist/lib/brain/proposal.js +341 -64
  89. package/dist/lib/brain/proposal.js.map +1 -1
  90. package/dist/lib/brain/safe-file.d.ts +25 -1
  91. package/dist/lib/brain/safe-file.d.ts.map +1 -1
  92. package/dist/lib/brain/safe-file.js +250 -30
  93. package/dist/lib/brain/safe-file.js.map +1 -1
  94. package/dist/lib/brain/safe-open.d.ts +2 -0
  95. package/dist/lib/brain/safe-open.d.ts.map +1 -0
  96. package/dist/lib/brain/safe-open.js +77 -0
  97. package/dist/lib/brain/safe-open.js.map +1 -0
  98. package/dist/lib/brain/safety.d.ts.map +1 -1
  99. package/dist/lib/brain/safety.js +5 -0
  100. package/dist/lib/brain/safety.js.map +1 -1
  101. package/dist/lib/brain/staged-apply.d.ts +2 -1
  102. package/dist/lib/brain/staged-apply.d.ts.map +1 -1
  103. package/dist/lib/brain/staged-apply.js +6 -3
  104. package/dist/lib/brain/staged-apply.js.map +1 -1
  105. package/dist/lib/brain/store.d.ts +3 -0
  106. package/dist/lib/brain/store.d.ts.map +1 -1
  107. package/dist/lib/brain/store.js +68 -0
  108. package/dist/lib/brain/store.js.map +1 -1
  109. package/dist/lib/brain/sync.d.ts +87 -0
  110. package/dist/lib/brain/sync.d.ts.map +1 -0
  111. package/dist/lib/brain/sync.js +238 -0
  112. package/dist/lib/brain/sync.js.map +1 -0
  113. package/dist/lib/brain/types.d.ts +23 -0
  114. package/dist/lib/brain/types.d.ts.map +1 -1
  115. package/dist/lib/config.d.ts +7 -0
  116. package/dist/lib/config.d.ts.map +1 -1
  117. package/dist/lib/config.js +3 -0
  118. package/dist/lib/config.js.map +1 -1
  119. package/dist/lib/daemon.js +1 -1
  120. package/dist/lib/daemon.js.map +1 -1
  121. package/dist/lib/goalRegistryFile.d.ts +169 -0
  122. package/dist/lib/goalRegistryFile.d.ts.map +1 -0
  123. package/dist/lib/goalRegistryFile.js +518 -0
  124. package/dist/lib/goalRegistryFile.js.map +1 -0
  125. package/dist/lib/goalsFile.d.ts +101 -0
  126. package/dist/lib/goalsFile.d.ts.map +1 -0
  127. package/dist/lib/goalsFile.js +413 -0
  128. package/dist/lib/goalsFile.js.map +1 -0
  129. package/dist/lib/handoff.d.ts +170 -0
  130. package/dist/lib/handoff.d.ts.map +1 -0
  131. package/dist/lib/handoff.js +316 -0
  132. package/dist/lib/handoff.js.map +1 -0
  133. package/dist/lib/machine-identity.d.ts +2 -0
  134. package/dist/lib/machine-identity.d.ts.map +1 -1
  135. package/dist/lib/machine-identity.js +13 -6
  136. package/dist/lib/machine-identity.js.map +1 -1
  137. package/dist/lib/onboarding.d.ts +4 -1
  138. package/dist/lib/onboarding.d.ts.map +1 -1
  139. package/dist/lib/onboarding.js +11 -3
  140. package/dist/lib/onboarding.js.map +1 -1
  141. package/dist/lib/orchestrator/loop.d.ts +7 -0
  142. package/dist/lib/orchestrator/loop.d.ts.map +1 -1
  143. package/dist/lib/orchestrator/loop.js +53 -0
  144. package/dist/lib/orchestrator/loop.js.map +1 -1
  145. package/dist/lib/orchestrator/run-journal.d.ts +178 -0
  146. package/dist/lib/orchestrator/run-journal.d.ts.map +1 -0
  147. package/dist/lib/orchestrator/run-journal.js +912 -0
  148. package/dist/lib/orchestrator/run-journal.js.map +1 -0
  149. package/dist/lib/orchestrator/supervisor.d.ts +11 -0
  150. package/dist/lib/orchestrator/supervisor.d.ts.map +1 -1
  151. package/dist/lib/orchestrator/supervisor.js +304 -116
  152. package/dist/lib/orchestrator/supervisor.js.map +1 -1
  153. package/dist/lib/orchestrator/tools.d.ts +2 -0
  154. package/dist/lib/orchestrator/tools.d.ts.map +1 -1
  155. package/dist/lib/orchestrator/tools.js +214 -1
  156. package/dist/lib/orchestrator/tools.js.map +1 -1
  157. package/dist/lib/redact.d.ts.map +1 -1
  158. package/dist/lib/redact.js +31 -6
  159. package/dist/lib/redact.js.map +1 -1
  160. package/dist/lib/remote-agent-event.d.ts +19 -0
  161. package/dist/lib/remote-agent-event.d.ts.map +1 -0
  162. package/dist/lib/remote-agent-event.js +85 -0
  163. package/dist/lib/remote-agent-event.js.map +1 -0
  164. package/dist/lib/remote-command.d.ts.map +1 -1
  165. package/dist/lib/remote-command.js +19 -0
  166. package/dist/lib/remote-command.js.map +1 -1
  167. package/dist/lib/remote-executor.d.ts +7 -1
  168. package/dist/lib/remote-executor.d.ts.map +1 -1
  169. package/dist/lib/remote-executor.js +391 -0
  170. package/dist/lib/remote-executor.js.map +1 -1
  171. package/dist/lib/sessionCheckpoint.d.ts +104 -0
  172. package/dist/lib/sessionCheckpoint.d.ts.map +1 -0
  173. package/dist/lib/sessionCheckpoint.js +191 -0
  174. package/dist/lib/sessionCheckpoint.js.map +1 -0
  175. package/dist/lib/sessionHandoff.d.ts +47 -0
  176. package/dist/lib/sessionHandoff.d.ts.map +1 -0
  177. package/dist/lib/sessionHandoff.js +305 -0
  178. package/dist/lib/sessionHandoff.js.map +1 -0
  179. package/dist/lib/stream.d.ts +3 -1
  180. package/dist/lib/stream.d.ts.map +1 -1
  181. package/dist/lib/stream.js +8 -3
  182. package/dist/lib/stream.js.map +1 -1
  183. package/dist/lib/taskLedgerProposal.d.ts +102 -0
  184. package/dist/lib/taskLedgerProposal.d.ts.map +1 -0
  185. package/dist/lib/taskLedgerProposal.js +408 -0
  186. package/dist/lib/taskLedgerProposal.js.map +1 -0
  187. package/dist/lib/tasksFile.d.ts +23 -2
  188. package/dist/lib/tasksFile.d.ts.map +1 -1
  189. package/dist/lib/tasksFile.js +122 -10
  190. package/dist/lib/tasksFile.js.map +1 -1
  191. package/dist/mcp/registry.d.ts.map +1 -1
  192. package/dist/mcp/registry.js +110 -0
  193. package/dist/mcp/registry.js.map +1 -1
  194. package/native/prebuilds/darwin-universal/youmd_safe_open.node +0 -0
  195. package/native/safe-open/youmd_safe_open.c +224 -0
  196. package/package.json +11 -3
  197. package/scripts/build-safe-open.mjs +63 -0
  198. package/scripts/skillstack-sync/com.you.context-sync.plist +1 -1
  199. package/scripts/skillstack-sync/com.you.identity-sync.plist +1 -1
  200. package/scripts/skillstack-sync/com.you.orchestrator-watch.plist +1 -1
  201. package/scripts/skillstack-sync/com.you.realtime-sync.plist +7 -1
  202. package/scripts/skillstack-sync/com.you.skillstack-sync.plist +1 -1
  203. package/scripts/skillstack-sync/context-sync.sh +19 -7
  204. package/scripts/skillstack-sync/install-daemons-linux.sh +6 -0
  205. package/scripts/smoke-safe-open-node.cjs +50 -0
@@ -0,0 +1,895 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.scanCodexSessions = scanCodexSessions;
40
+ exports.scanClaudeSessions = scanClaudeSessions;
41
+ exports.sessionListCommand = sessionListCommand;
42
+ exports.sessionPublishCommand = sessionPublishCommand;
43
+ exports.sessionResumeCommand = sessionResumeCommand;
44
+ exports.sessionCloseCommand = sessionCloseCommand;
45
+ exports.sessionCommand = sessionCommand;
46
+ const fs = __importStar(require("fs"));
47
+ const os = __importStar(require("os"));
48
+ const path = __importStar(require("path"));
49
+ const crypto_1 = require("crypto");
50
+ const child_process_1 = require("child_process");
51
+ const chalk_1 = __importDefault(require("chalk"));
52
+ const api_1 = require("../lib/api");
53
+ const config_1 = require("../lib/config");
54
+ const render_1 = require("../lib/render");
55
+ const machine_identity_1 = require("../lib/machine-identity");
56
+ const sessionCheckpoint_1 = require("../lib/sessionCheckpoint");
57
+ const redact_1 = require("../lib/redact");
58
+ const sessionHandoff_1 = require("../lib/sessionHandoff");
59
+ const ACCENT = chalk_1.default.hex("#C46A3A");
60
+ const DIM = chalk_1.default.dim;
61
+ const GREEN = chalk_1.default.hex("#4CAF50");
62
+ // Synced coding-agent sessions (request 176): publish bounded, secret-safe
63
+ // snapshots of the Codex/Claude Code sessions running on THIS machine so any
64
+ // other paired machine can `you session list` and `you session resume` them.
65
+ // Raw transcripts never leave this machine — resume rides on repo sync plus
66
+ // the runtime's own local/session storage; what syncs is the map, not the
67
+ // territory.
68
+ const MAX_LOCAL_SESSIONS = 12;
69
+ const FRESH_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
70
+ const MAX_TAIL_BYTES = 128 * 1024;
71
+ const MAX_TAIL_ROWS = 120;
72
+ function relativeTime(ts) {
73
+ const s = Math.floor((Date.now() - ts) / 1000);
74
+ if (s < 60)
75
+ return `${s}s ago`;
76
+ const m = Math.floor(s / 60);
77
+ if (m < 60)
78
+ return `${m}m ago`;
79
+ const h = Math.floor(m / 60);
80
+ if (h < 24)
81
+ return `${h}h ago`;
82
+ return `${Math.floor(h / 24)}d ago`;
83
+ }
84
+ function safeReadFirstJsonLines(filePath, maxLines = 8) {
85
+ try {
86
+ const fd = fs.openSync(filePath, "r");
87
+ const buffer = Buffer.alloc(64 * 1024);
88
+ const bytes = fs.readSync(fd, buffer, 0, buffer.length, 0);
89
+ fs.closeSync(fd);
90
+ const rows = [];
91
+ for (const line of buffer.toString("utf8", 0, bytes).split("\n").slice(0, maxLines)) {
92
+ const trimmed = line.trim();
93
+ if (!trimmed)
94
+ continue;
95
+ try {
96
+ const parsed = JSON.parse(trimmed);
97
+ rows.push(parsed);
98
+ }
99
+ catch {
100
+ break; // truncated tail of the read window — fine, we have the head
101
+ }
102
+ }
103
+ return rows;
104
+ }
105
+ catch {
106
+ return [];
107
+ }
108
+ }
109
+ function safeReadLastJsonLines(filePath, maxBytes = MAX_TAIL_BYTES, maxLines = MAX_TAIL_ROWS) {
110
+ let fd;
111
+ try {
112
+ fd = fs.openSync(filePath, "r");
113
+ const size = fs.fstatSync(fd).size;
114
+ const start = Math.max(0, size - maxBytes);
115
+ const buffer = Buffer.alloc(Math.min(size, maxBytes));
116
+ const bytes = fs.readSync(fd, buffer, 0, buffer.length, start);
117
+ let lines = buffer.toString("utf8", 0, bytes).split("\n");
118
+ // A non-zero offset normally begins halfway through a JSON row.
119
+ if (start > 0)
120
+ lines = lines.slice(1);
121
+ const rows = [];
122
+ for (const line of lines.slice(-maxLines)) {
123
+ const trimmed = line.trim();
124
+ if (!trimmed)
125
+ continue;
126
+ try {
127
+ const parsed = JSON.parse(trimmed);
128
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
129
+ rows.push(parsed);
130
+ }
131
+ }
132
+ catch {
133
+ // Concurrently-appended or partial rows are not portable evidence.
134
+ }
135
+ }
136
+ return rows;
137
+ }
138
+ catch {
139
+ return [];
140
+ }
141
+ finally {
142
+ if (fd !== undefined) {
143
+ try {
144
+ fs.closeSync(fd);
145
+ }
146
+ catch {
147
+ // The tail is optional local evidence.
148
+ }
149
+ }
150
+ }
151
+ }
152
+ const CONTEXT_LABELS = [
153
+ { pattern: "now|current|working\\s+on", bucket: "now" },
154
+ { pattern: "recent|completed|done|shipped", bucket: "recent" },
155
+ { pattern: "next|up\\s+next|continue|todo", bucket: "next" },
156
+ { pattern: "blockers?|blocked", bucket: "blockers" },
157
+ { pattern: "decisions?|decided", bucket: "decisions" },
158
+ ];
159
+ function textParts(content) {
160
+ if (typeof content === "string")
161
+ return [content];
162
+ if (!Array.isArray(content))
163
+ return [];
164
+ return content.flatMap((part) => {
165
+ if (!part || typeof part !== "object")
166
+ return [];
167
+ const item = part;
168
+ // Only human-visible message text is admissible. Tool calls/results,
169
+ // reasoning, images, and runtime metadata are ignored.
170
+ if (!["text", "input_text", "output_text"].includes(String(item.type)))
171
+ return [];
172
+ return typeof item.text === "string" ? [item.text] : [];
173
+ });
174
+ }
175
+ function codexPortableTexts(row) {
176
+ const payload = row.payload && typeof row.payload === "object"
177
+ ? row.payload
178
+ : undefined;
179
+ if (!payload)
180
+ return [];
181
+ if (payload.type === "message" && ["user", "assistant"].includes(String(payload.role))) {
182
+ return textParts(payload.content).map((text) => ({
183
+ text,
184
+ summary: false,
185
+ userAuthored: payload.role === "user",
186
+ }));
187
+ }
188
+ if (row.type === "event_msg"
189
+ && ["agent_message", "task_summary"].includes(String(payload.type))
190
+ && typeof payload.message === "string") {
191
+ return [{
192
+ text: payload.message,
193
+ summary: payload.type === "task_summary",
194
+ userAuthored: false,
195
+ }];
196
+ }
197
+ if (payload.type === "task_summary" && typeof payload.summary === "string") {
198
+ return [{ text: payload.summary, summary: true, userAuthored: false }];
199
+ }
200
+ return [];
201
+ }
202
+ function claudePortableTexts(row) {
203
+ if (row.isMeta === true)
204
+ return [];
205
+ if (row.type === "summary" && typeof row.summary === "string") {
206
+ return [{ text: row.summary, summary: true, userAuthored: false }];
207
+ }
208
+ if (!["user", "assistant"].includes(String(row.type)))
209
+ return [];
210
+ const message = row.message && typeof row.message === "object"
211
+ ? row.message
212
+ : undefined;
213
+ if (!message || !["user", "assistant"].includes(String(message.role)))
214
+ return [];
215
+ return textParts(message.content).map((text) => ({
216
+ text,
217
+ summary: false,
218
+ userAuthored: message.role === "user",
219
+ }));
220
+ }
221
+ function safePortableLine(value, nativeSessionId) {
222
+ const normalized = value.replace(/\s+/g, " ").trim();
223
+ if (!normalized)
224
+ return null;
225
+ try {
226
+ const parsed = JSON.parse(normalized);
227
+ if (parsed && typeof parsed === "object")
228
+ return null;
229
+ }
230
+ catch {
231
+ // Ordinary human-authored text is the expected portable summary.
232
+ }
233
+ if (nativeSessionId && normalized.includes(nativeSessionId))
234
+ return null;
235
+ if (/(?:^|\s)(?:\/Users\/|\/home\/|\/var\/|\/tmp\/|~\/|[A-Za-z]:\\)/.test(normalized)
236
+ || /(?:^|\s)\.env(?:\.local)?(?:\s|$)/i.test(normalized)
237
+ || /\b[A-Z][A-Z0-9_]{2,}\s*=/.test(normalized)) {
238
+ return null;
239
+ }
240
+ const redacted = (0, redact_1.redactText)(normalized);
241
+ // A whole prompt/line containing a secret-like value is excluded instead
242
+ // of publishing a partially-redacted continuation with ambiguous meaning.
243
+ if (redacted.hits > 0)
244
+ return null;
245
+ return redacted.text
246
+ .replace(/\b[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/gi, "[opaque-ref]")
247
+ .slice(0, 500);
248
+ }
249
+ function extractPortableContext(rows, runtime, nativeSessionId) {
250
+ const result = {
251
+ recent: [],
252
+ next: [],
253
+ blockers: [],
254
+ decisions: [],
255
+ };
256
+ const texts = rows.flatMap(runtime === "codex" ? codexPortableTexts : claudePortableTexts);
257
+ let latestSummary;
258
+ for (const entry of texts) {
259
+ // A user prompt containing any recognized secret/PII shape is treated as
260
+ // indivisible and omitted wholesale, even if another line looks harmless.
261
+ if (entry.userAuthored && (0, redact_1.redactText)(entry.text).hits > 0)
262
+ continue;
263
+ if (entry.summary) {
264
+ const summary = safePortableLine(entry.text, nativeSessionId);
265
+ if (summary)
266
+ latestSummary = summary;
267
+ }
268
+ for (const rawLine of entry.text.split(/\r?\n/)) {
269
+ const line = rawLine.replace(/^\s*(?:[-*]\s*|#{1,6}\s*)?/, "");
270
+ for (const label of CONTEXT_LABELS) {
271
+ const match = line.match(new RegExp(`^(?:${label.pattern})\\s*:\\s*(.+)$`, "i"));
272
+ if (!match)
273
+ continue;
274
+ const safe = safePortableLine(match[1], nativeSessionId);
275
+ if (!safe)
276
+ break;
277
+ if (label.bucket === "now")
278
+ result.now = safe;
279
+ else
280
+ result[label.bucket].push(safe);
281
+ break;
282
+ }
283
+ }
284
+ }
285
+ if (!result.now && latestSummary)
286
+ result.now = latestSummary;
287
+ for (const bucket of ["recent", "next", "blockers", "decisions"]) {
288
+ result[bucket] = [...new Set(result[bucket])].slice(-8);
289
+ }
290
+ return result;
291
+ }
292
+ function gitBranchFor(workspacePath) {
293
+ if (!workspacePath)
294
+ return undefined;
295
+ try {
296
+ return (0, child_process_1.execSync)("git rev-parse --abbrev-ref HEAD", {
297
+ cwd: workspacePath,
298
+ stdio: ["ignore", "pipe", "ignore"],
299
+ timeout: 3000,
300
+ })
301
+ .toString()
302
+ .trim() || undefined;
303
+ }
304
+ catch {
305
+ return undefined;
306
+ }
307
+ }
308
+ function gitOutput(workspacePath, args) {
309
+ try {
310
+ return (0, child_process_1.execSync)(`git ${args.join(" ")}`, {
311
+ cwd: workspacePath,
312
+ stdio: ["ignore", "pipe", "ignore"],
313
+ timeout: 3000,
314
+ })
315
+ .toString()
316
+ .trim() || undefined;
317
+ }
318
+ catch {
319
+ return undefined;
320
+ }
321
+ }
322
+ function canonicalRepoRef(remote) {
323
+ if (!remote)
324
+ return undefined;
325
+ const withoutCredentials = remote.replace(/^(https?:\/\/)[^/@]+@/i, "$1");
326
+ const normalized = withoutCredentials
327
+ .replace(/^git@([^:]+):/i, "$1/")
328
+ .replace(/^ssh:\/\/git@/i, "")
329
+ .replace(/^https?:\/\//i, "")
330
+ .replace(/\.git$/i, "")
331
+ .replace(/\/+$/, "");
332
+ if (!normalized || normalized.startsWith("/"))
333
+ return undefined;
334
+ return `repo:${normalized.toLowerCase()}`;
335
+ }
336
+ function inspectGitCheckpoint(workspacePath, machineRef) {
337
+ if (!workspacePath)
338
+ return {};
339
+ const branch = gitBranchFor(workspacePath);
340
+ const lastCommit = gitOutput(workspacePath, ["rev-parse", "HEAD"]);
341
+ const status = gitOutput(workspacePath, ["status", "--porcelain=v1"]) ?? "";
342
+ const dirtyFiles = status ? status.split("\n").filter(Boolean).length : 0;
343
+ const statusDigest = status ? `sha256:${(0, sessionCheckpoint_1.stableCheckpointHash)(status)}` : undefined;
344
+ const upstreamCounts = gitOutput(workspacePath, [
345
+ "rev-list",
346
+ "--left-right",
347
+ "--count",
348
+ "@{upstream}...HEAD",
349
+ ]);
350
+ const [behindRaw, aheadRaw] = upstreamCounts?.split(/\s+/) ?? [];
351
+ const behind = Number.parseInt(behindRaw ?? "", 10);
352
+ const ahead = Number.parseInt(aheadRaw ?? "", 10);
353
+ const repoRef = canonicalRepoRef(gitOutput(workspacePath, ["remote", "get-url", "origin"]));
354
+ const topLevel = gitOutput(workspacePath, ["rev-parse", "--show-toplevel"]);
355
+ const worktreeRef = topLevel
356
+ ? `worktree:${(0, sessionCheckpoint_1.stableCheckpointHash)({ machineRef, repoRef, branch, topLevel })}`
357
+ : undefined;
358
+ const gitState = {
359
+ dirtyFiles,
360
+ ...(Number.isFinite(ahead) ? { ahead } : {}),
361
+ ...(Number.isFinite(behind) ? { behind } : {}),
362
+ ...(lastCommit ? { lastCommit } : {}),
363
+ ...(statusDigest ? { statusDigest } : {}),
364
+ };
365
+ return { branch, repoRef, worktreeRef, gitState };
366
+ }
367
+ function hashFile(filePath) {
368
+ const hash = (0, crypto_1.createHash)("sha256");
369
+ const fd = fs.openSync(filePath, "r");
370
+ const buffer = Buffer.alloc(64 * 1024);
371
+ try {
372
+ let position = 0;
373
+ while (true) {
374
+ const bytes = fs.readSync(fd, buffer, 0, buffer.length, position);
375
+ if (bytes === 0)
376
+ break;
377
+ hash.update(buffer.subarray(0, bytes));
378
+ position += bytes;
379
+ }
380
+ }
381
+ finally {
382
+ fs.closeSync(fd);
383
+ }
384
+ return `sha256:${hash.digest("hex")}`;
385
+ }
386
+ function portableMachineRef(hostname, physicalMachineRef) {
387
+ return physicalMachineRef || `machine:${(0, sessionCheckpoint_1.stableCheckpointHash)(hostname.toLowerCase())}`;
388
+ }
389
+ function portableProjectSlug(cwd) {
390
+ if (!cwd)
391
+ return undefined;
392
+ const slug = path.basename(cwd)
393
+ .normalize("NFKD")
394
+ .replace(/[\u0300-\u036f]/g, "")
395
+ .toLowerCase()
396
+ .replace(/[^a-z0-9._-]+/g, "-")
397
+ .replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, "")
398
+ .slice(0, 80)
399
+ .replace(/[^a-z0-9]+$/g, "");
400
+ return /^[a-z0-9](?:[a-z0-9._-]{0,78}[a-z0-9])?$/.test(slug)
401
+ ? slug
402
+ : undefined;
403
+ }
404
+ function listRecentFiles(dir, suffix, limit) {
405
+ const out = [];
406
+ const walk = (current, depth) => {
407
+ if (depth > 4 || out.length > limit * 8)
408
+ return;
409
+ let entries;
410
+ try {
411
+ entries = fs.readdirSync(current, { withFileTypes: true });
412
+ }
413
+ catch {
414
+ return;
415
+ }
416
+ for (const entry of entries) {
417
+ const full = path.join(current, entry.name);
418
+ if (entry.isDirectory()) {
419
+ walk(full, depth + 1);
420
+ }
421
+ else if (entry.isFile() && entry.name.endsWith(suffix)) {
422
+ try {
423
+ const mtime = fs.statSync(full).mtimeMs;
424
+ if (Date.now() - mtime <= FRESH_WINDOW_MS)
425
+ out.push({ file: full, mtime });
426
+ }
427
+ catch {
428
+ /* unreadable file — skip */
429
+ }
430
+ }
431
+ }
432
+ };
433
+ walk(dir, 0);
434
+ return out
435
+ .sort((a, b) => b.mtime - a.mtime)
436
+ .slice(0, limit)
437
+ .map((entry) => entry.file);
438
+ }
439
+ // Codex rollouts: ~/.codex/sessions/YYYY/MM/DD/rollout-...-<uuid>.jsonl
440
+ function scanCodexSessions(hostname, root = path.join(os.homedir(), ".codex", "sessions"), physicalMachineRef, handoffBaseDir) {
441
+ if (!fs.existsSync(root))
442
+ return [];
443
+ const sessions = [];
444
+ for (const file of listRecentFiles(root, ".jsonl", MAX_LOCAL_SESSIONS)) {
445
+ const head = safeReadFirstJsonLines(file);
446
+ const meta = head.find((row) => row.payload && typeof row.payload === "object");
447
+ const payload = (meta?.payload ?? head[0] ?? {});
448
+ const id = (typeof payload.id === "string" && payload.id) ||
449
+ path.basename(file).replace(/\.jsonl$/, "").split("-").slice(-5).join("-");
450
+ const cwd = typeof payload.cwd === "string" ? payload.cwd : undefined;
451
+ let mtime = Date.now();
452
+ let startedAt = mtime;
453
+ try {
454
+ const stat = fs.statSync(file);
455
+ mtime = stat.mtimeMs;
456
+ startedAt = stat.birthtimeMs > 0 ? stat.birthtimeMs : stat.ctimeMs;
457
+ }
458
+ catch {
459
+ /* fall back to now */
460
+ }
461
+ const machineRef = portableMachineRef(hostname, physicalMachineRef);
462
+ const projectSlug = portableProjectSlug(cwd);
463
+ const git = inspectGitCheckpoint(cwd, machineRef);
464
+ const title = cwd ? `codex @ ${path.basename(cwd)}` : "codex session";
465
+ const resumeHint = `codex resume ${id}`;
466
+ const context = extractPortableContext(safeReadLastJsonLines(file), "codex", id);
467
+ const continuity = (0, sessionHandoff_1.consumeSessionHandoffBinding)({
468
+ runtime: "codex",
469
+ projectSlug,
470
+ repoRef: git.repoRef,
471
+ workspacePath: cwd,
472
+ localSessionAnchor: id,
473
+ sessionStartedAt: startedAt,
474
+ baseDir: handoffBaseDir,
475
+ });
476
+ const checkpoint = (0, sessionCheckpoint_1.adaptCodexCheckpoint)({
477
+ machineRef,
478
+ localSessionAnchor: id,
479
+ sessionRef: continuity?.sessionRef,
480
+ previousCheckpointRef: continuity?.previousCheckpointRef,
481
+ projectRef: projectSlug ? `project:${projectSlug}` : undefined,
482
+ projectSlug,
483
+ sourceRevision: hashFile(file),
484
+ branch: git.branch,
485
+ worktreeRef: git.worktreeRef,
486
+ repoRef: git.repoRef,
487
+ now: context.now ?? title,
488
+ recent: context.recent,
489
+ next: context.next.length > 0
490
+ ? context.next
491
+ : ["Continue from the latest portable checkpoint."],
492
+ blockers: context.blockers,
493
+ decisions: context.decisions,
494
+ gitState: git.gitState,
495
+ eventAt: Math.round(mtime),
496
+ nativeResumeHint: resumeHint,
497
+ }).checkpoint;
498
+ sessions.push({
499
+ sessionKey: `codex:${hostname}:${id}`,
500
+ runtime: "codex",
501
+ nativeSessionId: id,
502
+ workspacePath: cwd,
503
+ projectSlug,
504
+ branch: git.branch,
505
+ title,
506
+ resumeHint,
507
+ lastEventAt: Math.round(mtime),
508
+ checkpoint,
509
+ });
510
+ }
511
+ return sessions;
512
+ }
513
+ // Claude Code transcripts: ~/.claude/projects/<workspace-slug>/<uuid>.jsonl
514
+ function scanClaudeSessions(hostname, root = path.join(os.homedir(), ".claude", "projects"), physicalMachineRef, handoffBaseDir) {
515
+ if (!fs.existsSync(root))
516
+ return [];
517
+ const sessions = [];
518
+ for (const file of listRecentFiles(root, ".jsonl", MAX_LOCAL_SESSIONS)) {
519
+ const id = path.basename(file).replace(/\.jsonl$/, "");
520
+ const head = safeReadFirstJsonLines(file);
521
+ const withCwd = head.find((row) => typeof row.cwd === "string");
522
+ const cwd = withCwd ? withCwd.cwd : undefined;
523
+ let mtime = Date.now();
524
+ let startedAt = mtime;
525
+ try {
526
+ const stat = fs.statSync(file);
527
+ mtime = stat.mtimeMs;
528
+ startedAt = stat.birthtimeMs > 0 ? stat.birthtimeMs : stat.ctimeMs;
529
+ }
530
+ catch {
531
+ /* fall back to now */
532
+ }
533
+ const machineRef = portableMachineRef(hostname, physicalMachineRef);
534
+ const projectSlug = portableProjectSlug(cwd);
535
+ const git = inspectGitCheckpoint(cwd, machineRef);
536
+ const title = cwd ? `claude @ ${path.basename(cwd)}` : "claude code session";
537
+ const resumeHint = `claude --resume ${id}`;
538
+ const context = extractPortableContext(safeReadLastJsonLines(file), "claude-code", id);
539
+ const continuity = (0, sessionHandoff_1.consumeSessionHandoffBinding)({
540
+ runtime: "claude-code",
541
+ projectSlug,
542
+ repoRef: git.repoRef,
543
+ workspacePath: cwd,
544
+ localSessionAnchor: id,
545
+ sessionStartedAt: startedAt,
546
+ baseDir: handoffBaseDir,
547
+ });
548
+ const checkpoint = (0, sessionCheckpoint_1.adaptClaudeCheckpoint)({
549
+ machineRef,
550
+ localSessionAnchor: id,
551
+ sessionRef: continuity?.sessionRef,
552
+ previousCheckpointRef: continuity?.previousCheckpointRef,
553
+ projectRef: projectSlug ? `project:${projectSlug}` : undefined,
554
+ projectSlug,
555
+ sourceRevision: hashFile(file),
556
+ branch: git.branch,
557
+ worktreeRef: git.worktreeRef,
558
+ repoRef: git.repoRef,
559
+ now: context.now ?? title,
560
+ recent: context.recent,
561
+ next: context.next.length > 0
562
+ ? context.next
563
+ : ["Continue from the latest portable checkpoint."],
564
+ blockers: context.blockers,
565
+ decisions: context.decisions,
566
+ gitState: git.gitState,
567
+ eventAt: Math.round(mtime),
568
+ nativeResumeHint: resumeHint,
569
+ }).checkpoint;
570
+ sessions.push({
571
+ sessionKey: `claude-code:${hostname}:${id}`,
572
+ runtime: "claude-code",
573
+ nativeSessionId: id,
574
+ workspacePath: cwd,
575
+ projectSlug,
576
+ branch: git.branch,
577
+ title,
578
+ resumeHint,
579
+ lastEventAt: Math.round(mtime),
580
+ checkpoint,
581
+ });
582
+ }
583
+ return sessions;
584
+ }
585
+ function requireAuth(json) {
586
+ if ((0, config_1.isAuthenticated)())
587
+ return true;
588
+ if (json) {
589
+ console.log(JSON.stringify({ ok: false, error: "not_authenticated" }));
590
+ }
591
+ else {
592
+ console.log("");
593
+ console.log(chalk_1.default.yellow(" not authenticated"));
594
+ console.log(" run " + chalk_1.default.cyan("you login") + " first -- sessions sync through your identity.");
595
+ console.log("");
596
+ }
597
+ return false;
598
+ }
599
+ function statusBadge(session) {
600
+ if (session.status === "claimed") {
601
+ return ACCENT(`claimed by ${session.claimedByHost ?? "?"}`);
602
+ }
603
+ if (session.status === "active")
604
+ return GREEN("active");
605
+ if (session.status === "checkpointed")
606
+ return ACCENT("checkpointed");
607
+ if (session.status === "closed")
608
+ return DIM("closed");
609
+ return DIM(session.status);
610
+ }
611
+ function printSession(session, index) {
612
+ const where = session.projectSlug ?? session.workspacePath ?? "unknown workspace";
613
+ const branch = session.branch ? DIM(` (${session.branch})`) : "";
614
+ console.log(` ${DIM(String(index + 1).padStart(2))} ${ACCENT(session.runtime.padEnd(11))} ${where}${branch}`);
615
+ console.log(` ${DIM("on")} ${session.machineHostname} ${statusBadge(session)} ${DIM(relativeTime(session.lastEventAt))}`);
616
+ if (session.resumeHint) {
617
+ console.log(` ${DIM("resume:")} ${chalk_1.default.cyan(session.resumeHint)}`);
618
+ }
619
+ console.log(` ${DIM("key:")} ${DIM(session.sessionKey)}`);
620
+ }
621
+ async function sessionListCommand(options = {}) {
622
+ if (!requireAuth(options.json))
623
+ return;
624
+ const spinner = options.json ? null : new render_1.BrailleSpinner("finding every brain you left running...");
625
+ spinner?.start();
626
+ const res = await (0, api_1.listAgentSessions)({
627
+ status: options.status,
628
+ machine: options.machine,
629
+ includeClosed: options.all,
630
+ });
631
+ if (!res.ok) {
632
+ spinner?.fail("could not reach the session ledger");
633
+ const message = (0, api_1.apiErrorMessage)(res.data) ?? `HTTP ${res.status}`;
634
+ if (options.json)
635
+ console.log(JSON.stringify({ ok: false, error: message }));
636
+ else
637
+ console.log(" " + chalk_1.default.red(message));
638
+ return;
639
+ }
640
+ const sessions = res.data.sessions;
641
+ spinner?.stop(`${sessions.length} synced session${sessions.length === 1 ? "" : "s"}`);
642
+ if (options.json) {
643
+ console.log(JSON.stringify({ ok: true, sessions }, null, 2));
644
+ return;
645
+ }
646
+ console.log("");
647
+ if (sessions.length === 0) {
648
+ console.log(" " + DIM("no synced sessions -- run ") + chalk_1.default.cyan("you session publish") + DIM(" on the machine doing the work"));
649
+ console.log("");
650
+ return;
651
+ }
652
+ sessions.forEach((session, index) => {
653
+ printSession(session, index);
654
+ console.log("");
655
+ });
656
+ console.log(" " + DIM("pick one up anywhere: ") + chalk_1.default.cyan("you session resume <key>") + DIM(" -- it claims the session so two machines never fight over it"));
657
+ console.log("");
658
+ }
659
+ async function sessionPublishCommand(options = {}) {
660
+ if (!requireAuth(options.json))
661
+ return;
662
+ const hostname = os.hostname();
663
+ const machineRef = (() => {
664
+ try {
665
+ return (0, machine_identity_1.readPhysicalMachineKey)() ?? undefined;
666
+ }
667
+ catch {
668
+ return undefined;
669
+ }
670
+ })();
671
+ const spinner = options.json ? null : new render_1.BrailleSpinner("taking attendance of local coding agents...");
672
+ spinner?.start();
673
+ const local = [...scanCodexSessions(hostname, undefined, machineRef), ...scanClaudeSessions(hostname, undefined, machineRef)];
674
+ const checkpoints = (0, sessionCheckpoint_1.dedupeUnchangedCheckpoints)(local.flatMap((session) => (session.checkpoint ? [session.checkpoint] : [])));
675
+ const uniqueCheckpointRefs = new Set(checkpoints.map((checkpoint) => checkpoint.checkpointRef));
676
+ const publishable = local.filter((session) => !session.checkpoint || uniqueCheckpointRefs.delete(session.checkpoint.checkpointRef));
677
+ if (publishable.length === 0) {
678
+ spinner?.stop("no fresh local sessions found");
679
+ if (options.json)
680
+ console.log(JSON.stringify({ ok: true, published: 0 }));
681
+ else {
682
+ console.log("");
683
+ console.log(" " + DIM("nothing running here in the last 7 days -- codex and claude both quiet"));
684
+ console.log("");
685
+ }
686
+ return;
687
+ }
688
+ spinner?.update(`syncing ${publishable.length} session snapshot${publishable.length === 1 ? "" : "s"} to your brain...`);
689
+ let published = 0;
690
+ const failures = [];
691
+ for (const session of publishable) {
692
+ const res = await (0, api_1.publishAgentSession)({
693
+ sessionKey: session.checkpoint?.sessionRef ?? session.sessionKey,
694
+ runtime: session.checkpoint?.runtime ?? session.runtime,
695
+ machineHostname: hostname,
696
+ title: session.title,
697
+ status: "active",
698
+ checkpoint: session.checkpoint,
699
+ });
700
+ if (res.ok)
701
+ published += 1;
702
+ else
703
+ failures.push(`${session.sessionKey}: ${(0, api_1.apiErrorMessage)(res.data) ?? `HTTP ${res.status}`}`);
704
+ }
705
+ if (failures.length > 0) {
706
+ spinner?.fail(`published ${published}/${publishable.length} sessions`);
707
+ }
708
+ else {
709
+ spinner?.stop(`published ${published} session${published === 1 ? "" : "s"} -- resumable from any synced machine`);
710
+ }
711
+ if (options.json) {
712
+ console.log(JSON.stringify({ ok: failures.length === 0, published, failures }, null, 2));
713
+ return;
714
+ }
715
+ for (const failure of failures)
716
+ console.log(" " + chalk_1.default.red(failure));
717
+ console.log("");
718
+ }
719
+ async function sessionResumeCommand(sessionKey, options = {}) {
720
+ if (!requireAuth(options.json))
721
+ return;
722
+ if (!sessionKey) {
723
+ console.log("");
724
+ console.log(" " + chalk_1.default.yellow("which one?") + DIM(" usage: ") + chalk_1.default.cyan("you session resume <sessionKey>"));
725
+ console.log(" " + DIM("see keys with ") + chalk_1.default.cyan("you session list"));
726
+ console.log("");
727
+ return;
728
+ }
729
+ const hostname = os.hostname();
730
+ const spinner = options.json ? null : new render_1.BrailleSpinner("claiming the session for this machine...");
731
+ spinner?.start();
732
+ const res = await (0, api_1.claimAgentSession)({ sessionKey, claimedByHost: hostname });
733
+ if (!res.ok) {
734
+ spinner?.fail("claim refused");
735
+ const message = (0, api_1.apiErrorMessage)(res.data) ?? `HTTP ${res.status}`;
736
+ if (options.json)
737
+ console.log(JSON.stringify({ ok: false, error: message }));
738
+ else
739
+ console.log(" " + chalk_1.default.red(message));
740
+ return;
741
+ }
742
+ const session = res.data.session;
743
+ let localMachineRef;
744
+ try {
745
+ localMachineRef = (0, machine_identity_1.readPhysicalMachineKey)() ?? undefined;
746
+ }
747
+ catch {
748
+ // Hostname fallback is safe for display routing, never portable identity.
749
+ }
750
+ const sameMachine = (0, sessionHandoff_1.isSameMachineSession)({ machineHostname: session.machineHostname, machineRef: session.machineRef }, { hostname, machineRef: localMachineRef });
751
+ if (sameMachine) {
752
+ const localSession = [
753
+ ...scanCodexSessions(hostname, undefined, localMachineRef),
754
+ ...scanClaudeSessions(hostname, undefined, localMachineRef),
755
+ ].find((candidate) => candidate.checkpoint?.sessionRef === session.sessionKey
756
+ || candidate.checkpoint?.checkpointRef === session.checkpoint?.checkpointRef);
757
+ spinner?.stop("claimed -- this machine owns the resume");
758
+ if (options.json) {
759
+ console.log(JSON.stringify({
760
+ ok: true,
761
+ local: true,
762
+ session: {
763
+ sessionKey: session.sessionKey,
764
+ runtime: session.runtime,
765
+ machineHostname: session.machineHostname,
766
+ machineRef: session.machineRef,
767
+ projectSlug: session.projectSlug,
768
+ branch: session.branch,
769
+ title: session.title,
770
+ contextSummary: session.contextSummary,
771
+ status: session.status,
772
+ lastEventAt: session.lastEventAt,
773
+ checkpoint: session.checkpoint ?? null,
774
+ nativeSessionId: null,
775
+ workspacePath: null,
776
+ resumeHint: null,
777
+ secretValuesExposed: false,
778
+ },
779
+ localResumeAvailable: Boolean(localSession?.resumeHint),
780
+ secretValuesExposed: false,
781
+ }, null, 2));
782
+ return;
783
+ }
784
+ console.log("");
785
+ console.log(" " + DIM("origin: ") + `${session.machineHostname}`);
786
+ if (session.branch)
787
+ console.log(" " + DIM("branch: ") + session.branch);
788
+ if (session.contextSummary) {
789
+ console.log("");
790
+ console.log(" " + DIM("context: ") + session.contextSummary);
791
+ }
792
+ console.log("");
793
+ if (localSession?.resumeHint) {
794
+ console.log(" " + "next move: " + chalk_1.default.cyan(localSession.resumeHint));
795
+ }
796
+ else {
797
+ console.log(" " + DIM("no local session file matched -- start a fresh agent from the portable checkpoint"));
798
+ }
799
+ console.log("");
800
+ return;
801
+ }
802
+ try {
803
+ if (!session.checkpoint)
804
+ throw new Error("portable checkpoint is missing");
805
+ const handoff = (0, sessionHandoff_1.createPortableSessionHandoff)({
806
+ checkpoint: session.checkpoint,
807
+ projectDir: process.cwd(),
808
+ });
809
+ spinner?.stop("claimed -- portable handoff ready on this machine");
810
+ if (options.json) {
811
+ console.log(JSON.stringify({
812
+ ok: true,
813
+ local: false,
814
+ checkpointRef: handoff.checkpointRef,
815
+ sessionRef: handoff.sessionRef,
816
+ briefPath: handoff.briefPath,
817
+ command: handoff.command,
818
+ expiresAt: handoff.expiresAt,
819
+ secretValuesExposed: false,
820
+ }, null, 2));
821
+ return;
822
+ }
823
+ console.log("");
824
+ console.log(" " + DIM("portable checkpoint: ") + handoff.checkpointRef);
825
+ console.log(" " + DIM("handoff brief: ") + handoff.briefPath);
826
+ console.log("");
827
+ console.log(" " + "start a fresh local session:");
828
+ console.log(" " + chalk_1.default.cyan(handoff.command));
829
+ console.log("");
830
+ }
831
+ catch (error) {
832
+ spinner?.fail("claim has no safe portable handoff");
833
+ const message = error instanceof Error ? error.message : "portable checkpoint is missing or malformed";
834
+ if (options.json) {
835
+ console.log(JSON.stringify({
836
+ ok: false,
837
+ error: "portable_checkpoint_required",
838
+ message,
839
+ secretValuesExposed: false,
840
+ }));
841
+ }
842
+ else {
843
+ console.log(" " + chalk_1.default.red(message));
844
+ console.log(" " + DIM("origin-native resume ids are never valid on another machine"));
845
+ console.log("");
846
+ }
847
+ }
848
+ }
849
+ async function sessionCloseCommand(sessionKey, options = {}) {
850
+ if (!requireAuth(options.json))
851
+ return;
852
+ if (!sessionKey) {
853
+ console.log("");
854
+ console.log(" " + chalk_1.default.yellow("which one?") + DIM(" usage: ") + chalk_1.default.cyan("you session close <sessionKey>"));
855
+ console.log("");
856
+ return;
857
+ }
858
+ const spinner = options.json ? null : new render_1.BrailleSpinner("closing the session everywhere...");
859
+ spinner?.start();
860
+ const res = await (0, api_1.claimAgentSession)({ sessionKey, close: true });
861
+ if (!res.ok) {
862
+ spinner?.fail("close refused");
863
+ const message = (0, api_1.apiErrorMessage)(res.data) ?? `HTTP ${res.status}`;
864
+ if (options.json)
865
+ console.log(JSON.stringify({ ok: false, error: message }));
866
+ else
867
+ console.log(" " + chalk_1.default.red(message));
868
+ return;
869
+ }
870
+ spinner?.stop("closed -- off every machine's list");
871
+ if (options.json)
872
+ console.log(JSON.stringify({ ok: true, session: res.data.session }, null, 2));
873
+ console.log("");
874
+ }
875
+ async function sessionCommand(subcommand, args = [], options = {}) {
876
+ switch (subcommand) {
877
+ case undefined:
878
+ case "list":
879
+ return sessionListCommand(options);
880
+ case "publish":
881
+ case "sync":
882
+ return sessionPublishCommand(options);
883
+ case "resume":
884
+ case "claim":
885
+ return sessionResumeCommand(args[0], options);
886
+ case "close":
887
+ return sessionCloseCommand(args[0], options);
888
+ default:
889
+ console.log("");
890
+ console.log(" " + chalk_1.default.yellow(`unknown subcommand: ${subcommand}`));
891
+ console.log(" " + DIM("try: ") + chalk_1.default.cyan("you session list | publish | resume <key> | close <key>"));
892
+ console.log("");
893
+ }
894
+ }
895
+ //# sourceMappingURL=session.js.map