regionclaw 0.0.3 → 0.0.4

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 (117) hide show
  1. package/README.md +15 -0
  2. package/apps/ui/regionclaw-build/BUILD_ID +1 -1
  3. package/apps/ui/regionclaw-build/app-path-routes-manifest.json +2 -0
  4. package/apps/ui/regionclaw-build/build-manifest.json +2 -2
  5. package/apps/ui/regionclaw-build/prerender-manifest.json +0 -48
  6. package/apps/ui/regionclaw-build/routes-manifest.json +12 -0
  7. package/apps/ui/regionclaw-build/server/app/(app)/dashboard/page.js +3 -3
  8. package/apps/ui/regionclaw-build/server/app/(app)/dashboard/page_client-reference-manifest.js +1 -1
  9. package/apps/ui/regionclaw-build/server/app/_global-error/page.js +3 -3
  10. package/apps/ui/regionclaw-build/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/apps/ui/regionclaw-build/server/app/_global-error.html +1 -1
  12. package/apps/ui/regionclaw-build/server/app/_global-error.rsc +1 -1
  13. package/apps/ui/regionclaw-build/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/apps/ui/regionclaw-build/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/apps/ui/regionclaw-build/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  16. package/apps/ui/regionclaw-build/server/app/_global-error.segments/_head.segment.rsc +1 -1
  17. package/apps/ui/regionclaw-build/server/app/_global-error.segments/_index.segment.rsc +1 -1
  18. package/apps/ui/regionclaw-build/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/apps/ui/regionclaw-build/server/app/_not-found/page.js +2 -2
  20. package/apps/ui/regionclaw-build/server/app/_not-found/page_client-reference-manifest.js +1 -1
  21. package/apps/ui/regionclaw-build/server/app/_not-found.html +1 -1
  22. package/apps/ui/regionclaw-build/server/app/_not-found.rsc +4 -4
  23. package/apps/ui/regionclaw-build/server/app/_not-found.segments/_full.segment.rsc +4 -4
  24. package/apps/ui/regionclaw-build/server/app/_not-found.segments/_head.segment.rsc +1 -1
  25. package/apps/ui/regionclaw-build/server/app/_not-found.segments/_index.segment.rsc +4 -4
  26. package/apps/ui/regionclaw-build/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  27. package/apps/ui/regionclaw-build/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  28. package/apps/ui/regionclaw-build/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  29. package/apps/ui/regionclaw-build/server/app/api/auth/[...nextauth]/route.js +1 -1
  30. package/apps/ui/regionclaw-build/server/app/api/auth/first-run/route.js +1 -1
  31. package/apps/ui/regionclaw-build/server/app/api/trpc/[trpc]/route.js +2 -2
  32. package/apps/ui/regionclaw-build/server/app/api/update/run/route.js +1 -0
  33. package/apps/ui/regionclaw-build/server/app/api/update/run/route.js.nft.json +1 -0
  34. package/apps/ui/regionclaw-build/server/app/api/update/run/route_client-reference-manifest.js +1 -0
  35. package/apps/ui/regionclaw-build/server/app/api/update/status/route.js +1 -0
  36. package/apps/ui/regionclaw-build/server/app/api/update/status/route.js.nft.json +1 -0
  37. package/apps/ui/regionclaw-build/server/app/api/update/status/route_client-reference-manifest.js +1 -0
  38. package/apps/ui/regionclaw-build/server/app/favicon.ico/route.js +1 -1
  39. package/apps/ui/regionclaw-build/server/app/login/page.js +2 -2
  40. package/apps/ui/regionclaw-build/server/app/login/page_client-reference-manifest.js +1 -1
  41. package/apps/ui/regionclaw-build/server/app/page.js +2 -2
  42. package/apps/ui/regionclaw-build/server/app/page_client-reference-manifest.js +1 -1
  43. package/apps/ui/regionclaw-build/server/app/setup/page.js +2 -2
  44. package/apps/ui/regionclaw-build/server/app/setup/page_client-reference-manifest.js +1 -1
  45. package/apps/ui/regionclaw-build/server/app-paths-manifest.json +2 -0
  46. package/apps/ui/regionclaw-build/server/chunks/263.js +2 -2
  47. package/apps/ui/regionclaw-build/server/chunks/324.js +1 -1
  48. package/apps/ui/regionclaw-build/server/chunks/902.js +3 -3
  49. package/apps/ui/regionclaw-build/server/middleware-build-manifest.js +1 -1
  50. package/apps/ui/regionclaw-build/server/pages/404.html +1 -1
  51. package/apps/ui/regionclaw-build/server/pages/500.html +1 -1
  52. package/apps/ui/regionclaw-build/static/ZSgRLh43NEMa5560KIFa0/_buildManifest.js +1 -0
  53. package/apps/ui/regionclaw-build/static/chunks/557-16373a4e3e7fff4a.js +1 -0
  54. package/apps/ui/regionclaw-build/static/chunks/609-413da697ed2385f4.js +1 -0
  55. package/apps/ui/regionclaw-build/static/chunks/{777-2c6a164314456938.js → 835-8d09234672e1cdbd.js} +2 -2
  56. package/apps/ui/regionclaw-build/static/chunks/app/(app)/dashboard/page-fdd3dce681b59047.js +1 -0
  57. package/apps/ui/regionclaw-build/static/chunks/app/(app)/layout-16f7ce76f7f41b68.js +1 -0
  58. package/apps/ui/regionclaw-build/static/chunks/app/_global-error/page-fdd3dce681b59047.js +1 -0
  59. package/apps/ui/regionclaw-build/static/chunks/app/api/auth/[...nextauth]/route-fdd3dce681b59047.js +1 -0
  60. package/apps/ui/regionclaw-build/static/chunks/app/api/auth/first-run/route-fdd3dce681b59047.js +1 -0
  61. package/apps/ui/regionclaw-build/static/chunks/app/api/trpc/[trpc]/route-fdd3dce681b59047.js +1 -0
  62. package/apps/ui/regionclaw-build/static/chunks/app/api/update/run/route-fdd3dce681b59047.js +1 -0
  63. package/apps/ui/regionclaw-build/static/chunks/app/api/update/status/route-fdd3dce681b59047.js +1 -0
  64. package/apps/ui/regionclaw-build/static/chunks/app/{layout-87ffd83df9489fc3.js → layout-812a5dd3c2548265.js} +1 -1
  65. package/apps/ui/regionclaw-build/static/chunks/app/page-fdd3dce681b59047.js +1 -0
  66. package/apps/ui/regionclaw-build/static/chunks/next/dist/client/components/builtin/app-error-fdd3dce681b59047.js +1 -0
  67. package/apps/ui/regionclaw-build/static/chunks/next/dist/client/components/builtin/forbidden-fdd3dce681b59047.js +1 -0
  68. package/apps/ui/regionclaw-build/static/chunks/next/dist/client/components/builtin/not-found-fdd3dce681b59047.js +1 -0
  69. package/apps/ui/regionclaw-build/static/chunks/next/dist/client/components/builtin/unauthorized-fdd3dce681b59047.js +1 -0
  70. package/apps/ui/regionclaw-build/types/app/api/update/run/route.ts +351 -0
  71. package/apps/ui/regionclaw-build/types/app/api/update/status/route.ts +351 -0
  72. package/apps/ui/regionclaw-build/types/link.d.ts +2 -0
  73. package/apps/ui/regionclaw-build/types/routes.d.ts +3 -1
  74. package/apps/ui/regionclaw-build/types/validator.ts +18 -0
  75. package/apps/ui/src/app/(app)/layout.tsx +7 -1
  76. package/apps/ui/src/app/api/update/run/route.ts +36 -0
  77. package/apps/ui/src/app/api/update/status/route.ts +24 -0
  78. package/apps/ui/src/components/app-layout-header.tsx +8 -2
  79. package/apps/ui/src/components/app-sidebar.tsx +3 -0
  80. package/apps/ui/src/components/regionclaw-update-banner.tsx +229 -0
  81. package/apps/ui/src/server/regionclaw-meta.ts +3 -0
  82. package/apps/ui/src/server/update-cli.ts +88 -0
  83. package/bin/regionclaw.js +357 -3
  84. package/package.json +1 -1
  85. package/scripts/restart-running-regionclaw.mjs +54 -0
  86. package/apps/ui/regionclaw-build/server/app/index.html +0 -1
  87. package/apps/ui/regionclaw-build/server/app/index.meta +0 -16
  88. package/apps/ui/regionclaw-build/server/app/index.rsc +0 -21
  89. package/apps/ui/regionclaw-build/server/app/index.segments/__PAGE__.segment.rsc +0 -6
  90. package/apps/ui/regionclaw-build/server/app/index.segments/_full.segment.rsc +0 -21
  91. package/apps/ui/regionclaw-build/server/app/index.segments/_head.segment.rsc +0 -6
  92. package/apps/ui/regionclaw-build/server/app/index.segments/_index.segment.rsc +0 -9
  93. package/apps/ui/regionclaw-build/server/app/index.segments/_tree.segment.rsc +0 -5
  94. package/apps/ui/regionclaw-build/server/app/login.html +0 -1
  95. package/apps/ui/regionclaw-build/server/app/login.meta +0 -17
  96. package/apps/ui/regionclaw-build/server/app/login.rsc +0 -23
  97. package/apps/ui/regionclaw-build/server/app/login.segments/_full.segment.rsc +0 -23
  98. package/apps/ui/regionclaw-build/server/app/login.segments/_head.segment.rsc +0 -6
  99. package/apps/ui/regionclaw-build/server/app/login.segments/_index.segment.rsc +0 -9
  100. package/apps/ui/regionclaw-build/server/app/login.segments/_tree.segment.rsc +0 -5
  101. package/apps/ui/regionclaw-build/server/app/login.segments/login/__PAGE__.segment.rsc +0 -6
  102. package/apps/ui/regionclaw-build/server/app/login.segments/login.segment.rsc +0 -5
  103. package/apps/ui/regionclaw-build/static/chunks/314-50a286a8a2defe3e.js +0 -1
  104. package/apps/ui/regionclaw-build/static/chunks/774-b72ba79217cd49b6.js +0 -1
  105. package/apps/ui/regionclaw-build/static/chunks/app/(app)/dashboard/page-ee954e3484be7e8f.js +0 -1
  106. package/apps/ui/regionclaw-build/static/chunks/app/(app)/layout-8c2cbeb73ce3441e.js +0 -1
  107. package/apps/ui/regionclaw-build/static/chunks/app/_global-error/page-ee954e3484be7e8f.js +0 -1
  108. package/apps/ui/regionclaw-build/static/chunks/app/api/auth/[...nextauth]/route-ee954e3484be7e8f.js +0 -1
  109. package/apps/ui/regionclaw-build/static/chunks/app/api/auth/first-run/route-ee954e3484be7e8f.js +0 -1
  110. package/apps/ui/regionclaw-build/static/chunks/app/api/trpc/[trpc]/route-ee954e3484be7e8f.js +0 -1
  111. package/apps/ui/regionclaw-build/static/chunks/app/page-ee954e3484be7e8f.js +0 -1
  112. package/apps/ui/regionclaw-build/static/chunks/next/dist/client/components/builtin/app-error-ee954e3484be7e8f.js +0 -1
  113. package/apps/ui/regionclaw-build/static/chunks/next/dist/client/components/builtin/forbidden-ee954e3484be7e8f.js +0 -1
  114. package/apps/ui/regionclaw-build/static/chunks/next/dist/client/components/builtin/not-found-ee954e3484be7e8f.js +0 -1
  115. package/apps/ui/regionclaw-build/static/chunks/next/dist/client/components/builtin/unauthorized-ee954e3484be7e8f.js +0 -1
  116. package/apps/ui/regionclaw-build/static/tx6PZf8zETvK0EvVfqK73/_buildManifest.js +0 -1
  117. /package/apps/ui/regionclaw-build/static/{tx6PZf8zETvK0EvVfqK73 → ZSgRLh43NEMa5560KIFa0}/_ssgManifest.js +0 -0
@@ -2,7 +2,7 @@
2
2
  // Do not edit this file manually
3
3
 
4
4
  type AppRoutes = "/" | "/dashboard" | "/login" | "/setup"
5
- type AppRouteHandlerRoutes = "/api/auth/[...nextauth]" | "/api/auth/first-run" | "/api/trpc/[trpc]"
5
+ type AppRouteHandlerRoutes = "/api/auth/[...nextauth]" | "/api/auth/first-run" | "/api/trpc/[trpc]" | "/api/update/run" | "/api/update/status"
6
6
  type PageRoutes = never
7
7
  type LayoutRoutes = "/"
8
8
  type RedirectRoutes = never
@@ -15,6 +15,8 @@ interface ParamMap {
15
15
  "/api/auth/[...nextauth]": { "nextauth": string[]; }
16
16
  "/api/auth/first-run": {}
17
17
  "/api/trpc/[trpc]": { "trpc": string; }
18
+ "/api/update/run": {}
19
+ "/api/update/status": {}
18
20
  "/dashboard": {}
19
21
  "/login": {}
20
22
  "/setup": {}
@@ -110,6 +110,24 @@ type RouteHandlerConfig<Route extends AppRouteHandlerRoutes = AppRouteHandlerRou
110
110
  type __Unused = __Check
111
111
  }
112
112
 
113
+ // Validate ../../src/app/api/update/run/route.ts
114
+ {
115
+ type __IsExpected<Specific extends RouteHandlerConfig<"/api/update/run">> = Specific
116
+ const handler = {} as typeof import("../../src/app/api/update/run/route.js")
117
+ type __Check = __IsExpected<typeof handler>
118
+ // @ts-ignore
119
+ type __Unused = __Check
120
+ }
121
+
122
+ // Validate ../../src/app/api/update/status/route.ts
123
+ {
124
+ type __IsExpected<Specific extends RouteHandlerConfig<"/api/update/status">> = Specific
125
+ const handler = {} as typeof import("../../src/app/api/update/status/route.js")
126
+ type __Check = __IsExpected<typeof handler>
127
+ // @ts-ignore
128
+ type __Unused = __Check
129
+ }
130
+
113
131
 
114
132
 
115
133
 
@@ -3,7 +3,9 @@ import { redirect } from "next/navigation";
3
3
  import { getServerAuthSession } from "@/auth";
4
4
  import { AppLayoutHeader } from "@/components/app-layout-header";
5
5
  import { AppSidebar } from "@/components/app-sidebar";
6
+ import { RegionClawUpdateBanner } from "@/components/regionclaw-update-banner";
6
7
  import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
8
+ import { getRegionClawVersion } from "@/server/regionclaw-meta";
7
9
 
8
10
  export default async function AppLayout({
9
11
  children,
@@ -13,6 +15,8 @@ export default async function AppLayout({
13
15
  redirect("/login");
14
16
  }
15
17
 
18
+ const version = getRegionClawVersion();
19
+
16
20
  return (
17
21
  <SidebarProvider defaultOpen>
18
22
  <AppSidebar
@@ -21,9 +25,11 @@ export default async function AppLayout({
21
25
  email: session.user.email,
22
26
  username: session.user.username,
23
27
  }}
28
+ version={version}
24
29
  />
25
30
  <SidebarInset className="overflow-hidden border md:m-2 md:ml-0">
26
- <AppLayoutHeader title="Dashboard" />
31
+ <AppLayoutHeader title="Dashboard" version={version} />
32
+ <RegionClawUpdateBanner />
27
33
  <div className="flex flex-1 flex-col bg-background">{children}</div>
28
34
  </SidebarInset>
29
35
  </SidebarProvider>
@@ -0,0 +1,36 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ import { getServerAuthSession } from "@/auth";
4
+ import {
5
+ canScheduleRegionClawRestart,
6
+ runRegionClawUpdate,
7
+ scheduleRegionClawRestart,
8
+ } from "@/server/update-cli";
9
+
10
+ export async function POST() {
11
+ const session = await getServerAuthSession();
12
+ if (!session?.user?.id) {
13
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
14
+ }
15
+
16
+ try {
17
+ const result = await runRegionClawUpdate();
18
+ if (result.status === "updated" && result.requiresRestart && canScheduleRegionClawRestart()) {
19
+ result.restartScheduled = true;
20
+ result.message = "RegionClaw updated. Restarting now.";
21
+ setTimeout(() => {
22
+ scheduleRegionClawRestart();
23
+ }, 750).unref();
24
+ }
25
+ return NextResponse.json(result);
26
+ } catch (error) {
27
+ return NextResponse.json(
28
+ {
29
+ ok: false,
30
+ status: "error",
31
+ message: error instanceof Error ? error.message : String(error),
32
+ },
33
+ { status: 500 },
34
+ );
35
+ }
36
+ }
@@ -0,0 +1,24 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ import { getServerAuthSession } from "@/auth";
4
+ import { getRegionClawUpdateStatus } from "@/server/update-cli";
5
+
6
+ export async function GET() {
7
+ const session = await getServerAuthSession();
8
+ if (!session?.user?.id) {
9
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
10
+ }
11
+
12
+ try {
13
+ const status = await getRegionClawUpdateStatus();
14
+ return NextResponse.json(status);
15
+ } catch (error) {
16
+ return NextResponse.json(
17
+ {
18
+ ok: false,
19
+ error: error instanceof Error ? error.message : String(error),
20
+ },
21
+ { status: 500 },
22
+ );
23
+ }
24
+ }
@@ -1,12 +1,18 @@
1
1
  import { SidebarTrigger } from "@/components/ui/sidebar";
2
2
 
3
- export function AppLayoutHeader({ title }: { title: string }) {
3
+ export function AppLayoutHeader({
4
+ title,
5
+ version,
6
+ }: {
7
+ title: string;
8
+ version: string;
9
+ }) {
4
10
  return (
5
11
  <header className="flex h-16 shrink-0 items-center gap-3 border-b px-4">
6
12
  <SidebarTrigger className="-ml-1" />
7
13
  <div className="min-w-0">
8
14
  <p className="truncate text-sm font-medium text-muted-foreground">
9
- RegionClaw
15
+ RegionClaw v{version}
10
16
  </p>
11
17
  <h1 className="truncate text-base font-semibold text-foreground">{title}</h1>
12
18
  </div>
@@ -11,9 +11,11 @@ import {
11
11
 
12
12
  export function AppSidebar({
13
13
  user,
14
+ version,
14
15
  ...props
15
16
  }: React.ComponentProps<typeof Sidebar> & {
16
17
  user: SidebarUser;
18
+ version: string;
17
19
  }) {
18
20
  return (
19
21
  <Sidebar variant="inset" {...props}>
@@ -23,6 +25,7 @@ export function AppSidebar({
23
25
  Workspace
24
26
  </p>
25
27
  <p className="text-base font-semibold text-sidebar-foreground">RegionClaw</p>
28
+ <p className="text-xs text-sidebar-foreground/70">v{version}</p>
26
29
  </div>
27
30
  </SidebarHeader>
28
31
  <SidebarContent />
@@ -0,0 +1,229 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { RefreshCwIcon, TerminalSquareIcon } from "lucide-react";
5
+ import { toast } from "sonner";
6
+
7
+ import { Alert, AlertAction, AlertDescription, AlertTitle } from "@/components/ui/alert";
8
+ import { Button } from "@/components/ui/button";
9
+
10
+ type UpdateStatus = {
11
+ ok: boolean;
12
+ currentVersion: string;
13
+ latestVersion: string | null;
14
+ updateAvailable: boolean;
15
+ installKind: "global" | "npx" | "local";
16
+ packageManager: "npm" | "pnpm" | "unknown";
17
+ canUpdateInPlace: boolean;
18
+ recommendedCommand: string | null;
19
+ error?: string;
20
+ };
21
+
22
+ type UpdateRunResult = UpdateStatus & {
23
+ status: "updated" | "already-current" | "manual" | "error";
24
+ message: string;
25
+ requiresRestart?: boolean;
26
+ restartScheduled?: boolean;
27
+ };
28
+
29
+ export function RegionClawUpdateBanner() {
30
+ const [status, setStatus] = useState<UpdateStatus | null>(null);
31
+ const [result, setResult] = useState<UpdateRunResult | null>(null);
32
+ const [loading, setLoading] = useState(true);
33
+ const [updating, setUpdating] = useState(false);
34
+ const [dismissed, setDismissed] = useState(false);
35
+
36
+ useEffect(() => {
37
+ let cancelled = false;
38
+ let timeoutId: ReturnType<typeof setTimeout> | null = null;
39
+
40
+ function scheduleNextHourlyCheck() {
41
+ const now = new Date();
42
+ const next = new Date(now);
43
+ next.setMinutes(0, 0, 0);
44
+ next.setHours(next.getHours() + 1);
45
+ timeoutId = setTimeout(() => {
46
+ void loadStatus();
47
+ }, Math.max(1_000, next.getTime() - now.getTime()));
48
+ }
49
+
50
+ async function loadStatus() {
51
+ try {
52
+ const response = await fetch("/api/update/status", {
53
+ method: "GET",
54
+ credentials: "same-origin",
55
+ cache: "no-store",
56
+ });
57
+ const payload = (await response.json()) as UpdateStatus;
58
+ if (!cancelled) {
59
+ setStatus(payload.ok ? payload : null);
60
+ }
61
+ } catch {
62
+ if (!cancelled) {
63
+ setStatus(null);
64
+ }
65
+ } finally {
66
+ if (!cancelled) {
67
+ setLoading(false);
68
+ scheduleNextHourlyCheck();
69
+ }
70
+ }
71
+ }
72
+
73
+ void loadStatus();
74
+ return () => {
75
+ cancelled = true;
76
+ if (timeoutId) {
77
+ clearTimeout(timeoutId);
78
+ }
79
+ };
80
+ }, []);
81
+
82
+ async function waitForRestart(targetVersion: string) {
83
+ const startedAt = Date.now();
84
+
85
+ while (Date.now() - startedAt < 120_000) {
86
+ try {
87
+ const response = await fetch("/api/update/status", {
88
+ method: "GET",
89
+ credentials: "same-origin",
90
+ cache: "no-store",
91
+ });
92
+
93
+ if (response.ok) {
94
+ const payload = (await response.json()) as UpdateStatus;
95
+ if (payload.currentVersion === targetVersion || !payload.updateAvailable) {
96
+ window.location.reload();
97
+ return;
98
+ }
99
+ }
100
+ } catch {
101
+ // RegionClaw is probably restarting; keep polling.
102
+ }
103
+
104
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
105
+ }
106
+
107
+ toast.error("RegionClaw restarted too slowly. Refresh the page manually.");
108
+ }
109
+
110
+ async function copyCommand(command: string) {
111
+ try {
112
+ await navigator.clipboard.writeText(command);
113
+ toast.success("Update command copied.");
114
+ } catch {
115
+ toast.error("Failed to copy update command.");
116
+ }
117
+ }
118
+
119
+ async function runUpdate() {
120
+ setUpdating(true);
121
+ try {
122
+ const response = await fetch("/api/update/run", {
123
+ method: "POST",
124
+ credentials: "same-origin",
125
+ });
126
+ const payload = (await response.json()) as UpdateRunResult;
127
+ setResult(payload);
128
+
129
+ if (payload.status === "updated") {
130
+ toast.success(payload.message);
131
+ if (payload.restartScheduled && payload.latestVersion) {
132
+ void waitForRestart(payload.latestVersion);
133
+ return;
134
+ }
135
+
136
+ setStatus((previous) =>
137
+ previous
138
+ ? {
139
+ ...previous,
140
+ currentVersion: payload.latestVersion ?? previous.currentVersion,
141
+ updateAvailable: false,
142
+ }
143
+ : previous,
144
+ );
145
+ return;
146
+ }
147
+
148
+ if (payload.status === "manual" && payload.recommendedCommand) {
149
+ await copyCommand(payload.recommendedCommand);
150
+ return;
151
+ }
152
+
153
+ if (payload.status === "already-current") {
154
+ toast.success(payload.message);
155
+ return;
156
+ }
157
+
158
+ toast.error(payload.message);
159
+ } catch {
160
+ toast.error("Failed to run RegionClaw update.");
161
+ } finally {
162
+ setUpdating(false);
163
+ }
164
+ }
165
+
166
+ if (loading || dismissed) {
167
+ return null;
168
+ }
169
+
170
+ if (result?.status === "updated") {
171
+ return (
172
+ <div className="border-b px-4 py-3">
173
+ <Alert>
174
+ <TerminalSquareIcon className="size-4" />
175
+ <AlertTitle>RegionClaw updated</AlertTitle>
176
+ <AlertDescription>{result.message}</AlertDescription>
177
+ <AlertAction>
178
+ <Button size="sm" variant="ghost" onClick={() => setDismissed(true)}>
179
+ Dismiss
180
+ </Button>
181
+ </AlertAction>
182
+ </Alert>
183
+ </div>
184
+ );
185
+ }
186
+
187
+ if (!status?.ok || !status.updateAvailable || !status.latestVersion) {
188
+ return null;
189
+ }
190
+
191
+ const description = status.canUpdateInPlace
192
+ ? `Version ${status.latestVersion} is available. RegionClaw is currently running ${status.currentVersion}.`
193
+ : status.recommendedCommand
194
+ ? `Version ${status.latestVersion} is available. This session is running via ${status.installKind}, so update must be started with a command.`
195
+ : `Version ${status.latestVersion} is available, but this install cannot be updated automatically from inside RegionClaw.`;
196
+
197
+ return (
198
+ <div className="border-b px-4 py-3">
199
+ <Alert variant="destructive">
200
+ <RefreshCwIcon className="size-4" />
201
+ <AlertTitle>Update available</AlertTitle>
202
+ <AlertDescription>{description}</AlertDescription>
203
+ <AlertAction className="flex gap-2">
204
+ {status.recommendedCommand && !status.canUpdateInPlace ? (
205
+ <Button
206
+ size="sm"
207
+ variant="outline"
208
+ onClick={() => void copyCommand(status.recommendedCommand!)}
209
+ >
210
+ Copy command
211
+ </Button>
212
+ ) : null}
213
+ {status.canUpdateInPlace || status.recommendedCommand ? (
214
+ <Button size="sm" onClick={() => void runUpdate()} disabled={updating}>
215
+ {updating
216
+ ? "Updating..."
217
+ : status.canUpdateInPlace
218
+ ? "Update now"
219
+ : "Copy update command"}
220
+ </Button>
221
+ ) : null}
222
+ <Button size="sm" variant="ghost" onClick={() => setDismissed(true)}>
223
+ Dismiss
224
+ </Button>
225
+ </AlertAction>
226
+ </Alert>
227
+ </div>
228
+ );
229
+ }
@@ -0,0 +1,3 @@
1
+ export function getRegionClawVersion() {
2
+ return process.env.REGIONCLAW_VERSION ?? "dev";
3
+ }
@@ -0,0 +1,88 @@
1
+ import { execFile, spawn } from "node:child_process";
2
+ import path from "node:path";
3
+ import { promisify } from "node:util";
4
+
5
+ const execFileAsync = promisify(execFile);
6
+
7
+ function resolvePackageRoot() {
8
+ return (
9
+ process.env.REGIONCLAW_PACKAGE_ROOT ??
10
+ path.resolve(process.cwd(), "..", "..")
11
+ );
12
+ }
13
+
14
+ function resolveBinPath() {
15
+ return path.join(resolvePackageRoot(), "bin", "regionclaw.js");
16
+ }
17
+
18
+ export type RegionClawUpdateStatus = {
19
+ ok: boolean;
20
+ packageName: string;
21
+ currentVersion: string;
22
+ latestVersion: string | null;
23
+ updateAvailable: boolean;
24
+ installKind: "global" | "npx" | "local";
25
+ packageManager: "npm" | "pnpm" | "unknown";
26
+ canUpdateInPlace: boolean;
27
+ recommendedCommand: string | null;
28
+ error?: string;
29
+ };
30
+
31
+ export type RegionClawUpdateRunResult = RegionClawUpdateStatus & {
32
+ status: "updated" | "already-current" | "manual" | "error";
33
+ message: string;
34
+ requiresRestart?: boolean;
35
+ restartScheduled?: boolean;
36
+ command?: string;
37
+ stdout?: string | null;
38
+ stderr?: string | null;
39
+ };
40
+
41
+ async function runRegionClawCommand(args: string[]) {
42
+ const { stdout } = await execFileAsync(process.execPath, [resolveBinPath(), ...args], {
43
+ cwd: resolvePackageRoot(),
44
+ env: process.env,
45
+ maxBuffer: 1024 * 1024 * 4,
46
+ });
47
+
48
+ return stdout;
49
+ }
50
+
51
+ export async function getRegionClawUpdateStatus(): Promise<RegionClawUpdateStatus> {
52
+ const stdout = await runRegionClawCommand(["update", "status", "--json"]);
53
+ return JSON.parse(stdout) as RegionClawUpdateStatus;
54
+ }
55
+
56
+ export async function runRegionClawUpdate(): Promise<RegionClawUpdateRunResult> {
57
+ const stdout = await runRegionClawCommand(["update", "--json"]);
58
+ return JSON.parse(stdout) as RegionClawUpdateRunResult;
59
+ }
60
+
61
+ export function canScheduleRegionClawRestart() {
62
+ return Boolean(
63
+ process.env.REGIONCLAW_PARENT_PID &&
64
+ process.env.REGIONCLAW_HOST &&
65
+ process.env.REGIONCLAW_PORT,
66
+ );
67
+ }
68
+
69
+ export function scheduleRegionClawRestart() {
70
+ const parentPid = process.env.REGIONCLAW_PARENT_PID;
71
+ const host = process.env.REGIONCLAW_HOST;
72
+ const port = process.env.REGIONCLAW_PORT;
73
+
74
+ if (!parentPid || !host || !port) {
75
+ return false;
76
+ }
77
+
78
+ const helperPath = path.join(resolvePackageRoot(), "scripts", "restart-running-regionclaw.mjs");
79
+ const child = spawn(process.execPath, [helperPath, parentPid, resolveBinPath(), host, port], {
80
+ cwd: resolvePackageRoot(),
81
+ detached: true,
82
+ stdio: "ignore",
83
+ env: process.env,
84
+ });
85
+
86
+ child.unref();
87
+ return true;
88
+ }