| 154 | } |
| 155 | |
| 156 | function loadSnapshots(opts: ReturnType<typeof parseArgs>): Snapshot[] { |
| 157 | const metas = readdirSync(opts.dir).filter((f) => f.endsWith(".meta.json")); |
| 158 | const snaps: Snapshot[] = []; |
| 159 | for (const metaFile of metas) { |
| 160 | let meta: Json; |
| 161 | try { |
| 162 | meta = JSON.parse(readFileSync(join(opts.dir, metaFile), "utf8")) as Json; |
| 163 | } catch { |
| 164 | continue; |
| 165 | } |
| 166 | const session = String(meta.session ?? ""); |
| 167 | // The dump truncates session ids with an ellipsis ("ses_31366057…"); match |
| 168 | // on the visible head so a full id or a head fragment both work. |
| 169 | const head = session.replace(/[….]+$/, ""); |
| 170 | if (!session.startsWith(opts.sessionPrefix) && !opts.sessionPrefix.startsWith(head)) { |
| 171 | continue; |
| 172 | } |
| 173 | const createdAt = String(meta.createdAt ?? ""); |
| 174 | if (opts.since && createdAt < opts.since) continue; |
| 175 | if (opts.until && createdAt > opts.until) continue; |
| 176 | const files = meta.files as Json | undefined; |
| 177 | const bodyPath = files && typeof files.body === "string" ? files.body : undefined; |
| 178 | if (!bodyPath) continue; |
| 179 | let body: Json; |
| 180 | try { |
| 181 | body = JSON.parse(readFileSync(bodyPath, "utf8")) as Json; |
| 182 | } catch { |
| 183 | continue; |
| 184 | } |
| 185 | const bodyMeta = meta.body as Json | undefined; |
| 186 | snaps.push({ |
| 187 | file: metaFile, |
| 188 | createdAt, |
| 189 | session, |
| 190 | messagesCount: |
| 191 | typeof bodyMeta?.messagesCount === "number" ? bodyMeta.messagesCount : -1, |
| 192 | segments: buildSegments(body), |
| 193 | }); |
| 194 | } |
| 195 | snaps.sort((a, b) => a.createdAt.localeCompare(b.createdAt)); |
| 196 | if (opts.limit && snaps.length > opts.limit) { |
| 197 | return snaps.slice(snaps.length - opts.limit); |
| 198 | } |
| 199 | return snaps; |
| 200 | } |
| 201 | |
| 202 | /** First wire-order segment index where prev/cur diverge (added/removed/changed). */ |
| 203 | function firstDivergence(prev: Segment[], cur: Segment[]): number { |