anywhere-ai 0.0.31 → 0.0.33

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 (3) hide show
  1. package/dist/cli.js +9 -9
  2. package/dist/server.js +195 -112
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -17,13 +17,13 @@ var STATUS_PATH = path.join(ANYWHERE_DIR, "status.json");
17
17
  var args = process.argv.slice(2);
18
18
  var command = args.find((a) => !a.startsWith("-"));
19
19
  if (args.includes("--version") || args.includes("-v")) {
20
- console.log(`anywhere-ai v${"0.0.31"}`);
20
+ console.log(`anywhere-ai v${"0.0.33"}`);
21
21
  process.exit(0);
22
22
  }
23
23
  if (args.includes("--help") || args.includes("-h") || command === "help") {
24
- console.log(`anywhere-ai v${"0.0.31"} \u2014 Mobile coding agent
24
+ console.log(`anywhere-ai v${"0.0.33"} \u2014 Mobile coding agent
25
25
 
26
- Usage: anywhere-ai [command] [options]
26
+ Usage: npx anywhere-ai [command] [options]
27
27
 
28
28
  Commands:
29
29
  status Show server status and connection info
@@ -112,7 +112,7 @@ if (command === "regenerate-token") {
112
112
  try {
113
113
  await fs.access(CONFIG_PATH);
114
114
  } catch {
115
- console.error("No config found. Run `anywhere-ai` first to set up.");
115
+ console.error("No config found. Run `npx anywhere-ai` first to set up.");
116
116
  process.exit(1);
117
117
  }
118
118
  const config2 = JSON.parse(await fs.readFile(CONFIG_PATH, "utf-8"));
@@ -124,16 +124,16 @@ if (command === "regenerate-token") {
124
124
  var isDaemon = !args.includes("--foreground");
125
125
  var existingPid = getDaemonPid();
126
126
  if (existingPid) {
127
- console.log(`Server already running (PID ${existingPid}). Use 'anywhere-ai stop' first.`);
127
+ console.log(`Server already running (PID ${existingPid}). Use 'npx anywhere-ai stop' first.`);
128
128
  process.exit(1);
129
129
  }
130
130
  async function checkForUpdate() {
131
131
  try {
132
132
  const res = await fetch("https://registry.npmjs.org/anywhere-ai/latest", { signal: AbortSignal.timeout(3e3) });
133
133
  const data = await res.json();
134
- if (data.version && data.version !== "0.0.31") {
134
+ if (data.version && data.version !== "0.0.33") {
135
135
  console.log(`
136
- Update available: v${"0.0.31"} \u2192 v${data.version}`);
136
+ Update available: v${"0.0.33"} \u2192 v${data.version}`);
137
137
  console.log(` Run: npx anywhere-ai@latest
138
138
  `);
139
139
  }
@@ -429,8 +429,8 @@ if (isDaemon) {
429
429
  });
430
430
  console.log(" Server will keep running after terminal closes.");
431
431
  console.log(" Use --foreground to disable this behavior.");
432
- console.log(` Status: anywhere-ai status`);
433
- console.log(` Stop: anywhere-ai stop`);
432
+ console.log(` Status: npx anywhere-ai status`);
433
+ console.log(` Stop: npx anywhere-ai stop`);
434
434
  console.log();
435
435
  }
436
436
  function cleanup() {
package/dist/server.js CHANGED
@@ -300,6 +300,32 @@ ${prompt.slice(0, 500)}`;
300
300
 
301
301
  // src/routes/chats/index.ts
302
302
  var repoNameCache = /* @__PURE__ */ new Map();
303
+ var sessionFileIndex = /* @__PURE__ */ new Map();
304
+ var indexBuilt = false;
305
+ async function buildSessionIndex() {
306
+ const projectsBase = path3.join(os3.homedir(), ".claude", "projects");
307
+ try {
308
+ const dirs = await readdir2(projectsBase);
309
+ for (const dir of dirs) {
310
+ const dirPath = path3.join(projectsBase, dir);
311
+ try {
312
+ const s = await stat2(dirPath);
313
+ if (!s.isDirectory()) continue;
314
+ const files = await readdir2(dirPath);
315
+ for (const file of files) {
316
+ if (!file.endsWith(".jsonl")) continue;
317
+ const sid = file.replace(".jsonl", "");
318
+ sessionFileIndex.set(sid, path3.join(dirPath, file));
319
+ }
320
+ } catch {
321
+ }
322
+ }
323
+ } catch {
324
+ }
325
+ indexBuilt = true;
326
+ }
327
+ buildSessionIndex();
328
+ var chatDetailCache = /* @__PURE__ */ new Map();
303
329
  function isHomeDir(dir) {
304
330
  const home = os3.homedir();
305
331
  if (dir === home) return true;
@@ -359,6 +385,15 @@ function deriveRepoName(cwd) {
359
385
  return name;
360
386
  }
361
387
  async function findSessionFile(sessionId) {
388
+ if (sessionFileIndex.has(sessionId)) {
389
+ const cached = sessionFileIndex.get(sessionId);
390
+ try {
391
+ await stat2(cached);
392
+ return cached;
393
+ } catch {
394
+ sessionFileIndex.delete(sessionId);
395
+ }
396
+ }
362
397
  const projectsBase = path3.join(os3.homedir(), ".claude", "projects");
363
398
  try {
364
399
  const dirs = await readdir2(projectsBase);
@@ -366,6 +401,7 @@ async function findSessionFile(sessionId) {
366
401
  const filePath = path3.join(projectsBase, dir, `${sessionId}.jsonl`);
367
402
  try {
368
403
  await stat2(filePath);
404
+ sessionFileIndex.set(sessionId, filePath);
369
405
  return filePath;
370
406
  } catch {
371
407
  }
@@ -551,6 +587,64 @@ chats.post("/:id/permission", async (c) => {
551
587
  }
552
588
  return c.json({ ok: true });
553
589
  });
590
+ var listEntryCache = /* @__PURE__ */ new Map();
591
+ function parseListEntry(content, sessionId, fileMtimeIso, chatName) {
592
+ const lines = content.split("\n").filter(Boolean);
593
+ let preview = "";
594
+ let timestamp = "";
595
+ let cwd = "";
596
+ let gitBranch = "";
597
+ let lastMessage = "";
598
+ let lastTimestamp = "";
599
+ for (const line of lines) {
600
+ try {
601
+ const obj = JSON.parse(line);
602
+ if (obj.type === "user") {
603
+ if (!cwd && obj.cwd) cwd = obj.cwd;
604
+ if (!gitBranch && obj.gitBranch) gitBranch = obj.gitBranch;
605
+ if (!obj.isMeta) {
606
+ if (!preview) {
607
+ const msgContent = obj.message.content;
608
+ if (typeof msgContent === "string") {
609
+ preview = msgContent.slice(0, 100);
610
+ } else {
611
+ preview = msgContent.filter((b) => b.type === "text").map((b) => b.text).join("").slice(0, 100);
612
+ }
613
+ timestamp = obj.timestamp;
614
+ }
615
+ if (obj.timestamp) lastTimestamp = obj.timestamp;
616
+ }
617
+ }
618
+ if (obj.type === "assistant") {
619
+ if (obj.timestamp) lastTimestamp = obj.timestamp;
620
+ const content2 = obj.message?.content;
621
+ if (typeof content2 === "string") {
622
+ lastMessage = content2.slice(0, 120);
623
+ } else if (Array.isArray(content2)) {
624
+ const text = content2.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
625
+ if (text) lastMessage = text.slice(0, 120);
626
+ }
627
+ }
628
+ } catch {
629
+ continue;
630
+ }
631
+ }
632
+ if (!timestamp) return null;
633
+ const updatedAt = lastTimestamp || fileMtimeIso;
634
+ const repo = deriveRepoName(cwd);
635
+ return {
636
+ sessionId,
637
+ text: preview,
638
+ timestamp,
639
+ updatedAt,
640
+ lastMessage,
641
+ isActive: activeSessions.has(sessionId),
642
+ repo,
643
+ cwd,
644
+ gitBranch,
645
+ chatName
646
+ };
647
+ }
554
648
  chats.get("/", async (c) => {
555
649
  try {
556
650
  const projectsBase = path3.join(os3.homedir(), ".claude", "projects");
@@ -570,64 +664,30 @@ chats.get("/", async (c) => {
570
664
  );
571
665
  for (const file of files) {
572
666
  const filePath = path3.join(dirPath, file);
573
- const content = await readFile2(filePath, "utf-8");
574
- const lines = content.split("\n").filter(Boolean);
575
667
  const sessionId = file.replace(".jsonl", "");
576
- let preview = "";
577
- let timestamp = "";
578
- let cwd = "";
579
- let gitBranch = "";
580
- let lastMessage = "";
581
- let lastTimestamp = "";
582
- for (const line of lines) {
583
- try {
584
- const obj = JSON.parse(line);
585
- if (obj.type === "user") {
586
- if (!cwd && obj.cwd) cwd = obj.cwd;
587
- if (!gitBranch && obj.gitBranch) gitBranch = obj.gitBranch;
588
- if (!obj.isMeta) {
589
- if (!preview) {
590
- const msgContent = obj.message.content;
591
- if (typeof msgContent === "string") {
592
- preview = msgContent.slice(0, 100);
593
- } else {
594
- preview = msgContent.filter((b) => b.type === "text").map((b) => b.text).join("").slice(0, 100);
595
- }
596
- timestamp = obj.timestamp;
597
- }
598
- if (obj.timestamp) lastTimestamp = obj.timestamp;
599
- }
600
- }
601
- if (obj.type === "assistant") {
602
- if (obj.timestamp) lastTimestamp = obj.timestamp;
603
- const content2 = obj.message?.content;
604
- if (typeof content2 === "string") {
605
- lastMessage = content2.slice(0, 120);
606
- } else if (Array.isArray(content2)) {
607
- const text = content2.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
608
- if (text) lastMessage = text.slice(0, 120);
609
- }
610
- }
611
- } catch {
612
- continue;
613
- }
614
- }
615
- if (!timestamp) continue;
616
668
  const fileStat = await stat2(filePath);
617
- const updatedAt = lastTimestamp || fileStat.mtime.toISOString();
618
- const repo = deriveRepoName(cwd);
619
- allChats.push({
669
+ const cached = listEntryCache.get(sessionId);
670
+ if (cached && cached.mtimeMs === fileStat.mtimeMs) {
671
+ const entry2 = {
672
+ ...cached.entry,
673
+ isActive: activeSessions.has(sessionId),
674
+ chatName: chatNamesMap[sessionId] || ""
675
+ };
676
+ allChats.push(entry2);
677
+ continue;
678
+ }
679
+ const content = await readFile2(filePath, "utf-8");
680
+ const entry = parseListEntry(
681
+ content,
620
682
  sessionId,
621
- text: preview,
622
- timestamp,
623
- updatedAt,
624
- lastMessage,
625
- isActive: activeSessions.has(sessionId),
626
- repo,
627
- cwd,
628
- gitBranch,
629
- chatName: chatNamesMap[sessionId] || ""
630
- });
683
+ fileStat.mtime.toISOString(),
684
+ chatNamesMap[sessionId] || ""
685
+ );
686
+ if (entry) {
687
+ listEntryCache.set(sessionId, { mtimeMs: fileStat.mtimeMs, entry });
688
+ sessionFileIndex.set(sessionId, filePath);
689
+ allChats.push(entry);
690
+ }
631
691
  }
632
692
  }
633
693
  return c.json({ result: allChats });
@@ -640,71 +700,94 @@ chats.get("/:id", async (c) => {
640
700
  const sessionId = c.req.param("id");
641
701
  if (!sessionId || /[\/\\]/.test(sessionId))
642
702
  return c.json({ error: "Invalid session id" }, 400);
703
+ const limit = Math.min(Math.max(parseInt(c.req.query("limit") || "15", 10) || 15, 1), 500);
704
+ const offset = Math.max(parseInt(c.req.query("offset") || "0", 10) || 0, 0);
643
705
  try {
644
706
  const file = await findSessionFile(sessionId);
645
707
  if (!file) return c.json({ error: "Chat not found" }, 404);
646
- const content = await readFile2(file, "utf-8");
647
- const lines = content.split("\n").filter(Boolean);
648
- const parsed = lines.map((line) => JSON.parse(line));
708
+ const fileStat = await stat2(file);
709
+ const cached = chatDetailCache.get(sessionId);
710
+ let allMessages;
649
711
  let model;
650
712
  let permissionMode;
651
713
  let cwd;
652
- for (const obj of parsed) {
653
- if (obj.type === "user" && obj.cwd && !cwd) cwd = obj.cwd;
654
- }
655
- for (const obj of [...parsed].reverse()) {
656
- if (!model && obj.type === "assistant") model = obj.message?.model;
657
- if (!permissionMode && obj.type === "user" && !obj.isMeta)
658
- permissionMode = obj.permissionMode;
659
- if (model && permissionMode) break;
660
- }
661
- const messages = parsed.filter(
662
- (obj) => obj.type === "user" && !obj.isMeta || obj.type === "assistant"
663
- ).flatMap((obj) => {
664
- const content2 = obj.message.content;
665
- if (typeof content2 === "string") {
666
- return [
667
- {
668
- role: obj.message.role,
669
- type: "text",
670
- text: content2.trim(),
671
- timestamp: obj.timestamp
672
- }
673
- ];
714
+ let chatName;
715
+ if (cached && cached.mtimeMs === fileStat.mtimeMs) {
716
+ allMessages = cached.response.messages;
717
+ model = cached.response.model;
718
+ permissionMode = cached.response.permissionMode;
719
+ cwd = cached.response.cwd;
720
+ chatName = cached.response.chatName;
721
+ } else {
722
+ const content = await readFile2(file, "utf-8");
723
+ const lines = content.split("\n").filter(Boolean);
724
+ const parsed = lines.map((line) => JSON.parse(line));
725
+ for (const obj of parsed) {
726
+ if (obj.type === "user" && obj.cwd && !cwd) cwd = obj.cwd;
674
727
  }
675
- return content2.filter(
676
- (b) => ["text", "tool_use", "tool_result"].includes(b.type)
677
- ).map((b) => {
678
- if (b.type === "text") {
679
- return {
680
- role: obj.message.role,
681
- type: "text",
682
- text: b.text.trim(),
683
- timestamp: obj.timestamp
684
- };
685
- }
686
- if (b.type === "tool_use") {
687
- return {
688
- role: obj.message.role,
689
- type: "tool_use",
690
- name: b.name,
691
- input: b.input,
692
- timestamp: obj.timestamp
693
- };
694
- }
695
- if (b.type === "tool_result") {
696
- const resultText = Array.isArray(b.content) ? b.content.filter((r) => r.type === "text").map((r) => r.text).join("") : b.content ?? "";
697
- return {
698
- role: obj.message.role,
699
- type: "tool_result",
700
- text: resultText.trim(),
701
- timestamp: obj.timestamp
702
- };
728
+ for (const obj of [...parsed].reverse()) {
729
+ if (!model && obj.type === "assistant") model = obj.message?.model;
730
+ if (!permissionMode && obj.type === "user" && !obj.isMeta)
731
+ permissionMode = obj.permissionMode;
732
+ if (model && permissionMode) break;
733
+ }
734
+ allMessages = parsed.filter(
735
+ (obj) => obj.type === "user" && !obj.isMeta || obj.type === "assistant"
736
+ ).flatMap((obj) => {
737
+ const content2 = obj.message.content;
738
+ if (typeof content2 === "string") {
739
+ return [
740
+ {
741
+ role: obj.message.role,
742
+ type: "text",
743
+ text: content2.trim(),
744
+ timestamp: obj.timestamp
745
+ }
746
+ ];
703
747
  }
704
- }).filter(Boolean);
705
- });
706
- const chatName = await getChatName(sessionId);
707
- return c.json({ messages, model, permissionMode, cwd, chatName: chatName || "" });
748
+ return content2.filter(
749
+ (b) => ["text", "tool_use", "tool_result"].includes(b.type)
750
+ ).map((b) => {
751
+ if (b.type === "text") {
752
+ return {
753
+ role: obj.message.role,
754
+ type: "text",
755
+ text: b.text.trim(),
756
+ timestamp: obj.timestamp
757
+ };
758
+ }
759
+ if (b.type === "tool_use") {
760
+ return {
761
+ role: obj.message.role,
762
+ type: "tool_use",
763
+ name: b.name,
764
+ input: b.input,
765
+ timestamp: obj.timestamp
766
+ };
767
+ }
768
+ if (b.type === "tool_result") {
769
+ const resultText = Array.isArray(b.content) ? b.content.filter((r) => r.type === "text").map((r) => r.text).join("") : b.content ?? "";
770
+ return {
771
+ role: obj.message.role,
772
+ type: "tool_result",
773
+ text: resultText.trim(),
774
+ timestamp: obj.timestamp
775
+ };
776
+ }
777
+ }).filter(Boolean);
778
+ });
779
+ chatName = await getChatName(sessionId) || "";
780
+ chatDetailCache.set(sessionId, {
781
+ mtimeMs: fileStat.mtimeMs,
782
+ response: { messages: allMessages, model, permissionMode, cwd, chatName }
783
+ });
784
+ }
785
+ const total = allMessages.length;
786
+ const start = Math.max(total - offset - limit, 0);
787
+ const end = Math.max(total - offset, 0);
788
+ const messages = allMessages.slice(start, end);
789
+ const hasMore = start > 0;
790
+ return c.json({ messages, model, permissionMode, cwd, chatName, total, hasMore });
708
791
  } catch {
709
792
  return c.json({ error: "Chat not found" }, 404);
710
793
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anywhere-ai",
3
- "version": "0.0.31",
3
+ "version": "0.0.33",
4
4
  "type": "module",
5
5
  "description": "Code on any repo from your phone",
6
6
  "bin": {