sessiongraph-app-darwin-arm64 1.0.10 → 1.0.12

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 (24) hide show
  1. package/package.json +2 -2
  2. package/payload/SessionGraph.app/Contents/Info.plist +2 -2
  3. package/payload/SessionGraph.app/Contents/MacOS/sessiongraph +0 -0
  4. package/payload/SessionGraph.app/Contents/Resources/resources/node +2 -2
  5. package/payload/SessionGraph.app/Contents/Resources/resources/server.mjs +163 -58
  6. package/payload/SessionGraph.app/Contents/Resources/resources/web/assets/external-links-D0O8IeG0.js +1 -0
  7. package/payload/SessionGraph.app/Contents/Resources/resources/web/assets/{index-BNX-8Jnn.js → index-BS2Voifb.js} +1 -1
  8. package/payload/SessionGraph.app/Contents/Resources/resources/web/assets/{index-BtLEIWKh.js → index-CIhs02tq.js} +12 -12
  9. package/payload/SessionGraph.app/Contents/Resources/resources/web/assets/{index-RfmyzBnX.js → index-GTrhxXlP.js} +1 -1
  10. package/payload/SessionGraph.app/Contents/Resources/resources/web/assets/index-Vq4WYmzW.js +1 -0
  11. package/payload/SessionGraph.app/Contents/Resources/resources/web/assets/index-cVgPCrFV.css +1 -0
  12. package/payload/SessionGraph.app/Contents/Resources/resources/web/assets/{mp4-worker-HFPra5JP.js → mp4-worker-Dc_1QUwS.js} +1 -1
  13. package/payload/SessionGraph.app/Contents/Resources/resources/web/assets/publishing-ofssf6d6.js +25 -0
  14. package/payload/SessionGraph.app/Contents/Resources/resources/web/assets/{scene-B_CpQ0qP.js → scene-KCDTPCOD.js} +1 -1
  15. package/payload/SessionGraph.app/Contents/Resources/resources/web/assets/share-DKeJCFAo.js +7 -0
  16. package/payload/SessionGraph.app/Contents/Resources/resources/web/assets/worker-C10U_apJ.js +2 -0
  17. package/payload/SessionGraph.app/Contents/Resources/resources/web/index.html +2 -2
  18. package/payload/SessionGraph.app/Contents/_CodeSignature/CodeResources +61 -61
  19. package/payload/SessionGraph.app/Contents/Resources/resources/web/assets/gif-worker-CghHEBpp.js +0 -1
  20. package/payload/SessionGraph.app/Contents/Resources/resources/web/assets/index-BcoCDV6F.js +0 -1
  21. package/payload/SessionGraph.app/Contents/Resources/resources/web/assets/index-BnhC5gO7.css +0 -1
  22. package/payload/SessionGraph.app/Contents/Resources/resources/web/assets/publishing-DoHRK4Ea.js +0 -26
  23. package/payload/SessionGraph.app/Contents/Resources/resources/web/assets/share-DPdu1Hbo.js +0 -8
  24. package/payload/SessionGraph.app/Contents/Resources/resources/web/assets/worker-DwqxiDWY.js +0 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sessiongraph-app-darwin-arm64",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "Explore & visualize all your LLM sessions — darwin/arm64",
5
5
  "repository": {
6
6
  "type": "git",
@@ -10,7 +10,7 @@
10
10
  "access": "public",
11
11
  "registry": "https://registry.npmjs.org/"
12
12
  },
13
- "sessionGraphCommit": "0e6a305823678d14392776d56259954d56233836",
13
+ "sessionGraphCommit": "d99e40d05031062b93e980d62c5fd68e296d7e2c",
14
14
  "os": [
15
15
  "darwin"
16
16
  ],
@@ -17,9 +17,9 @@
17
17
  <key>CFBundlePackageType</key>
18
18
  <string>APPL</string>
19
19
  <key>CFBundleShortVersionString</key>
20
- <string>1.0.10</string>
20
+ <string>1.0.12</string>
21
21
  <key>CFBundleVersion</key>
22
- <string>1.0.10</string>
22
+ <string>1.0.12</string>
23
23
  <key>CSResourcesFileMapped</key>
24
24
  <true/>
25
25
  <key>LSApplicationCategoryType</key>
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/payload/SessionGraph.app/Contents/Resources/resources/node
3
- size: 122544576 bytes
4
- sha256: 614a59e57b84783a088d8f1da0d1d2d7023348d04a4453794d6ce0ac4328c269
3
+ size: 122563152 bytes
4
+ sha256: d8ddcaaac87e77e48b9e61b1de6d7097cb5faaf3eb3df5b522821a07b19ace9f
@@ -2627,12 +2627,12 @@ var serviceFile = (env = process.env) => env.SESSIONGRAPH_SERVICE_FILE || env.SE
2627
2627
  function databasePath(env = process.env, platform = process.platform, home = homedir2()) {
2628
2628
  const override = env.SESSIONGRAPH_DATA_DIR || env.SESSION_ATLAS_DATA_DIR || env.ATLAS_DATA_DIR;
2629
2629
  const base = platform === "darwin" ? join2(home, "Library", "Application Support") : platform === "win32" ? env.LOCALAPPDATA || home : env.XDG_DATA_HOME || join2(home, ".local", "share");
2630
- const paths = [
2630
+ const paths2 = [
2631
2631
  join2(override || join2(base, platform === "linux" ? "sessiongraph" : "SessionGraph"), "sessiongraph.sqlite"),
2632
2632
  join2(override || join2(base, platform === "linux" ? "session-atlas" : "Session Atlas"), "session-atlas.sqlite"),
2633
2633
  join2(override || join2(base, platform === "linux" ? "atlas" : "Atlas"), "atlas.sqlite")
2634
2634
  ];
2635
- return paths.find((path) => existsSync(path)) || paths[0];
2635
+ return paths2.find((path) => existsSync(path)) || paths2[0];
2636
2636
  }
2637
2637
 
2638
2638
  // src/playback-tokens.ts
@@ -2738,7 +2738,7 @@ function editedPaths(part) {
2738
2738
  }
2739
2739
  return [];
2740
2740
  }
2741
- function summarizeMessages(id2, messages, nextCursor) {
2741
+ function summarizeMessages(id2, messages, nextCursor2) {
2742
2742
  const files = /* @__PURE__ */ new Map();
2743
2743
  const tools = /* @__PURE__ */ new Map();
2744
2744
  const locations = [];
@@ -2770,8 +2770,8 @@ function summarizeMessages(id2, messages, nextCursor) {
2770
2770
  locations,
2771
2771
  messagesScanned: messages.length,
2772
2772
  snapshotSteps,
2773
- partial: Boolean(nextCursor),
2774
- nextCursor,
2773
+ partial: Boolean(nextCursor2),
2774
+ nextCursor: nextCursor2,
2775
2775
  loadedAt: Date.now()
2776
2776
  };
2777
2777
  }
@@ -30778,6 +30778,29 @@ function successfulEdits(messages, editNames) {
30778
30778
 
30779
30779
  // server/adapters/pi.ts
30780
30780
  import { homedir as homedir3 } from "node:os";
30781
+
30782
+ // server/source-paths.ts
30783
+ import { AsyncLocalStorage } from "node:async_hooks";
30784
+ var paths = new AsyncLocalStorage();
30785
+ var sourcePath = (id2) => paths.getStore()?.[id2];
30786
+ function withSourcePaths(adapter, configured) {
30787
+ return {
30788
+ ...adapter,
30789
+ detect: () => paths.run(configured(), () => adapter.detect()),
30790
+ discover: () => paths.run(configured(), async () => {
30791
+ const result = await adapter.discover();
30792
+ if (!sourcePath(adapter.id)) return result;
30793
+ return { ...result, sessions: result.sessions.map((candidate) => ({ ...candidate, session: { ...candidate.session, nativeResume: false } })) };
30794
+ }),
30795
+ read: (candidate, cursor) => paths.run(configured(), async () => {
30796
+ const result = await adapter.read(candidate, cursor);
30797
+ if (sourcePath(adapter.id)) candidate.session.nativeResume = false;
30798
+ return result;
30799
+ })
30800
+ };
30801
+ }
30802
+
30803
+ // server/adapters/pi.ts
30781
30804
  import { join as join5 } from "node:path";
30782
30805
  function piHistory(value) {
30783
30806
  const { rows: rows2, partial } = jsonLines(value);
@@ -30809,11 +30832,11 @@ var piAdapter = localFileAdapter({
30809
30832
  name: "Pi",
30810
30833
  description: "Local JSONL sessions \xB7 messages, tools, compaction, and branch entries",
30811
30834
  capabilities: { history: true, forks: false, children: false, files: true },
30812
- roots: () => [process.env.SESSIONGRAPH_PI_SESSIONS_DIR || join5(process.env.PI_CODING_AGENT_DIR || join5(homedir3(), ".pi", "agent"), "sessions")],
30835
+ roots: () => [sourcePath("pi") || process.env.SESSIONGRAPH_PI_SESSIONS_DIR || join5(process.env.PI_CODING_AGENT_DIR || join5(homedir3(), ".pi", "agent"), "sessions")],
30813
30836
  match: (path) => path.endsWith(".jsonl"),
30814
30837
  parse(path, files) {
30815
30838
  const history = piHistory(files.get(path));
30816
- history.session.nativeResume = Boolean(history.session.directory) && !process.env.SESSIONGRAPH_PI_SESSIONS_DIR;
30839
+ history.session.nativeResume = Boolean(history.session.directory) && !sourcePath("pi") && !process.env.SESSIONGRAPH_PI_SESSIONS_DIR;
30817
30840
  return history;
30818
30841
  }
30819
30842
  });
@@ -31183,9 +31206,9 @@ function sqliteHistoryAdapter(source) {
31183
31206
  }
31184
31207
  };
31185
31208
  }
31186
- async function existingDatabases(paths) {
31209
+ async function existingDatabases(paths2) {
31187
31210
  const found = [];
31188
- for (const path of new Set(paths)) {
31211
+ for (const path of new Set(paths2)) {
31189
31212
  try {
31190
31213
  if ((await lstat3(path)).isFile()) found.push(path);
31191
31214
  } catch (e10) {
@@ -31230,7 +31253,7 @@ function combineAdapters(parts) {
31230
31253
  }
31231
31254
 
31232
31255
  // server/adapters/claude.ts
31233
- var root = () => join8(process.env.CLAUDE_CONFIG_DIR || join8(homedir4(), ".claude"), "projects");
31256
+ var root = () => join8(sourcePath("claude-code") || process.env.CLAUDE_CONFIG_DIR || join8(homedir4(), ".claude"), "projects");
31234
31257
  function claudeMessages(rows2, candidate, times) {
31235
31258
  const calls = /* @__PURE__ */ new Map();
31236
31259
  return rows2.map((row) => {
@@ -31275,7 +31298,7 @@ var sdkClaudeAdapter = {
31275
31298
  },
31276
31299
  async discover() {
31277
31300
  const sessions = [], warnings = [];
31278
- const infos = await I1t({ limit: 2e4, includeProgrammatic: true });
31301
+ const infos = sourcePath("claude-code") ? [] : await I1t({ limit: 2e4, includeProgrammatic: true });
31279
31302
  if (infos.length === 2e4) warnings.push("Claude Code listing is limited to 20,000 sessions.");
31280
31303
  const files = /* @__PURE__ */ new Map();
31281
31304
  let unreadable = 0;
@@ -31368,7 +31391,7 @@ var exportedClaudeAdapter = localFileAdapter({
31368
31391
  name: "Claude Code",
31369
31392
  description: "Legacy local transcript exports",
31370
31393
  capabilities: { history: true, forks: false, children: false, files: false },
31371
- roots: () => [join8(process.env.CLAUDE_CONFIG_DIR || join8(homedir4(), ".claude"), "transcripts")],
31394
+ roots: () => [join8(sourcePath("claude-code") || process.env.CLAUDE_CONFIG_DIR || join8(homedir4(), ".claude"), "transcripts")],
31372
31395
  match: (p) => p.endsWith(".jsonl"),
31373
31396
  parse: (path, files) => envelopeHistory(path, files.get(path))
31374
31397
  });
@@ -31473,7 +31496,7 @@ async function transcriptFiles(root3, max = 2e4, match2 = (name) => name.endsWit
31473
31496
  }
31474
31497
 
31475
31498
  // server/adapters/codex.ts
31476
- var root2 = () => process.env.CODEX_HOME || join10(homedir5(), ".codex");
31499
+ var root2 = () => sourcePath("codex") || process.env.CODEX_HOME || join10(homedir5(), ".codex");
31477
31500
  var revision = async (path) => {
31478
31501
  const s = await stat2(path);
31479
31502
  return digest([8, s.mtimeMs, s.size]);
@@ -31574,10 +31597,10 @@ var codexAdapter = {
31574
31597
  const found = /* @__PURE__ */ new Map(), warnings = [];
31575
31598
  const names = await sessionNames();
31576
31599
  const match2 = (name) => /\.jsonl(?:\.[\w.-]+)?$/.test(name);
31577
- const paths = [...await transcriptFiles(join10(root2(), "sessions"), 2e4, match2), ...await transcriptFiles(join10(root2(), "archived_sessions"), 2e4, match2)];
31578
- if (paths.length >= 2e4) warnings.push("Codex transcript listing may be partial (20,000 files per history folder).");
31600
+ const paths2 = [...await transcriptFiles(join10(root2(), "sessions"), 2e4, match2), ...await transcriptFiles(join10(root2(), "archived_sessions"), 2e4, match2)];
31601
+ if (paths2.length >= 2e4) warnings.push("Codex transcript listing may be partial (20,000 files per history folder).");
31579
31602
  let unsupported = 0;
31580
- for (const path of paths) {
31603
+ for (const path of paths2) {
31581
31604
  try {
31582
31605
  const first = await readJSONL(path, 0, 2e6, 100);
31583
31606
  const meta = first.rows.find((r) => r.value.type === "session_meta")?.value.payload;
@@ -31652,14 +31675,14 @@ function status(value) {
31652
31675
  return ["pending", "running", "completed", "failed"].includes(string(value)) ? value : "unknown";
31653
31676
  }
31654
31677
  async function databasePath2() {
31655
- const root3 = process.env.SESSIONGRAPH_T3_CODE_HISTORY_DIR || process.env.T3CODE_HOME || join11(homedir6(), ".t3");
31656
- const paths = /\.sqlite$/.test(root3) ? [root3] : [
31678
+ const root3 = sourcePath("t3-code") || process.env.SESSIONGRAPH_T3_CODE_HISTORY_DIR || process.env.T3CODE_HOME || join11(homedir6(), ".t3");
31679
+ const paths2 = /\.sqlite$/.test(root3) ? [root3] : [
31657
31680
  join11(root3, "userdata", "statev2.sqlite"),
31658
31681
  join11(root3, "statev2.sqlite"),
31659
31682
  join11(root3, "userdata", "state.sqlite"),
31660
31683
  join11(root3, "state.sqlite")
31661
31684
  ];
31662
- for (const path of paths) {
31685
+ for (const path of paths2) {
31663
31686
  try {
31664
31687
  if ((await lstat4(path)).isFile()) return path;
31665
31688
  } catch (error) {
@@ -32314,7 +32337,7 @@ async function workspacePaths(root3, warnings) {
32314
32337
  return stores;
32315
32338
  }
32316
32339
  function createCursorAdapter(options = {}) {
32317
- const root3 = () => options.home || process.env.SESSIONGRAPH_CURSOR_HOME || process.env.SESSION_ATLAS_CURSOR_HOME || join13(homedir7(), ".cursor");
32340
+ const root3 = () => options.home || sourcePath("cursor") || process.env.SESSIONGRAPH_CURSOR_HOME || process.env.SESSION_ATLAS_CURSOR_HOME || join13(homedir7(), ".cursor");
32318
32341
  const editor = () => options.userData || process.env.SESSIONGRAPH_CURSOR_USER_DATA_DIR || process.env.SESSION_ATLAS_CURSOR_USER_DATA_DIR || userData();
32319
32342
  return {
32320
32343
  id: "cursor",
@@ -32337,7 +32360,7 @@ function createCursorAdapter(options = {}) {
32337
32360
  async discover() {
32338
32361
  const warnings = [], found = /* @__PURE__ */ new Map(), blocked = /* @__PURE__ */ new Set();
32339
32362
  const workspaceStores = await workspacePaths(editor(), warnings);
32340
- const paths = [...new Set([...workspaceStores.values()].filter(Boolean))];
32363
+ const paths2 = [...new Set([...workspaceStores.values()].filter(Boolean))];
32341
32364
  const legacyStores = new Map([[join13(editor(), "User/globalStorage/state.vscdb"), ""], ...workspaceStores]);
32342
32365
  for (const [path, workspace] of legacyStores) {
32343
32366
  try {
@@ -32358,7 +32381,7 @@ function createCursorAdapter(options = {}) {
32358
32381
  }
32359
32382
  const composers = new Map(found);
32360
32383
  const match2 = (key, encode) => {
32361
- const matches = paths.filter((path) => encode(path) === key);
32384
+ const matches = paths2.filter((path) => encode(path) === key);
32362
32385
  return matches.length === 1 ? matches[0] : "";
32363
32386
  };
32364
32387
  let unreadable = 0, legacy = 0, scanned = 0;
@@ -32565,7 +32588,7 @@ var geminiAdapter = localFileAdapter({
32565
32588
  name: "Gemini CLI",
32566
32589
  description: "Local JSON and JSONL chats, including recorded subagent histories",
32567
32590
  capabilities: { history: true, forks: false, children: true, files: true },
32568
- roots: () => process.env.SESSIONGRAPH_GEMINI_HOME ? [join14(process.env.SESSIONGRAPH_GEMINI_HOME, "tmp")] : [join14(process.env.GEMINI_CLI_HOME || homedir8(), ".gemini", "tmp"), join14(process.env.GEMINI_CLI_HOME || homedir8(), ".cache", ".gemini", "tmp")],
32591
+ roots: () => sourcePath("gemini") || process.env.SESSIONGRAPH_GEMINI_HOME ? [join14(sourcePath("gemini") || process.env.SESSIONGRAPH_GEMINI_HOME, "tmp")] : [join14(process.env.GEMINI_CLI_HOME || homedir8(), ".gemini", "tmp"), join14(process.env.GEMINI_CLI_HOME || homedir8(), ".cache", ".gemini", "tmp")],
32569
32592
  match: (path) => /^[^/]+\/chats\/(?:[^/]+\/)?[^/]+\.jsonl?$/.test(path),
32570
32593
  extras: (path) => [projectRootFile(path)],
32571
32594
  parse(path, files) {
@@ -32594,7 +32617,7 @@ var geminiAdapter = localFileAdapter({
32594
32617
  messages,
32595
32618
  parent: parent2,
32596
32619
  partial,
32597
- session: { directory: directory2, created: time(meta.startTime), title: text(meta.summary), model: text([...records].reverse().find((r) => r.model)?.model) || "Unknown model", provider: "Google", nativeResume: !parent2 && Boolean(directory2) && !process.env.SESSIONGRAPH_GEMINI_HOME && !path.includes(`${sep3}.cache${sep3}`) },
32620
+ session: { directory: directory2, created: time(meta.startTime), title: text(meta.summary), model: text([...records].reverse().find((r) => r.model)?.model) || "Unknown model", provider: "Google", nativeResume: !parent2 && Boolean(directory2) && !sourcePath("gemini") && !process.env.SESSIONGRAPH_GEMINI_HOME && !path.includes(`${sep3}.cache${sep3}`) },
32598
32621
  note: "Local Gemini chat records with rewind and patch reconstruction. Only explicit subagent directories form parent links; workspace paths come from the project-root marker. Binary content is omitted."
32599
32622
  };
32600
32623
  }
@@ -32608,7 +32631,7 @@ var qwenAdapter = localFileAdapter({
32608
32631
  name: "Qwen Code",
32609
32632
  description: "Local project chat records \xB7 tool results, forks, and child sessions",
32610
32633
  capabilities: { history: true, forks: true, children: true, files: true },
32611
- roots: () => [join15(process.env.SESSIONGRAPH_QWEN_HOME || process.env.QWEN_RUNTIME_DIR || process.env.QWEN_HOME || join15(homedir9(), ".qwen"), "projects")],
32634
+ roots: () => [join15(sourcePath("qwen-code") || process.env.SESSIONGRAPH_QWEN_HOME || process.env.QWEN_RUNTIME_DIR || process.env.QWEN_HOME || join15(homedir9(), ".qwen"), "projects")],
32612
32635
  match: (path) => /^[^/]+\/chats\/[^/]+\.jsonl$/.test(path),
32613
32636
  parse(path, files) {
32614
32637
  const { rows: rows2, partial } = jsonLines(files.get(path));
@@ -32638,7 +32661,7 @@ var qwenAdapter = localFileAdapter({
32638
32661
  partial,
32639
32662
  parent: text(parent2) || void 0,
32640
32663
  fork: text(rows2.find((r) => r.forkedFrom?.sessionId)?.forkedFrom?.sessionId) || void 0,
32641
- session: { directory: text(first.cwd), created: time(first.timestamp), title: text(title), model: text(latest.find((r) => r.model)?.model) || text(latest.find((r) => r.subtype === "session_model")?.systemPayload?.modelId) || "Unknown model", nativeResume: Boolean(first.cwd) && !process.env.SESSIONGRAPH_QWEN_HOME },
32664
+ session: { directory: text(first.cwd), created: time(first.timestamp), title: text(title), model: text(latest.find((r) => r.model)?.model) || text(latest.find((r) => r.subtype === "session_model")?.systemPayload?.modelId) || "Unknown model", nativeResume: Boolean(first.cwd) && !sourcePath("qwen-code") && !process.env.SESSIONGRAPH_QWEN_HOME },
32642
32665
  note: "Local Qwen chat records, including compaction and branch events in source order. Parent and fork links use recorded session IDs. Managed-daemon-only and binary history may be unavailable."
32643
32666
  };
32644
32667
  }
@@ -32652,7 +32675,7 @@ var copilotAdapter = localFileAdapter({
32652
32675
  name: "GitHub Copilot CLI",
32653
32676
  description: "Local session event logs \xB7 conversations and completed file edits",
32654
32677
  capabilities: { history: true, forks: false, children: true, files: true },
32655
- roots: () => [join16(process.env.SESSIONGRAPH_COPILOT_HOME || join16(homedir10(), ".copilot"), "session-state")],
32678
+ roots: () => [join16(sourcePath("copilot") || process.env.SESSIONGRAPH_COPILOT_HOME || join16(homedir10(), ".copilot"), "session-state")],
32656
32679
  match: (path) => /(?:^|\/)events\.jsonl$/.test(path),
32657
32680
  parse(path, files) {
32658
32681
  const { rows: rows2, partial } = jsonLines(files.get(path));
@@ -32692,7 +32715,7 @@ var copilotAdapter = localFileAdapter({
32692
32715
  messages,
32693
32716
  partial,
32694
32717
  parent: text(meta.detachedFromSpawningParentSessionId) || void 0,
32695
- session: { directory: text(meta.context?.cwd), created: time(meta.startTime || start?.timestamp), title: text(latest.find((r) => r.type === "session.title_changed")?.data?.title), model: text(latest.find((r) => r.type === "session.model_change")?.data?.newModel) || text(meta.selectedModel) || "Unknown model", provider: text(meta.providerId), nativeResume: Boolean(meta.context?.cwd) && !process.env.SESSIONGRAPH_COPILOT_HOME },
32718
+ session: { directory: text(meta.context?.cwd), created: time(meta.startTime || start?.timestamp), title: text(latest.find((r) => r.type === "session.title_changed")?.data?.title), model: text(latest.find((r) => r.type === "session.model_change")?.data?.newModel) || text(meta.selectedModel) || "Unknown model", provider: text(meta.providerId), nativeResume: Boolean(meta.context?.cwd) && !sourcePath("copilot") && !process.env.SESSIONGRAPH_COPILOT_HOME },
32696
32719
  note: "Persisted Copilot CLI events only. Streaming deltas are excluded in favor of final messages. Tool-call parents are event correlations, not session ancestry. Cloud-only sessions and binary assets are unavailable."
32697
32720
  };
32698
32721
  }
@@ -32750,7 +32773,8 @@ function vscodeChatLog(content) {
32750
32773
  function copilotChatRoots(platform = process.platform, home = homedir11(), env = process.env) {
32751
32774
  const path = platform === "win32" ? win322 : posix;
32752
32775
  const base = platform === "darwin" ? path.join(home, "Library", "Application Support") : platform === "win32" ? env.APPDATA || path.join(home, "AppData", "Roaming") : env.XDG_CONFIG_HOME || path.join(home, ".config");
32753
- const users = env.SESSIONGRAPH_COPILOT_CHAT_USER_DATA_DIR ? [path.join(env.SESSIONGRAPH_COPILOT_CHAT_USER_DATA_DIR, "User")] : [...["Code", "Code - Insiders"].map((app) => path.join(base, app, "User")), ...[".vscode-server", ".vscode-server-insiders"].map((app) => path.join(home, app, "data", "User"))];
32776
+ const override = sourcePath("copilot-chat") || env.SESSIONGRAPH_COPILOT_CHAT_USER_DATA_DIR;
32777
+ const users = override ? [path.join(override, "User")] : [...["Code", "Code - Insiders"].map((app) => path.join(base, app, "User")), ...[".vscode-server", ".vscode-server-insiders"].map((app) => path.join(home, app, "data", "User"))];
32754
32778
  return users.flatMap((user) => [path.join(user, "workspaceStorage"), path.join(user, "globalStorage", "emptyWindowChatSessions")]);
32755
32779
  }
32756
32780
  function vscodeChatPath(value) {
@@ -32881,7 +32905,8 @@ function editorTaskRoots(extension) {
32881
32905
  return ["Code", "Code - Insiders", "VSCodium", "Cursor", "Windsurf"].map((app) => join18(base, app, "User", "globalStorage", extension, "tasks")).concat([".vscode-server", ".vscode-server-insiders"].map((app) => join18(home, app, "data", "User", "globalStorage", extension, "tasks")));
32882
32906
  }
32883
32907
  function taskAdapter(id2, name, extension, env) {
32884
- const roots2 = () => process.env[env] ? [join18(process.env[env], "tasks"), ...id2 === "cline" ? [join18(process.env[env], "data", "sessions")] : []] : [...editorTaskRoots(extension), ...id2 === "cline" ? [process.env.CLINE_SESSION_DATA_DIR || join18(process.env.CLINE_DATA_DIR || join18(process.env.CLINE_DIR || join18(homedir12(), ".cline"), "data"), "sessions")] : []];
32908
+ const home = () => sourcePath(id2) || process.env[env];
32909
+ const roots2 = () => home() ? [join18(home(), "tasks"), ...id2 === "cline" ? [join18(home(), "data", "sessions")] : []] : [...editorTaskRoots(extension), ...id2 === "cline" ? [process.env.CLINE_SESSION_DATA_DIR || join18(process.env.CLINE_DATA_DIR || join18(process.env.CLINE_DIR || join18(homedir12(), ".cline"), "data"), "sessions")] : []];
32885
32910
  return localFileAdapter({
32886
32911
  id: id2,
32887
32912
  name,
@@ -32930,7 +32955,7 @@ var continueAdapter = localFileAdapter({
32930
32955
  name: "Continue",
32931
32956
  description: "Local IDE and CLI session JSON \xB7 recorded conversation and tool history",
32932
32957
  capabilities: { history: true, forks: false, children: false, files: false },
32933
- roots: () => [join18(process.env.SESSIONGRAPH_CONTINUE_HOME || process.env.CONTINUE_GLOBAL_DIR || join18(homedir12(), ".continue"), "sessions")],
32958
+ roots: () => [join18(sourcePath("continue") || process.env.SESSIONGRAPH_CONTINUE_HOME || process.env.CONTINUE_GLOBAL_DIR || join18(homedir12(), ".continue"), "sessions")],
32934
32959
  match: (path) => /^[^/]+\.json$/.test(path) && path !== "sessions.json",
32935
32960
  extras: (path) => [join18(dirname5(path), "sessions.json")],
32936
32961
  revisionExtras: false,
@@ -32968,7 +32993,7 @@ var appPath = (...parts) => join19(process.platform === "darwin" ? homePath("Lib
32968
32993
  var tokscalePath = (...parts) => join19(process.env.TOKSCALE_CONFIG_DIR || (process.platform === "win32" ? appPath("tokscale") : configPath("tokscale")), ...parts);
32969
32994
  var envHome = (env, fallback, ...parts) => join19(process.env[env]?.trim() || homePath(fallback), ...parts);
32970
32995
  var harnessRoots = (id2, defaults) => () => {
32971
- const override = process.env[`SESSIONGRAPH_${id2.replace(/-/g, "_").toUpperCase()}_HISTORY_DIR`]?.trim();
32996
+ const override = sourcePath(id2) || process.env[`SESSIONGRAPH_${id2.replace(/-/g, "_").toUpperCase()}_HISTORY_DIR`]?.trim();
32972
32997
  return override ? [override] : defaults();
32973
32998
  };
32974
32999
 
@@ -33184,7 +33209,7 @@ var parsedContent = (v3) => typeof v3 === "string" && /^[\[{]/.test(v3.trim()) ?
33184
33209
  var millisOrSeconds = (v3) => typeof v3 === "number" && v3 > 0 && v3 < 1e10 ? seconds(v3) : time(v3);
33185
33210
  function dbPaths(id2, defaults, match2) {
33186
33211
  return async () => {
33187
- const override = process.env[`SESSIONGRAPH_${id2.replace(/-/g, "_").toUpperCase()}_HISTORY_DIR`];
33212
+ const override = sourcePath(id2) || process.env[`SESSIONGRAPH_${id2.replace(/-/g, "_").toUpperCase()}_HISTORY_DIR`];
33188
33213
  return override ? (await historyFiles([override], match2)).files : existingDatabases(defaults());
33189
33214
  };
33190
33215
  }
@@ -33324,21 +33349,21 @@ var crush = {
33324
33349
  key: "id",
33325
33350
  fields: ["id", "title", "created_at", "updated_at", "cost"],
33326
33351
  paths: async () => {
33327
- const override = process.env.SESSIONGRAPH_CRUSH_HISTORY_DIR;
33352
+ const override = sourcePath("crush") || process.env.SESSIONGRAPH_CRUSH_HISTORY_DIR;
33328
33353
  if (override) return (await historyFiles([override], (p) => p.endsWith("crush.db"))).files;
33329
33354
  const registry = join22(process.env.CRUSH_GLOBAL_DATA || dataPath("crush"), "projects.json");
33330
33355
  try {
33331
33356
  const stat6 = await lstat6(registry);
33332
33357
  if (!stat6.isFile() || stat6.size > 2e6) throw new Error("Unsupported Crush registry.");
33333
33358
  const raw = JSON.parse(await readFile4(registry, "utf8"));
33334
- const paths = [];
33359
+ const paths2 = [];
33335
33360
  for (const p of Array.isArray(raw.projects) ? raw.projects.slice(0, 2e4) : []) {
33336
33361
  if (!isAbsolute5(text(p.path)) || !text(p.data_dir)) continue;
33337
33362
  const path = join22(isAbsolute5(p.data_dir) ? p.data_dir : join22(p.path, p.data_dir), "crush.db");
33338
- paths.push(path);
33363
+ paths2.push(path);
33339
33364
  crushDirectories.set(path, p.path);
33340
33365
  }
33341
- return existingDatabases(paths);
33366
+ return existingDatabases(paths2);
33342
33367
  } catch (e10) {
33343
33368
  if (e10.code === "ENOENT") return [];
33344
33369
  throw e10;
@@ -33759,6 +33784,46 @@ function visibleSessions(sessions) {
33759
33784
  }));
33760
33785
  }
33761
33786
 
33787
+ // server/library-query.ts
33788
+ import { createHash as createHash4 } from "node:crypto";
33789
+
33790
+ // packages/core/src/layout.ts
33791
+ var directoryPath = (directory2) => (/^(?:[a-z]:[\\/]|\\\\)/i.test(directory2) ? directory2.replace(/\\/g, "/") : directory2).replace(/\/+$/, "") || (directory2.startsWith("/") ? "/" : "");
33792
+ var laneKey = (session) => session.directory ? JSON.stringify(["directory", directoryPath(session.directory)]) : JSON.stringify(["project", session.source || "opencode", session.projectID || session.id]);
33793
+
33794
+ // server/library-query.ts
33795
+ var LibraryError = class extends Error {
33796
+ constructor(code, message2, exitCode = 1) {
33797
+ super(message2);
33798
+ this.code = code;
33799
+ this.exitCode = exitCode;
33800
+ }
33801
+ code;
33802
+ exitCode;
33803
+ };
33804
+ function invalid(message2) {
33805
+ throw new LibraryError("INVALID_ARGUMENT", message2, 2);
33806
+ }
33807
+ var fingerprint = (value) => createHash4("sha256").update(JSON.stringify(value)).digest("hex");
33808
+ function pageLimit(value = 50) {
33809
+ if (!Number.isSafeInteger(value) || value < 1 || value > 200) invalid("Limit must be an integer from 1 to 200.");
33810
+ return value;
33811
+ }
33812
+ function pageOffset(cursor, key) {
33813
+ if (!cursor) return 0;
33814
+ let parsed;
33815
+ try {
33816
+ if (cursor.length > 512 || !/^[\w-]+$/.test(cursor)) throw new Error();
33817
+ parsed = JSON.parse(Buffer.from(cursor, "base64url").toString());
33818
+ if (!parsed || !Number.isSafeInteger(parsed.offset) || parsed.offset < 0 || typeof parsed.key !== "string") throw new Error();
33819
+ } catch {
33820
+ return invalid("Invalid cursor. Use nextCursor from the previous response.");
33821
+ }
33822
+ if (parsed.key !== key) throw new LibraryError("STALE_CURSOR", "Saved history or query changed. Restart without --cursor.");
33823
+ return parsed.offset;
33824
+ }
33825
+ var nextCursor = (key, offset) => Buffer.from(JSON.stringify({ key, offset })).toString("base64url");
33826
+
33762
33827
  // server/store.ts
33763
33828
  var SessionGraphStore = class {
33764
33829
  constructor(path = databasePath()) {
@@ -34008,6 +34073,41 @@ var SessionGraphStore = class {
34008
34073
  truncated ||= this.getSession(id2)?.coverage?.history !== "complete";
34009
34074
  return { id: id2, fragments, messages: page.messages.length, nextCursor: page.next, truncated };
34010
34075
  }
34076
+ messages(id2, options = {}) {
34077
+ return this.readSnapshot(id2, (session) => {
34078
+ const key = fingerprint(["messages", id2, session.revision, session.coverage]);
34079
+ const offset = pageOffset(options.cursor, key), limit = pageLimit(options.limit);
34080
+ const rows2 = this.messagePage(id2, offset, limit);
34081
+ let remaining = 64e3, truncated = false;
34082
+ const messages = rows2.messages.map((message2) => {
34083
+ const fragments = message2.fragments.slice(0, 200).map((fragment) => {
34084
+ const text2 = fragment.text.slice(0, remaining);
34085
+ remaining -= text2.length;
34086
+ truncated ||= text2.length < fragment.text.length;
34087
+ return { ...fragment, text: text2, truncated: text2.length < fragment.text.length };
34088
+ });
34089
+ truncated ||= !!message2.partial;
34090
+ return {
34091
+ id: message2.id,
34092
+ time: messageTime(message2.time),
34093
+ role: message2.role,
34094
+ fragments,
34095
+ files: message2.files.slice(0, 200),
34096
+ tools: message2.tools.slice(0, 200),
34097
+ partial: !!message2.partial || message2.fragments.length > 200 || message2.files.length > 200 || message2.tools.length > 200 || fragments.some((f) => f.truncated)
34098
+ };
34099
+ });
34100
+ return {
34101
+ sessionID: id2,
34102
+ revision: session.revision,
34103
+ messages,
34104
+ order: "newest-first",
34105
+ nextCursor: rows2.next ? nextCursor(key, Number(rows2.next)) : null,
34106
+ partial: truncated || messages.some((m) => m.partial) || session.coverage?.history !== "complete",
34107
+ coverage: session.coverage
34108
+ };
34109
+ });
34110
+ }
34011
34111
  detail(id2, cursor) {
34012
34112
  return this.readSnapshot(id2, (session) => {
34013
34113
  const page = this.messagePage(id2, Number(cursor || 0), 120);
@@ -34093,26 +34193,33 @@ import { setImmediate as yieldToIO } from "node:timers/promises";
34093
34193
  var Importer = class {
34094
34194
  constructor(store, adapters2) {
34095
34195
  this.store = store;
34096
- this.adapters = adapters2;
34196
+ this.adapters = adapters2.map((adapter) => withSourcePaths(adapter, () => this.store.setting("sourcePaths") || {}));
34097
34197
  }
34098
34198
  store;
34099
- adapters;
34100
34199
  status = { running: false, discovered: 0, imported: 0, unchanged: 0, failed: 0, errors: [] };
34101
34200
  job;
34201
+ adapters;
34102
34202
  async sources() {
34103
34203
  return Promise.all(this.adapters.map(async (adapter) => ({ id: adapter.id, name: adapter.name, description: adapter.description, capabilities: adapter.capabilities, available: await adapter.detect().catch(() => false), selected: this.store.selected().includes(adapter.id), ...this.store.sourceInfo(adapter.id) })));
34104
34204
  }
34105
34205
  start(ids, automatic = false) {
34206
+ return this.begin(ids, automatic, true);
34207
+ }
34208
+ /** A one-off subset refresh never replaces the saved source selection. */
34209
+ sync(ids) {
34210
+ return this.begin(ids, false, false);
34211
+ }
34212
+ begin(ids, automatic, save) {
34106
34213
  if (this.job) return this.status;
34107
34214
  if (automatic && (!this.store.autoImportEnabled() || !this.store.selected().length)) return this.status;
34108
34215
  if (ids?.some((id2) => !this.adapters.some((a) => a.id === id2))) throw new Error("Unknown session source.");
34109
34216
  if (!this.store.acquireImport()) throw new Error("Another SessionGraph window is importing this library.");
34110
- if (ids) {
34217
+ if (ids && save) {
34111
34218
  this.store.setting("selected", [...new Set(ids)]);
34112
34219
  }
34113
34220
  this.status = { running: true, discovered: 0, imported: 0, unchanged: 0, failed: 0, errors: [] };
34114
34221
  const heartbeat = setInterval(() => this.store.heartbeat(), 5e3);
34115
- this.job = this.run().catch(() => {
34222
+ this.job = this.run(ids || this.store.selected()).catch(() => {
34116
34223
  this.status.failed++;
34117
34224
  this.status.errors.push("Import interrupted. Re-import to resume remaining sessions.");
34118
34225
  }).finally(() => {
@@ -34127,8 +34234,8 @@ var Importer = class {
34127
34234
  async settled() {
34128
34235
  await this.job;
34129
34236
  }
34130
- async run() {
34131
- for (const adapter of this.adapters.filter((a) => this.store.selected().includes(a.id))) {
34237
+ async run(ids) {
34238
+ for (const adapter of this.adapters.filter((a) => ids.includes(a.id))) {
34132
34239
  this.status.source = adapter.id;
34133
34240
  try {
34134
34241
  if (!await adapter.detect()) throw new Error(`${adapter.name} is unavailable. Saved imports remain available.`);
@@ -34225,6 +34332,7 @@ var HARNESSES = [
34225
34332
  var isHarness = (value) => HARNESSES.some((h3) => h3.id === value);
34226
34333
  var validSessionID = (id2) => /^ses[a-zA-Z0-9_-]+$/.test(id2);
34227
34334
  function resumeArgs(value) {
34335
+ if (typeof value !== "string" && value.nativeResume === false) throw new Error("This saved history requires a context handoff.");
34228
34336
  const source = typeof value === "string" ? "opencode" : value.source || "opencode";
34229
34337
  const id2 = typeof value === "string" ? value : value.nativeID || value.id;
34230
34338
  if (source !== "opencode") {
@@ -34289,7 +34397,7 @@ function launchCommand(binary, args, directory2, platform = process.platform) {
34289
34397
  }
34290
34398
 
34291
34399
  // server/handoff.ts
34292
- import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
34400
+ import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
34293
34401
  import { mkdir, writeFile as writeFile2, rename as rename2, rm as rm4 } from "node:fs/promises";
34294
34402
  import { join as join24 } from "node:path";
34295
34403
  function handoffDocument(snapshot2) {
@@ -34356,7 +34464,7 @@ This handoff transfers saved text and file-change evidence, not files, credentia
34356
34464
  }
34357
34465
  async function saveHandoff(snapshot2, directory2) {
34358
34466
  const { text: text2, ...coverage2 } = handoffDocument(snapshot2);
34359
- const digest2 = createHash4("sha256").update(text2).digest("hex");
34467
+ const digest2 = createHash5("sha256").update(text2).digest("hex");
34360
34468
  const root3 = join24(directory2, "handoffs");
34361
34469
  await mkdir(root3, { recursive: true, mode: 448 });
34362
34470
  const path = join24(root3, `${digest2}.md`), temporary = join24(root3, `.${randomUUID3()}.tmp`);
@@ -34396,7 +34504,7 @@ async function prepareLaunch(store, session, target, directory2 = session.direct
34396
34504
  const [binary, terminal] = await Promise.all([harnessExecutable(target), terminalExecutable()]);
34397
34505
  let args;
34398
34506
  const subagent = session.source === "claude-code" && Boolean(session.parentID) && /^[\w-]+\/agent\/[\w-]+$/.test(session.nativeID || "");
34399
- const localHandoff = ["cursor", "gemini", "qwen-code", "pi", "copilot"].includes(session.source || "") && (!session.nativeResume || directory2 !== session.directory);
34507
+ const localHandoff = session.nativeResume === false || ["cursor", "gemini", "qwen-code", "pi", "copilot"].includes(session.source || "") && (!session.nativeResume || directory2 !== session.directory);
34400
34508
  if (target === (session.source || "opencode") && !subagent && !localHandoff) args = resumeArgs(session);
34401
34509
  const mode = args ? "resume" : "handoff";
34402
34510
  if (mode === "handoff" && store.path === ":memory:") throw new Error("Save this library before creating a context handoff.");
@@ -34442,14 +34550,10 @@ async function launchTerminal(session, prepared) {
34442
34550
  }
34443
34551
 
34444
34552
  // server/publishing.ts
34445
- import { createHash as createHash5, randomBytes } from "node:crypto";
34553
+ import { createHash as createHash6, randomBytes } from "node:crypto";
34446
34554
  import { chmod, mkdir as mkdir2, readFile as readFile5, rename as rename3, rm as rm5, writeFile as writeFile3 } from "node:fs/promises";
34447
34555
  import { dirname as dirname10, join as join25 } from "node:path";
34448
34556
 
34449
- // packages/core/src/layout.ts
34450
- var directoryPath = (directory2) => (/^(?:[a-z]:[\\/]|\\\\)/i.test(directory2) ? directory2.replace(/\\/g, "/") : directory2).replace(/\/+$/, "") || (directory2.startsWith("/") ? "/" : "");
34451
- var laneKey = (session) => session.directory ? JSON.stringify(["directory", directoryPath(session.directory)]) : JSON.stringify(["project", session.source || "opencode", session.projectID || session.id]);
34452
-
34453
34557
  // packages/core/src/sources.ts
34454
34558
  var sources = {
34455
34559
  opencode: { name: "OpenCode", icon: "opencode" },
@@ -34622,7 +34726,7 @@ var PublishingError = class extends Error {
34622
34726
  status;
34623
34727
  };
34624
34728
  var SITE = "https://sessiongraph.ai";
34625
- var hash = (value) => createHash5("sha256").update(value).digest("hex");
34729
+ var hash = (value) => createHash6("sha256").update(value).digest("hex");
34626
34730
  var token = () => randomBytes(32).toString("base64url");
34627
34731
  function preparePublicGraph(store, ids, inputSettings) {
34628
34732
  if (!Array.isArray(ids) || !ids.length || ids.length > PUBLIC_GRAPH_MAX_SESSIONS || ids.some((id2) => typeof id2 !== "string") || new Set(ids).size !== ids.length) throw new PublishingError("Choose a valid set of sessions.");
@@ -34703,11 +34807,12 @@ var PublishingClient = class {
34703
34807
  }
34704
34808
  async call(path, method = "GET", value, credential) {
34705
34809
  const response = await this.fetcher(`${SITE}/api/${path}`, { method, redirect: "error", signal: AbortSignal.timeout(25e3), headers: { "Content-Type": "application/json", "x-sessiongraph-action": "1", ...credential ? { Authorization: `Bearer ${credential.token}` } : {} }, ...value === void 0 ? {} : { body: JSON.stringify(value) } });
34706
- const data = await response.json().catch(() => ({}));
34810
+ const data = await response.json().catch(() => null);
34707
34811
  if (!response.ok) {
34708
34812
  if (response.status === 401) await rm5(this.credentialPath, { force: true });
34709
- throw new PublishingError(typeof data.error === "string" ? data.error : "The publishing service is unavailable.", response.status);
34813
+ throw new PublishingError(typeof data?.error === "string" ? data.error : "The publishing service is unavailable.", response.status);
34710
34814
  }
34815
+ if (!data || typeof data !== "object" || Array.isArray(data)) throw new PublishingError("The publishing service returned an unexpected response. Please try again.", 502);
34711
34816
  return data;
34712
34817
  }
34713
34818
  async status() {
@@ -34719,7 +34824,7 @@ var PublishingClient = class {
34719
34824
  if (this.busy) throw new PublishingError("Wait for the current account operation to finish.", 409);
34720
34825
  const verifier = token(), challenge = hash(verifier);
34721
34826
  this.pending = { verifier, expires: Date.now() + 6e5, nextPoll: 0 };
34722
- return { url: `${SITE}/account?device=${challenge}`, code: challenge.slice(0, 8).toUpperCase() };
34827
+ return { url: `${SITE}/api/auth/github?device=${challenge}`, code: challenge.slice(0, 8).toUpperCase() };
34723
34828
  }
34724
34829
  async poll() {
34725
34830
  const pending2 = this.pending;
@@ -34731,8 +34836,8 @@ var PublishingClient = class {
34731
34836
  pending2.nextPoll = Date.now() + 3e3;
34732
34837
  return this.exclusive(async () => {
34733
34838
  const result = await this.call("auth/device", "POST", { verifier: pending2.verifier });
34734
- if (this.pending !== pending2 || result.pending) return { pending: true };
34735
- if (typeof result.token !== "string" || !/^[\w-]{43}$/.test(result.token) || typeof result.username !== "string" || !Number.isFinite(Date.parse(result.expiresAt))) throw new PublishingError("Invalid login response.");
34839
+ if (this.pending !== pending2 || result.pending === true) return { pending: true };
34840
+ if (typeof result.token !== "string" || !/^[\w-]{43}$/.test(result.token) || typeof result.username !== "string" || !Number.isFinite(Date.parse(result.expiresAt)) || Date.parse(result.expiresAt) <= Date.now()) throw new PublishingError("The publishing service returned an invalid login response. Please try again.", 502);
34736
34841
  await this.save(result);
34737
34842
  this.pending = void 0;
34738
34843
  return { username: result.username };
@@ -34790,7 +34895,7 @@ function library() {
34790
34895
  if (runtime.sessionGraphRuntime?.importer.status.running && runtime.sessionGraphRuntime.generation !== generation) {
34791
34896
  for (const live of runtime.sessionGraphRuntime.importer.adapters) {
34792
34897
  const updated = adapters.find((adapter) => adapter.id === live.id);
34793
- if (updated) Object.assign(live, updated);
34898
+ if (updated) Object.assign(live, withSourcePaths(updated, () => runtime.sessionGraphRuntime.store.setting("sourcePaths") || {}));
34794
34899
  }
34795
34900
  }
34796
34901
  if (runtime.sessionGraphRuntime && runtime.sessionGraphRuntime.generation !== generation && !runtime.sessionGraphRuntime.importer.status.running) {
@@ -0,0 +1 @@
1
+ import{n as u}from"./index-CIhs02tq.js";function p(t,e,c){t.open&&(t.addEventListener("close",()=>c(e),{once:!0}),t.close())}function b(t,e,c){const l=document.createDocumentFragment(),i=document.createElement("header");i.className="sharing-header";const s=document.createElement("h2");s.id=`sharing-${e}-title`,s.textContent="Share SessionGraph",t.setAttribute("aria-labelledby",s.id);const n=document.createElement("button");n.type="button",n.className="text-button",n.textContent="Close",n.setAttribute("aria-label","Close sharing"),n.onclick=()=>t.close(),i.append(s,n);const r=document.createElement("div");r.className="sharing-modes",r.setAttribute("role","group"),r.setAttribute("aria-label","Share method");for(const a of["publish","video"]){const o=document.createElement("button");o.type="button",o.dataset.shareMode=a,o.textContent=a==="publish"?"Publish":"Video",o.setAttribute("aria-pressed",String(a===e)),o.onclick=()=>{a!==e&&p(t,a,c)},r.append(o)}return l.append(i,r),l}async function d(t,e){await u("plugin:opener|open_url",{url:t,with:e})}function h(t){const e=new URL(t);if(!["https:","http:"].includes(e.protocol)||e.username||e.password)throw new Error("Choose a valid web link.");return e.href}async function f(t,e=d){await e(h(t))}export{h as e,f as o,b as s};