Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,16 +416,17 @@ The active page's **Command reference** section provides a brief, copy-only
cheat sheet grouped under **ClawCtl** and **OpenClaw**. Enter these commands
in your normal Windows terminal (user session):

- **ClawCtl:** `clawctl pwsh` first, then `clawctl gateway-service status`,
`clawctl gateway-service stop && clawctl gateway-service start`, and
- **ClawCtl:** `clawctl gateway-service status`, `clawctl gateway-service restart`,
`clawctl open`, `clawctl pwsh`, and
`clawctl --help`. PowerShell opens inside the isolated agent, where `openclaw`
and `node` are available; ClawCtl manages the session from outside it.
The restart sequence preserves the session and its data, requires PowerShell 7,
and starts the Gateway only after a successful stop.
- **OpenClaw:** `openclaw tui`, `openclaw dashboard --no-open`, and
`openclaw --help`. The packaged `openclaw` command forwards to your agent
session; inside `clawctl pwsh`, it runs directly. The dashboard reference
shows the access URL for this dashboard without opening a browser.
Restart preserves the session and its data and starts the Gateway if it is
not running. An unverified stop prevents a replacement from starting.
**Open dashboard** opens the default browser when you run `clawctl open`;
it requires completed setup and a running Gateway, and does not print
authenticated URLs or tokens.
- **OpenClaw:** `openclaw tui` and `openclaw --help`. The packaged `openclaw`
command forwards to your agent session; inside `clawctl pwsh`, it runs directly.

The page never executes commands or sends mutation requests. Copy controls
announce success only after a clipboard operation succeeds; otherwise they
Expand Down
28 changes: 14 additions & 14 deletions plugins/gateway-isolation/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@ const COMMAND_GROUPS = [
id: "clawctl",
title: "ClawCtl",
commands: [
{
id: "agent-shell",
title: "Agent session PowerShell",
description: "Open PowerShell inside the isolated agent. openclaw and node are available there; clawctl manages the session from outside it.",
command: "clawctl pwsh",
},
{
id: "gateway-status",
title: "Gateway status",
Expand All @@ -21,8 +15,20 @@ const COMMAND_GROUPS = [
{
id: "gateway-restart",
title: "Restart Gateway",
description: "Stop, then start the gateway, keeping the session and its data. Requires PowerShell 7 on the Gateway host.",
command: "clawctl gateway-service stop && clawctl gateway-service start",
description: "Restart the gateway, keeping the session and its data. Starts it if no gateway is running.",
command: "clawctl gateway-service restart",
},
{
id: "dashboard",
title: "Open dashboard",
description: "Open the dashboard in your default browser. Requires completed setup and a running gateway. Does not print authenticated URLs or tokens.",
command: "clawctl open",
},
{
id: "agent-shell",
title: "Agent session PowerShell",
description: "Open PowerShell inside the isolated agent. openclaw and node are available there; clawctl manages the session from outside it.",
command: "clawctl pwsh",
},
{
id: "launcher-help",
Expand All @@ -43,12 +49,6 @@ const COMMAND_GROUPS = [
description: "Chat in the terminal.",
command: "openclaw tui",
},
{
id: "dashboard",
title: "Dashboard access",
description: "Show the access URL for this dashboard.",
command: "openclaw dashboard --no-open",
},
{
id: "openclaw-help",
title: "OpenClaw help",
Expand Down
36 changes: 19 additions & 17 deletions plugins/gateway-isolation/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,12 @@ test("renders one informational Gateway Isolation row with a single active badge
});

const expectedCommands = [
["Agent session PowerShell", "clawctl pwsh"],
["Gateway status", "clawctl gateway-service status"],
["Restart Gateway", "clawctl gateway-service stop && clawctl gateway-service start"],
["Restart Gateway", "clawctl gateway-service restart"],
["Open dashboard", "clawctl open"],
["Agent session PowerShell", "clawctl pwsh"],
["Launcher help", "clawctl --help"],
["Gateway chat TUI", "openclaw tui"],
["Dashboard access", "openclaw dashboard --no-open"],
["OpenClaw help", "openclaw --help"],
];

Expand All @@ -176,17 +176,17 @@ test("groups the brief cheat sheet with ClawCtl first and packaged OpenClaw seco
[1, "Windows Launcher"],
[2, "Command reference"],
[3, "ClawCtl"],
...expectedCommands.slice(0, 4).map(([title]) => [4, title]),
...expectedCommands.slice(0, 5).map(([title]) => [4, title]),
[3, "OpenClaw"],
...expectedCommands.slice(4).map(([title]) => [4, title]),
...expectedCommands.slice(5).map(([title]) => [4, title]),
],
);
const groups = [...html.matchAll(/<section aria-labelledby="([^"]+)">([\s\S]*?)<\/section>/g)];
assert.deepEqual(groups.map(match => match[1]), ["clawctl-title", "openclaw-title"]);
assert.match(groups[0][2], /<h3 id="clawctl-title" class="command-group-title">ClawCtl<\/h3>/);
assert.match(html, /<h2 class="command-reference-title">Command reference<\/h2>\s*<p class="intro">Run these commands in your normal Windows terminal \(user session\)\.<\/p>\s*<section aria-labelledby="clawctl-title">/);
assert.match(groups[1][2], /<h3 id="openclaw-title" class="command-group-title">OpenClaw<\/h3>\s*<p class="intro">The packaged openclaw command forwards to your agent session\. Inside clawctl pwsh, it runs directly\.<\/p>/);
for (const [index, expected] of [expectedCommands.slice(0, 4), expectedCommands.slice(4)].entries()) {
for (const [index, expected] of [expectedCommands.slice(0, 5), expectedCommands.slice(5)].entries()) {
assert.deepEqual(
[...groups[index][2].matchAll(/<h4>([^<]+)<\/h4>/g)].map(match => match[1]),
expected.map(([title]) => title),
Expand All @@ -195,12 +195,12 @@ test("groups the brief cheat sheet with ClawCtl first and packaged OpenClaw seco
assert.deepEqual(
[...html.matchAll(/<p class="command-description">([^<]+)<\/p>/g)].map(match => match[1]),
[
"Open PowerShell inside the isolated agent. openclaw and node are available there; clawctl manages the session from outside it.",
"Show whether the gateway is running.",
"Stop, then start the gateway, keeping the session and its data. Requires PowerShell 7 on the Gateway host.",
"Restart the gateway, keeping the session and its data. Starts it if no gateway is running.",
"Open the dashboard in your default browser. Requires completed setup and a running gateway. Does not print authenticated URLs or tokens.",
"Open PowerShell inside the isolated agent. openclaw and node are available there; clawctl manages the session from outside it.",
"List launcher commands.",
"Chat in the terminal.",
"Show the access URL for this dashboard.",
"List OpenClaw commands.",
],
);
Expand All @@ -218,7 +218,7 @@ test("offers only supported general commands with individually named copy contro
assert.equal([...html.matchAll(/id="copy-status"/g)].length, 1);
assert.match(html, /button:focus-visible/);
assert.match(html, /@media \(max-width: 620px\)/);
assert.doesNotMatch(html, /clawctl tui|clawctl tty|clawctl dashboard|gateway-service restart|help --all|--help --all/);
assert.doesNotMatch(html, /clawctl tui|clawctl tty|clawctl dashboard|gateway-service stop|openclaw dashboard|--no-open|--no-browser|help --all|--help --all/);
});

function runCopyScript({ legacy = true, selectionAvailable = true, modern = "missing" } = {}) {
Expand Down Expand Up @@ -284,15 +284,17 @@ test("copies every exact command synchronously and announces only successful cop
for (const legacy of [false, "throw"]) {
test(`uses Clipboard API when legacy copy returns ${legacy}`, async () => {
const copy = runCopyScript({ legacy, modern: "success" });
const restartIndex = expectedCommands.findIndex(([title]) => title === "Restart Gateway");
await copy.buttons[restartIndex].click();
assert.equal(copy.feedback.textContent, "Copied Restart Gateway command.");
assert.deepEqual(copy.calls.at(-1), ["modern", expectedCommands[restartIndex][1]]);
for (const title of ["Restart Gateway", "Open dashboard"]) {
const index = expectedCommands.findIndex(([commandTitle]) => commandTitle === title);
await copy.buttons[index].click();
assert.equal(copy.feedback.textContent, `Copied ${title} command.`);
assert.deepEqual(copy.calls.at(-1), ["modern", expectedCommands[index][1]]);
}
});
for (const modern of ["missing", "reject"]) {
test(`offers selected manual copy when legacy=${legacy} and modern=${modern}`, async () => {
const copy = runCopyScript({ legacy, modern });
for (const index of [0, 2, 0]) {
for (const index of [3, 1, 2, 3]) {
await copy.buttons[index].click();
assert.equal(copy.feedback.textContent, `Copy unavailable. ${expectedCommands[index][0]} command selected; press Ctrl+C to copy.`);
assert.equal(copy.selected.textContent, expectedCommands[index][1]);
Expand All @@ -304,14 +306,14 @@ for (const legacy of [false, "throw"]) {

test("reports inability to select or copy without claiming manual selection succeeded", async () => {
const copy = runCopyScript({ selectionAvailable: false });
await copy.buttons[0].click();
await copy.buttons[3].click();
assert.equal(copy.feedback.textContent, "Could not copy Agent session PowerShell command. Select the command and copy it manually.");
assert.equal(copy.selected, undefined);
});

test("can use Clipboard API without DOM selection support", async () => {
const copy = runCopyScript({ selectionAvailable: false, modern: "success" });
await copy.buttons[0].click();
await copy.buttons[3].click();
assert.equal(copy.feedback.textContent, "Copied Agent session PowerShell command.");
assert.deepEqual(copy.calls, [["modern", "clawctl pwsh"]]);
});
Expand Down
Loading