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.
- package/dist/cli.js +9 -9
- package/dist/server.js +195 -112
- 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.
|
|
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.
|
|
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.
|
|
134
|
+
if (data.version && data.version !== "0.0.33") {
|
|
135
135
|
console.log(`
|
|
136
|
-
Update available: v${"0.0.
|
|
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
|
|
618
|
-
|
|
619
|
-
|
|
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
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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
|
|
647
|
-
const
|
|
648
|
-
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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
|
-
|
|
676
|
-
(
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
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
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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
|
}
|