anywhere-ai 0.0.9 → 0.0.10

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 CHANGED
@@ -10,7 +10,7 @@ import { spawnSync, execSync } from "child_process";
10
10
  import readline from "readline";
11
11
  var args = process.argv.slice(2);
12
12
  if (args.includes("--version") || args.includes("-v")) {
13
- console.log(`anywhere-ai v${"0.0.9"}`);
13
+ console.log(`anywhere-ai v${"0.0.10"}`);
14
14
  process.exit(0);
15
15
  }
16
16
  function ask(question, preserveCase = false) {
@@ -184,4 +184,4 @@ if (isVPS) {
184
184
  console.log(" Make sure port " + port + " is open in your firewall.");
185
185
  }
186
186
  console.log();
187
- import("./server-KMHUP4CE.js");
187
+ import("./server-QUVKJH7W.js");
@@ -0,0 +1,661 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/server.ts
4
+ import { Hono as Hono4 } from "hono";
5
+ import { serve } from "@hono/node-server";
6
+ import { cors } from "hono/cors";
7
+
8
+ // src/routes/chats/index.ts
9
+ import { Hono } from "hono";
10
+ import os2 from "os";
11
+ import path2 from "path";
12
+ import { readdir, readFile, stat } from "fs/promises";
13
+ import { execSync as execSync2 } from "child_process";
14
+ import { streamSSE } from "hono/streaming";
15
+
16
+ // src/chats.ts
17
+ import {
18
+ unstable_v2_createSession,
19
+ unstable_v2_resumeSession
20
+ } from "@anthropic-ai/claude-agent-sdk";
21
+ import fs from "fs/promises";
22
+ import path from "path";
23
+ import os from "os";
24
+ import { execSync } from "child_process";
25
+ var ANYWHERE_DIR = path.join(os.homedir(), ".anywhere");
26
+ var PROJECTS_DIR = path.join(ANYWHERE_DIR, "projects");
27
+ var WORKTREES_DIR = path.join(ANYWHERE_DIR, "worktrees");
28
+ var sessions = /* @__PURE__ */ new Map();
29
+ var activeSessions = /* @__PURE__ */ new Set();
30
+ var pendingPermissions = /* @__PURE__ */ new Map();
31
+ function getAssistantText(msg) {
32
+ if (msg.type !== "assistant") return null;
33
+ return msg.message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
34
+ }
35
+ var permissionCallbacks = /* @__PURE__ */ new Map();
36
+ function setPermissionCallback(sessionId, cb) {
37
+ if (cb) {
38
+ permissionCallbacks.set(sessionId, cb);
39
+ } else {
40
+ permissionCallbacks.delete(sessionId);
41
+ }
42
+ }
43
+ var permReqCounter = 0;
44
+ function makeCanUseTool(sessionHint, permissionMode) {
45
+ return async (toolName, input, options) => {
46
+ console.log(
47
+ `[canUseTool] tool=${toolName} reason=${options.decisionReason ?? "none"} mode=${permissionMode}`
48
+ );
49
+ const requestId = `perm_${++permReqCounter}_${Date.now()}`;
50
+ const req = {
51
+ requestId,
52
+ toolName,
53
+ input,
54
+ description: input.description,
55
+ decisionReason: options.decisionReason,
56
+ suggestions: options.suggestions
57
+ };
58
+ const cb = permissionCallbacks.get(sessionHint);
59
+ if (cb) cb(req);
60
+ return new Promise((resolve) => {
61
+ pendingPermissions.set(requestId, resolve);
62
+ const timeout = setTimeout(
63
+ () => {
64
+ if (pendingPermissions.has(requestId)) {
65
+ pendingPermissions.delete(requestId);
66
+ resolve({
67
+ behavior: "deny",
68
+ message: "Permission request timed out"
69
+ });
70
+ }
71
+ },
72
+ 5 * 60 * 1e3
73
+ );
74
+ options.signal.addEventListener("abort", () => {
75
+ clearTimeout(timeout);
76
+ if (pendingPermissions.has(requestId)) {
77
+ pendingPermissions.delete(requestId);
78
+ resolve({ behavior: "deny", message: "Request aborted" });
79
+ }
80
+ });
81
+ const origResolve = resolve;
82
+ pendingPermissions.set(requestId, (result) => {
83
+ clearTimeout(timeout);
84
+ pendingPermissions.delete(requestId);
85
+ origResolve(result);
86
+ });
87
+ });
88
+ };
89
+ }
90
+ var setupRepo = async (repo) => {
91
+ console.log(`[setupRepo] repo=${repo}`);
92
+ await fs.mkdir(PROJECTS_DIR, { recursive: true });
93
+ await fs.mkdir(WORKTREES_DIR, { recursive: true });
94
+ const repoPath = path.join(PROJECTS_DIR, repo);
95
+ try {
96
+ await fs.access(repoPath);
97
+ console.log(`[setupRepo] repo exists at ${repoPath}, fetching origin`);
98
+ execSync("git fetch origin", { cwd: repoPath, encoding: "utf-8" });
99
+ } catch {
100
+ console.log(`[setupRepo] cloning repo ${repo} to ${repoPath}`);
101
+ execSync(`gh repo clone ${repo} ${repoPath}`, { encoding: "utf-8" });
102
+ }
103
+ const branchName = `chat/chat-${Date.now()}`;
104
+ console.log(`[setupRepo] branchName=${branchName}`);
105
+ const worktreePath = path.join(WORKTREES_DIR, branchName);
106
+ console.log(`[setupRepo] creating worktree at ${worktreePath}`);
107
+ execSync(`git worktree add ${worktreePath} -b ${branchName}`, {
108
+ cwd: repoPath,
109
+ encoding: "utf-8"
110
+ });
111
+ console.log(`[setupRepo] worktree ready at ${worktreePath}`);
112
+ return worktreePath;
113
+ };
114
+ var createChat = async ({
115
+ model,
116
+ permission,
117
+ repo,
118
+ prompt
119
+ }) => {
120
+ console.log(`[createChat] model=${model} permission=${permission} repo=${repo} prompt="${prompt?.slice(0, 50)}"`);
121
+ const repoPath = repo ? await setupRepo(repo) : void 0;
122
+ console.log(`[createChat] cwd=${repoPath || process.cwd()}`);
123
+ const tempId = `temp_${Date.now()}`;
124
+ const mode = permission || "acceptEdits";
125
+ console.log(`[createChat] tempId=${tempId} mode=${mode}`);
126
+ const session = unstable_v2_createSession({
127
+ model: model || "claude-opus-4-6",
128
+ permissionMode: mode,
129
+ canUseTool: makeCanUseTool(tempId, mode),
130
+ ...repoPath ? { cwd: repoPath } : {}
131
+ });
132
+ return { session, tempId };
133
+ };
134
+ var sendMessage = async ({
135
+ prompt,
136
+ session
137
+ }) => {
138
+ await session.send(prompt);
139
+ };
140
+ var getSession = async ({
141
+ sessionId,
142
+ model,
143
+ permission
144
+ }) => {
145
+ let session = sessions.get(sessionId);
146
+ if (session && (model || permission)) {
147
+ console.log(`[getSession] ${sessionId} re-creating (model=${model} permission=${permission})`);
148
+ session.close();
149
+ sessions.delete(sessionId);
150
+ session = void 0;
151
+ }
152
+ if (!session) {
153
+ console.log(`[getSession] ${sessionId} resuming from JSONL`);
154
+ const mode = permission || "acceptEdits";
155
+ session = unstable_v2_resumeSession(sessionId, {
156
+ model: model || "claude-opus-4-6",
157
+ permissionMode: mode,
158
+ canUseTool: makeCanUseTool(sessionId, mode)
159
+ });
160
+ sessions.set(sessionId, session);
161
+ } else {
162
+ console.log(`[getSession] ${sessionId} found in memory`);
163
+ }
164
+ return session;
165
+ };
166
+
167
+ // src/routes/chats/index.ts
168
+ var repoNameCache = /* @__PURE__ */ new Map();
169
+ function deriveRepoName(cwd) {
170
+ if (!cwd) return "Unknown";
171
+ const cached = repoNameCache.get(cwd);
172
+ if (cached) return cached;
173
+ let name = "Unknown";
174
+ try {
175
+ const url = execSync2("git remote get-url origin 2>/dev/null", {
176
+ cwd,
177
+ encoding: "utf-8",
178
+ timeout: 3e3
179
+ }).trim();
180
+ const match = url.match(/[:/]([^/]+\/[^/.]+?)(?:\.git)?$/);
181
+ if (match?.[1]) name = match[1];
182
+ } catch {
183
+ try {
184
+ const root = execSync2("git rev-parse --show-toplevel 2>/dev/null", {
185
+ cwd,
186
+ encoding: "utf-8",
187
+ timeout: 3e3
188
+ }).trim();
189
+ name = root.split("/").filter(Boolean).pop() || "Unknown";
190
+ } catch {
191
+ const segments = cwd.split("/").filter(Boolean);
192
+ name = segments[segments.length - 1] || "Unknown";
193
+ }
194
+ }
195
+ repoNameCache.set(cwd, name);
196
+ return name;
197
+ }
198
+ async function findSessionFile(sessionId) {
199
+ const projectsBase = path2.join(os2.homedir(), ".claude", "projects");
200
+ try {
201
+ const dirs = await readdir(projectsBase);
202
+ for (const dir of dirs) {
203
+ const filePath = path2.join(projectsBase, dir, `${sessionId}.jsonl`);
204
+ try {
205
+ await stat(filePath);
206
+ return filePath;
207
+ } catch {
208
+ }
209
+ }
210
+ } catch {
211
+ }
212
+ return null;
213
+ }
214
+ var chats = new Hono();
215
+ chats.post("/new", async (c) => {
216
+ const { prompt, model, permission, repo } = await c.req.json();
217
+ console.log(`[POST /new] prompt="${prompt?.slice(0, 50)}" model=${model} repo=${repo}`);
218
+ if (!prompt) return c.json({ error: "Prompt is required" }, 400);
219
+ try {
220
+ const { session, tempId } = await createChat({ model, permission, repo, prompt });
221
+ await sendMessage({ prompt, session });
222
+ return streamSSE(c, async (stream) => {
223
+ let finalSessionId = "";
224
+ console.log(`[POST /new] SSE stream started, tempId=${tempId}`);
225
+ const sendPerm = async (req) => {
226
+ try {
227
+ await stream.writeSSE({
228
+ data: JSON.stringify({
229
+ type: "permission_request",
230
+ ...req,
231
+ sessionId: finalSessionId || "pending"
232
+ })
233
+ });
234
+ } catch (e) {
235
+ console.error("Failed to send permission SSE:", e);
236
+ }
237
+ };
238
+ setPermissionCallback(tempId, sendPerm);
239
+ try {
240
+ for await (const msg of session.stream()) {
241
+ if (msg.session_id && !finalSessionId) {
242
+ finalSessionId = msg.session_id;
243
+ console.log(`[POST /new] sessionId received: ${finalSessionId}`);
244
+ activeSessions.add(finalSessionId);
245
+ setPermissionCallback(tempId, null);
246
+ setPermissionCallback(finalSessionId, sendPerm);
247
+ }
248
+ const text = getAssistantText(msg);
249
+ if (text)
250
+ await stream.writeSSE({
251
+ data: JSON.stringify({
252
+ response: text.trim(),
253
+ sessionId: msg.session_id
254
+ })
255
+ });
256
+ }
257
+ if (finalSessionId) {
258
+ sessions.set(finalSessionId, session);
259
+ console.log(`[POST /new] session stored: ${finalSessionId}`);
260
+ }
261
+ } catch (err) {
262
+ console.error(err);
263
+ await stream.writeSSE({
264
+ data: JSON.stringify({ error: "Stream error" })
265
+ });
266
+ } finally {
267
+ setPermissionCallback(tempId, null);
268
+ if (finalSessionId) {
269
+ setPermissionCallback(finalSessionId, null);
270
+ activeSessions.delete(finalSessionId);
271
+ }
272
+ }
273
+ });
274
+ } catch (error) {
275
+ console.error(error);
276
+ return c.json({ error: "Failed to create new chat" }, 400);
277
+ }
278
+ });
279
+ chats.post("/:id/message", async (c) => {
280
+ const { prompt, model, permission } = await c.req.json();
281
+ const sessionId = c.req.param("id");
282
+ console.log(`[POST /:id/message] sessionId=${sessionId} prompt="${prompt?.slice(0, 50)}"`);
283
+ if (!prompt || !sessionId || /[\/\\]/.test(sessionId))
284
+ return c.json({ error: "Invalid request" }, 400);
285
+ try {
286
+ const session = await getSession({ sessionId, model, permission });
287
+ if (!session) return c.json({ error: "Session is required" }, 400);
288
+ await sendMessage({ prompt, session });
289
+ return streamSSE(c, async (stream) => {
290
+ activeSessions.add(sessionId);
291
+ setPermissionCallback(sessionId, async (req) => {
292
+ try {
293
+ await stream.writeSSE({
294
+ data: JSON.stringify({
295
+ type: "permission_request",
296
+ ...req,
297
+ sessionId
298
+ })
299
+ });
300
+ } catch (e) {
301
+ console.error("Failed to send permission SSE:", e);
302
+ }
303
+ });
304
+ try {
305
+ for await (const msg of session.stream()) {
306
+ const text = getAssistantText(msg);
307
+ if (text)
308
+ await stream.writeSSE({
309
+ data: JSON.stringify({
310
+ response: text?.trim() || null,
311
+ sessionId: msg.session_id
312
+ })
313
+ });
314
+ }
315
+ } catch (err) {
316
+ console.error(err);
317
+ await stream.writeSSE({
318
+ data: JSON.stringify({ error: "Stream error" })
319
+ });
320
+ } finally {
321
+ setPermissionCallback(sessionId, null);
322
+ activeSessions.delete(sessionId);
323
+ }
324
+ });
325
+ } catch (error) {
326
+ console.error(error);
327
+ return c.json({ error: "Failed to send message" }, 400);
328
+ }
329
+ });
330
+ chats.post("/:id/permission", async (c) => {
331
+ const { requestId, behavior, updatedPermissions } = await c.req.json();
332
+ if (!requestId || !behavior)
333
+ return c.json({ error: "requestId and behavior are required" }, 400);
334
+ const resolve = pendingPermissions.get(requestId);
335
+ if (!resolve)
336
+ return c.json({ error: "No pending permission request found" }, 404);
337
+ if (behavior === "allow") {
338
+ resolve({
339
+ behavior: "allow",
340
+ ...updatedPermissions ? { updatedPermissions } : {}
341
+ });
342
+ } else {
343
+ resolve({ behavior: "deny", message: "User denied permission" });
344
+ }
345
+ return c.json({ ok: true });
346
+ });
347
+ chats.get("/", async (c) => {
348
+ try {
349
+ const projectsBase = path2.join(os2.homedir(), ".claude", "projects");
350
+ const dirs = await readdir(projectsBase);
351
+ const allChats = [];
352
+ for (const dir of dirs) {
353
+ const dirPath = path2.join(projectsBase, dir);
354
+ try {
355
+ const s = await stat(dirPath);
356
+ if (!s.isDirectory()) continue;
357
+ } catch {
358
+ continue;
359
+ }
360
+ const files = (await readdir(dirPath)).filter(
361
+ (f) => f.endsWith(".jsonl")
362
+ );
363
+ for (const file of files) {
364
+ const filePath = path2.join(dirPath, file);
365
+ const content = await readFile(filePath, "utf-8");
366
+ const lines = content.split("\n").filter(Boolean);
367
+ const sessionId = file.replace(".jsonl", "");
368
+ let preview = "";
369
+ let timestamp = "";
370
+ let cwd = "";
371
+ let gitBranch = "";
372
+ for (const line of lines) {
373
+ try {
374
+ const obj = JSON.parse(line);
375
+ if (obj.type === "user") {
376
+ if (!cwd && obj.cwd) cwd = obj.cwd;
377
+ if (!gitBranch && obj.gitBranch) gitBranch = obj.gitBranch;
378
+ if (!obj.isMeta) {
379
+ const msgContent = obj.message.content;
380
+ if (typeof msgContent === "string") {
381
+ preview = msgContent.slice(0, 100);
382
+ } else {
383
+ preview = msgContent.filter((b) => b.type === "text").map((b) => b.text).join("").slice(0, 100);
384
+ }
385
+ timestamp = obj.timestamp;
386
+ break;
387
+ }
388
+ }
389
+ } catch {
390
+ continue;
391
+ }
392
+ }
393
+ if (!timestamp) continue;
394
+ allChats.push({
395
+ sessionId,
396
+ text: preview,
397
+ timestamp,
398
+ isActive: activeSessions.has(sessionId),
399
+ repo: deriveRepoName(cwd),
400
+ cwd,
401
+ gitBranch
402
+ });
403
+ }
404
+ }
405
+ return c.json({ result: allChats });
406
+ } catch (error) {
407
+ console.error("[GET /chats] error:", error);
408
+ return c.json({ result: [] });
409
+ }
410
+ });
411
+ chats.get("/:id", async (c) => {
412
+ const sessionId = c.req.param("id");
413
+ if (!sessionId || /[\/\\]/.test(sessionId))
414
+ return c.json({ error: "Invalid session id" }, 400);
415
+ try {
416
+ const file = await findSessionFile(sessionId);
417
+ if (!file) return c.json({ error: "Chat not found" }, 404);
418
+ const content = await readFile(file, "utf-8");
419
+ const lines = content.split("\n").filter(Boolean);
420
+ const parsed = lines.map((line) => JSON.parse(line));
421
+ let model;
422
+ let permissionMode;
423
+ for (const obj of [...parsed].reverse()) {
424
+ if (!model && obj.type === "assistant") model = obj.message?.model;
425
+ if (!permissionMode && obj.type === "user" && !obj.isMeta)
426
+ permissionMode = obj.permissionMode;
427
+ if (model && permissionMode) break;
428
+ }
429
+ const messages = parsed.filter(
430
+ (obj) => obj.type === "user" && !obj.isMeta || obj.type === "assistant"
431
+ ).flatMap((obj) => {
432
+ const content2 = obj.message.content;
433
+ if (typeof content2 === "string") {
434
+ return [
435
+ {
436
+ role: obj.message.role,
437
+ type: "text",
438
+ text: content2.trim(),
439
+ timestamp: obj.timestamp
440
+ }
441
+ ];
442
+ }
443
+ return content2.filter(
444
+ (b) => ["text", "tool_use", "tool_result"].includes(b.type)
445
+ ).map((b) => {
446
+ if (b.type === "text") {
447
+ return {
448
+ role: obj.message.role,
449
+ type: "text",
450
+ text: b.text.trim(),
451
+ timestamp: obj.timestamp
452
+ };
453
+ }
454
+ if (b.type === "tool_use") {
455
+ return {
456
+ role: obj.message.role,
457
+ type: "tool_use",
458
+ name: b.name,
459
+ input: b.input,
460
+ timestamp: obj.timestamp
461
+ };
462
+ }
463
+ if (b.type === "tool_result") {
464
+ const resultText = Array.isArray(b.content) ? b.content.filter((r) => r.type === "text").map((r) => r.text).join("") : b.content ?? "";
465
+ return {
466
+ role: obj.message.role,
467
+ type: "tool_result",
468
+ text: resultText.trim(),
469
+ timestamp: obj.timestamp
470
+ };
471
+ }
472
+ }).filter(Boolean);
473
+ });
474
+ return c.json({ messages, model, permissionMode });
475
+ } catch {
476
+ return c.json({ error: "Chat not found" }, 404);
477
+ }
478
+ });
479
+
480
+ // src/routes/git/index.ts
481
+ import { Hono as Hono2 } from "hono";
482
+ import { exec } from "child_process";
483
+ import { readFile as readFile2 } from "fs/promises";
484
+ import path3 from "path";
485
+ import { promisify } from "util";
486
+ var execAsync = promisify(exec);
487
+ var _repoRoot = null;
488
+ async function getRepoRoot() {
489
+ if (_repoRoot === null) {
490
+ try {
491
+ const { stdout } = await execAsync("git rev-parse --show-toplevel");
492
+ _repoRoot = stdout.trim();
493
+ } catch {
494
+ _repoRoot = false;
495
+ }
496
+ }
497
+ return _repoRoot || null;
498
+ }
499
+ var git = new Hono2();
500
+ git.get("/status", async (c) => {
501
+ try {
502
+ const root = await getRepoRoot();
503
+ if (!root) return c.json({ files: [], error: "Not a git repository" });
504
+ const { stdout: statusOut } = await execAsync("git status --porcelain", {
505
+ cwd: root
506
+ });
507
+ const [{ stdout: unstagedNum }, { stdout: stagedNum }] = await Promise.all([
508
+ execAsync("git diff --numstat", { cwd: root }).catch(() => ({
509
+ stdout: ""
510
+ })),
511
+ execAsync("git diff --cached --numstat", { cwd: root }).catch(() => ({
512
+ stdout: ""
513
+ }))
514
+ ]);
515
+ const parseNumstat = (raw) => {
516
+ const map = /* @__PURE__ */ new Map();
517
+ for (const line of raw.split("\n").filter(Boolean)) {
518
+ const [add, del, file] = line.split(" ");
519
+ if (file) {
520
+ map.set(file, {
521
+ additions: add === "-" ? 0 : parseInt(add, 10),
522
+ deletions: del === "-" ? 0 : parseInt(del, 10)
523
+ });
524
+ }
525
+ }
526
+ return map;
527
+ };
528
+ const unstagedStats = parseNumstat(unstagedNum);
529
+ const stagedStats = parseNumstat(stagedNum);
530
+ const files = [];
531
+ for (const line of statusOut.split("\n").filter(Boolean)) {
532
+ const xy = line.slice(0, 2);
533
+ const filePath = line.slice(3).trim();
534
+ let status = "M";
535
+ let staged = false;
536
+ if (xy === "??") {
537
+ status = "?";
538
+ } else if (xy === "!!") {
539
+ continue;
540
+ } else {
541
+ const indexStatus = xy[0];
542
+ const worktreeStatus = xy[1];
543
+ if (indexStatus !== " " && indexStatus !== "?") {
544
+ staged = true;
545
+ if (indexStatus === "A") status = "A";
546
+ else if (indexStatus === "D") status = "D";
547
+ else if (indexStatus === "R") status = "R";
548
+ else status = "M";
549
+ } else {
550
+ if (worktreeStatus === "D") status = "D";
551
+ else status = "M";
552
+ }
553
+ }
554
+ const stats = staged ? stagedStats.get(filePath) : unstagedStats.get(filePath);
555
+ let additions = stats?.additions ?? 0;
556
+ let deletions = stats?.deletions ?? 0;
557
+ if (status === "?") {
558
+ try {
559
+ const content = await readFile2(path3.join(root, filePath), "utf-8");
560
+ additions = content.split("\n").length;
561
+ } catch {
562
+ additions = 0;
563
+ }
564
+ }
565
+ files.push({ path: filePath, status, staged, additions, deletions });
566
+ }
567
+ return c.json({ files });
568
+ } catch (error) {
569
+ console.error("git status error:", error);
570
+ return c.json({ files: [] });
571
+ }
572
+ });
573
+ git.get("/diff", async (c) => {
574
+ const filePath = c.req.query("file");
575
+ if (!filePath) return c.json({ error: "file parameter required" }, 400);
576
+ if (filePath.includes("..")) return c.json({ error: "Invalid path" }, 400);
577
+ const root = await getRepoRoot();
578
+ if (!root) return c.json({ error: "Not a git repository" }, 400);
579
+ const safePath = filePath.replace(/'/g, "'\\''");
580
+ try {
581
+ let oldContent = "";
582
+ let newContent = "";
583
+ try {
584
+ const { stdout } = await execAsync(`git show 'HEAD:${safePath}'`, {
585
+ cwd: root,
586
+ maxBuffer: 10 * 1024 * 1024
587
+ });
588
+ oldContent = stdout;
589
+ } catch {
590
+ oldContent = "";
591
+ }
592
+ try {
593
+ newContent = await readFile2(path3.join(root, filePath), "utf-8");
594
+ } catch (e) {
595
+ console.error("readFile error for", filePath, e);
596
+ newContent = "";
597
+ }
598
+ return c.json({ path: filePath, oldContent, newContent });
599
+ } catch (error) {
600
+ console.error("git diff error:", error);
601
+ return c.json({ error: "Failed to get diff" }, 500);
602
+ }
603
+ });
604
+
605
+ // src/routes/gh/index.ts
606
+ import { Hono as Hono3 } from "hono";
607
+ import { execSync as execSync3 } from "child_process";
608
+ var gh = new Hono3();
609
+ gh.get("/repos", async (c) => {
610
+ const fields = "--json name,nameWithOwner,description,url,isPrivate,updatedAt --limit 100";
611
+ try {
612
+ const personal = JSON.parse(
613
+ execSync3(`gh repo list ${fields}`, { encoding: "utf-8" })
614
+ );
615
+ let orgRepos = [];
616
+ try {
617
+ const orgs = execSync3("gh api user/orgs --jq '.[].login'", { encoding: "utf-8" }).trim().split("\n").filter(Boolean);
618
+ for (const org of orgs) {
619
+ const repos = JSON.parse(
620
+ execSync3(`gh repo list ${org} ${fields}`, { encoding: "utf-8" })
621
+ );
622
+ orgRepos.push(...repos);
623
+ }
624
+ } catch {
625
+ }
626
+ const username = execSync3("gh api user --jq '.login'", { encoding: "utf-8" }).trim();
627
+ const all = [...personal, ...orgRepos].sort(
628
+ (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
629
+ );
630
+ return c.json({ username, repos: all });
631
+ } catch (error) {
632
+ return c.json([]);
633
+ }
634
+ });
635
+
636
+ // src/server.ts
637
+ import { bearerAuth } from "hono/bearer-auth";
638
+ var app = new Hono4();
639
+ var token = process.env.AUTH_TOKEN;
640
+ if (!token) {
641
+ console.error("AUTH_TOKEN is not set. Run `anywhere-ai init` first.");
642
+ process.exit(1);
643
+ }
644
+ app.use("*", cors());
645
+ app.get("/health", async (c) => {
646
+ return c.json({ message: "server is healthy" }, 200);
647
+ });
648
+ app.use("/v1/*", bearerAuth({ token }));
649
+ app.route("/v1/chats", chats);
650
+ app.route("/v1/git", git);
651
+ app.route("/v1/gh", gh);
652
+ serve(
653
+ {
654
+ fetch: app.fetch,
655
+ hostname: "0.0.0.0",
656
+ port: parseInt(process.env.PORT || "3847")
657
+ },
658
+ (info) => {
659
+ console.log("Server is running");
660
+ }
661
+ );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anywhere-ai",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "type": "module",
5
5
  "description": "Code on any repo from your phone",
6
6
  "bin": {