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.
- package/README.md +15 -0
- package/apps/ui/regionclaw-build/BUILD_ID +1 -1
- package/apps/ui/regionclaw-build/app-path-routes-manifest.json +2 -0
- package/apps/ui/regionclaw-build/build-manifest.json +2 -2
- package/apps/ui/regionclaw-build/prerender-manifest.json +0 -48
- package/apps/ui/regionclaw-build/routes-manifest.json +12 -0
- package/apps/ui/regionclaw-build/server/app/(app)/dashboard/page.js +3 -3
- package/apps/ui/regionclaw-build/server/app/(app)/dashboard/page_client-reference-manifest.js +1 -1
- package/apps/ui/regionclaw-build/server/app/_global-error/page.js +3 -3
- package/apps/ui/regionclaw-build/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/apps/ui/regionclaw-build/server/app/_global-error.html +1 -1
- package/apps/ui/regionclaw-build/server/app/_global-error.rsc +1 -1
- package/apps/ui/regionclaw-build/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/apps/ui/regionclaw-build/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/apps/ui/regionclaw-build/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/apps/ui/regionclaw-build/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/apps/ui/regionclaw-build/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/apps/ui/regionclaw-build/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/apps/ui/regionclaw-build/server/app/_not-found/page.js +2 -2
- package/apps/ui/regionclaw-build/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/apps/ui/regionclaw-build/server/app/_not-found.html +1 -1
- package/apps/ui/regionclaw-build/server/app/_not-found.rsc +4 -4
- package/apps/ui/regionclaw-build/server/app/_not-found.segments/_full.segment.rsc +4 -4
- package/apps/ui/regionclaw-build/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/apps/ui/regionclaw-build/server/app/_not-found.segments/_index.segment.rsc +4 -4
- package/apps/ui/regionclaw-build/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/apps/ui/regionclaw-build/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/apps/ui/regionclaw-build/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/apps/ui/regionclaw-build/server/app/api/auth/[...nextauth]/route.js +1 -1
- package/apps/ui/regionclaw-build/server/app/api/auth/first-run/route.js +1 -1
- package/apps/ui/regionclaw-build/server/app/api/trpc/[trpc]/route.js +2 -2
- package/apps/ui/regionclaw-build/server/app/api/update/run/route.js +1 -0
- package/apps/ui/regionclaw-build/server/app/api/update/run/route.js.nft.json +1 -0
- package/apps/ui/regionclaw-build/server/app/api/update/run/route_client-reference-manifest.js +1 -0
- package/apps/ui/regionclaw-build/server/app/api/update/status/route.js +1 -0
- package/apps/ui/regionclaw-build/server/app/api/update/status/route.js.nft.json +1 -0
- package/apps/ui/regionclaw-build/server/app/api/update/status/route_client-reference-manifest.js +1 -0
- package/apps/ui/regionclaw-build/server/app/favicon.ico/route.js +1 -1
- package/apps/ui/regionclaw-build/server/app/login/page.js +2 -2
- package/apps/ui/regionclaw-build/server/app/login/page_client-reference-manifest.js +1 -1
- package/apps/ui/regionclaw-build/server/app/page.js +2 -2
- package/apps/ui/regionclaw-build/server/app/page_client-reference-manifest.js +1 -1
- package/apps/ui/regionclaw-build/server/app/setup/page.js +2 -2
- package/apps/ui/regionclaw-build/server/app/setup/page_client-reference-manifest.js +1 -1
- package/apps/ui/regionclaw-build/server/app-paths-manifest.json +2 -0
- package/apps/ui/regionclaw-build/server/chunks/263.js +2 -2
- package/apps/ui/regionclaw-build/server/chunks/324.js +1 -1
- package/apps/ui/regionclaw-build/server/chunks/902.js +3 -3
- package/apps/ui/regionclaw-build/server/middleware-build-manifest.js +1 -1
- package/apps/ui/regionclaw-build/server/pages/404.html +1 -1
- package/apps/ui/regionclaw-build/server/pages/500.html +1 -1
- package/apps/ui/regionclaw-build/static/ZSgRLh43NEMa5560KIFa0/_buildManifest.js +1 -0
- package/apps/ui/regionclaw-build/static/chunks/557-16373a4e3e7fff4a.js +1 -0
- package/apps/ui/regionclaw-build/static/chunks/609-413da697ed2385f4.js +1 -0
- package/apps/ui/regionclaw-build/static/chunks/{777-2c6a164314456938.js → 835-8d09234672e1cdbd.js} +2 -2
- package/apps/ui/regionclaw-build/static/chunks/app/(app)/dashboard/page-fdd3dce681b59047.js +1 -0
- package/apps/ui/regionclaw-build/static/chunks/app/(app)/layout-16f7ce76f7f41b68.js +1 -0
- package/apps/ui/regionclaw-build/static/chunks/app/_global-error/page-fdd3dce681b59047.js +1 -0
- package/apps/ui/regionclaw-build/static/chunks/app/api/auth/[...nextauth]/route-fdd3dce681b59047.js +1 -0
- package/apps/ui/regionclaw-build/static/chunks/app/api/auth/first-run/route-fdd3dce681b59047.js +1 -0
- package/apps/ui/regionclaw-build/static/chunks/app/api/trpc/[trpc]/route-fdd3dce681b59047.js +1 -0
- package/apps/ui/regionclaw-build/static/chunks/app/api/update/run/route-fdd3dce681b59047.js +1 -0
- package/apps/ui/regionclaw-build/static/chunks/app/api/update/status/route-fdd3dce681b59047.js +1 -0
- package/apps/ui/regionclaw-build/static/chunks/app/{layout-87ffd83df9489fc3.js → layout-812a5dd3c2548265.js} +1 -1
- package/apps/ui/regionclaw-build/static/chunks/app/page-fdd3dce681b59047.js +1 -0
- package/apps/ui/regionclaw-build/static/chunks/next/dist/client/components/builtin/app-error-fdd3dce681b59047.js +1 -0
- package/apps/ui/regionclaw-build/static/chunks/next/dist/client/components/builtin/forbidden-fdd3dce681b59047.js +1 -0
- package/apps/ui/regionclaw-build/static/chunks/next/dist/client/components/builtin/not-found-fdd3dce681b59047.js +1 -0
- package/apps/ui/regionclaw-build/static/chunks/next/dist/client/components/builtin/unauthorized-fdd3dce681b59047.js +1 -0
- package/apps/ui/regionclaw-build/types/app/api/update/run/route.ts +351 -0
- package/apps/ui/regionclaw-build/types/app/api/update/status/route.ts +351 -0
- package/apps/ui/regionclaw-build/types/link.d.ts +2 -0
- package/apps/ui/regionclaw-build/types/routes.d.ts +3 -1
- package/apps/ui/regionclaw-build/types/validator.ts +18 -0
- package/apps/ui/src/app/(app)/layout.tsx +7 -1
- package/apps/ui/src/app/api/update/run/route.ts +36 -0
- package/apps/ui/src/app/api/update/status/route.ts +24 -0
- package/apps/ui/src/components/app-layout-header.tsx +8 -2
- package/apps/ui/src/components/app-sidebar.tsx +3 -0
- package/apps/ui/src/components/regionclaw-update-banner.tsx +229 -0
- package/apps/ui/src/server/regionclaw-meta.ts +3 -0
- package/apps/ui/src/server/update-cli.ts +88 -0
- package/bin/regionclaw.js +357 -3
- package/package.json +1 -1
- package/scripts/restart-running-regionclaw.mjs +54 -0
- package/apps/ui/regionclaw-build/server/app/index.html +0 -1
- package/apps/ui/regionclaw-build/server/app/index.meta +0 -16
- package/apps/ui/regionclaw-build/server/app/index.rsc +0 -21
- package/apps/ui/regionclaw-build/server/app/index.segments/__PAGE__.segment.rsc +0 -6
- package/apps/ui/regionclaw-build/server/app/index.segments/_full.segment.rsc +0 -21
- package/apps/ui/regionclaw-build/server/app/index.segments/_head.segment.rsc +0 -6
- package/apps/ui/regionclaw-build/server/app/index.segments/_index.segment.rsc +0 -9
- package/apps/ui/regionclaw-build/server/app/index.segments/_tree.segment.rsc +0 -5
- package/apps/ui/regionclaw-build/server/app/login.html +0 -1
- package/apps/ui/regionclaw-build/server/app/login.meta +0 -17
- package/apps/ui/regionclaw-build/server/app/login.rsc +0 -23
- package/apps/ui/regionclaw-build/server/app/login.segments/_full.segment.rsc +0 -23
- package/apps/ui/regionclaw-build/server/app/login.segments/_head.segment.rsc +0 -6
- package/apps/ui/regionclaw-build/server/app/login.segments/_index.segment.rsc +0 -9
- package/apps/ui/regionclaw-build/server/app/login.segments/_tree.segment.rsc +0 -5
- package/apps/ui/regionclaw-build/server/app/login.segments/login/__PAGE__.segment.rsc +0 -6
- package/apps/ui/regionclaw-build/server/app/login.segments/login.segment.rsc +0 -5
- package/apps/ui/regionclaw-build/static/chunks/314-50a286a8a2defe3e.js +0 -1
- package/apps/ui/regionclaw-build/static/chunks/774-b72ba79217cd49b6.js +0 -1
- package/apps/ui/regionclaw-build/static/chunks/app/(app)/dashboard/page-ee954e3484be7e8f.js +0 -1
- package/apps/ui/regionclaw-build/static/chunks/app/(app)/layout-8c2cbeb73ce3441e.js +0 -1
- package/apps/ui/regionclaw-build/static/chunks/app/_global-error/page-ee954e3484be7e8f.js +0 -1
- package/apps/ui/regionclaw-build/static/chunks/app/api/auth/[...nextauth]/route-ee954e3484be7e8f.js +0 -1
- package/apps/ui/regionclaw-build/static/chunks/app/api/auth/first-run/route-ee954e3484be7e8f.js +0 -1
- package/apps/ui/regionclaw-build/static/chunks/app/api/trpc/[trpc]/route-ee954e3484be7e8f.js +0 -1
- package/apps/ui/regionclaw-build/static/chunks/app/page-ee954e3484be7e8f.js +0 -1
- package/apps/ui/regionclaw-build/static/chunks/next/dist/client/components/builtin/app-error-ee954e3484be7e8f.js +0 -1
- package/apps/ui/regionclaw-build/static/chunks/next/dist/client/components/builtin/forbidden-ee954e3484be7e8f.js +0 -1
- package/apps/ui/regionclaw-build/static/chunks/next/dist/client/components/builtin/not-found-ee954e3484be7e8f.js +0 -1
- package/apps/ui/regionclaw-build/static/chunks/next/dist/client/components/builtin/unauthorized-ee954e3484be7e8f.js +0 -1
- package/apps/ui/regionclaw-build/static/tx6PZf8zETvK0EvVfqK73/_buildManifest.js +0 -1
- /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({
|
|
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,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
|
+
}
|