From c872f6c4c4926260a70574eb6a3396bb281f361c Mon Sep 17 00:00:00 2001 From: Chris Wanstrath <2+defunkt@users.noreply.github.com> Date: Tue, 7 Oct 2025 17:01:38 -0700 Subject: [PATCH] browser commands are now real commands! (again) --- bin/browse.ts | 16 ++++++++++++++++ bin/browser-session.ts | 6 ++++++ bin/clear.ts | 8 ++++++++ bin/commands.ts | 8 ++++++++ bin/fullscreen.ts | 4 ++++ bin/reload.ts | 4 ++++ bin/status.ts | 6 ++++++ src/commands.ts | 2 +- src/js/commands.ts | 27 +++------------------------ src/js/shell.ts | 9 +++++++-- src/server.tsx | 7 ++++++- 11 files changed, 69 insertions(+), 28 deletions(-) create mode 100644 bin/browse.ts create mode 100644 bin/browser-session.ts create mode 100644 bin/clear.ts create mode 100644 bin/commands.ts create mode 100644 bin/fullscreen.ts create mode 100644 bin/reload.ts create mode 100644 bin/status.ts diff --git a/bin/browse.ts b/bin/browse.ts new file mode 100644 index 0000000..ff642c8 --- /dev/null +++ b/bin/browse.ts @@ -0,0 +1,16 @@ +/// +// Open a URL (or the current project) in the NOSEbrowser. + +import { openBrowser } from "@/js/browser" +import { setStatus, latestId } from "@/js/scrollback" +import { currentAppUrl } from "@/js/webapp" + +export default function (url?: string) { + const currentUrl = url ?? currentAppUrl() + if (currentUrl) { + openBrowser(currentUrl, "command") + } else { + setTimeout(() => setStatus(latestId()!, "error"), 0) + return "usage: browse " + } +} \ No newline at end of file diff --git a/bin/browser-session.ts b/bin/browser-session.ts new file mode 100644 index 0000000..abf8b51 --- /dev/null +++ b/bin/browser-session.ts @@ -0,0 +1,6 @@ +/// +// Print the browser's session ID + +import { sessionId } from "@/js/session" + +export default () => sessionId \ No newline at end of file diff --git a/bin/clear.ts b/bin/clear.ts new file mode 100644 index 0000000..f69c6f9 --- /dev/null +++ b/bin/clear.ts @@ -0,0 +1,8 @@ +/// +// Clear the scrollback history. + +import { scrollback } from "@/js/dom" + +export default function () { + scrollback.innerHTML = "" +} \ No newline at end of file diff --git a/bin/commands.ts b/bin/commands.ts new file mode 100644 index 0000000..c143fc3 --- /dev/null +++ b/bin/commands.ts @@ -0,0 +1,8 @@ +/// +// Print all the commands. + +import { commands } from "@/js/commands" + +export default function () { + return { html: "
" + Object.keys(commands).map(cmd => `${cmd}`).join("") + "
" } +} \ No newline at end of file diff --git a/bin/fullscreen.ts b/bin/fullscreen.ts new file mode 100644 index 0000000..0b05504 --- /dev/null +++ b/bin/fullscreen.ts @@ -0,0 +1,4 @@ +/// +// Toggle fullscreen mode. + +export default () => { document.body.requestFullscreen() } \ No newline at end of file diff --git a/bin/reload.ts b/bin/reload.ts new file mode 100644 index 0000000..31cdc60 --- /dev/null +++ b/bin/reload.ts @@ -0,0 +1,4 @@ +/// +// Reload the browser window. + +export default () => { window.location.reload() } \ No newline at end of file diff --git a/bin/status.ts b/bin/status.ts new file mode 100644 index 0000000..fbdbed3 --- /dev/null +++ b/bin/status.ts @@ -0,0 +1,6 @@ +/// +// Display a status message in the status bar. + +import { status } from "@/js/statusbar" + +export default (msg: string) => { status(msg) } \ No newline at end of file diff --git a/src/commands.ts b/src/commands.ts index 15e6fd3..ed6fced 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -43,7 +43,7 @@ export async function findCommands(path: string): Promise { function describeCommand(path: string): Command { const code = readFileSync(path, "utf8") let game = /^export const game = true$/mg.test(code) - let browser = /^\/\/\/ ?$/.test(code) + let browser = /^\/\/\/ ?$/mg.test(code) return { name: basename(path).replace(/\.tsx?$/, ""), diff --git a/src/js/commands.ts b/src/js/commands.ts index 7755b00..87249b1 100644 --- a/src/js/commands.ts +++ b/src/js/commands.ts @@ -1,35 +1,16 @@ //// // temporary hack for browser commands -import type { CommandOutput, Commands, Command } from "../shared/types" -import { openBrowser } from "./browser" -import { scrollback, content } from "./dom" +import type { CommandOutput, Commands } from "../shared/types" +import { content } from "./dom" import { focusInput } from "./focus" import { resize } from "./resize" -import { sessionId } from "./session" import { send } from "./websocket" -import { status } from "./statusbar" -import { setStatus, latestId } from "./scrollback" -import { currentAppUrl } from "./webapp" export const commands: Commands = {} +// TODO: convert to bin/shell "browser" commands export const browserCommands: Record void | Promise | CommandOutput> = { - browse: (url?: string) => { - const currentUrl = url ?? currentAppUrl() - if (currentUrl) { - openBrowser(currentUrl, "command") - } else { - setTimeout(() => setStatus(latestId()!, "error"), 0) - return "usage: browse " - } - }, - "browser-session": () => sessionId, - clear: () => scrollback.innerHTML = "", - commands: () => { - return { html: "
" + Object.keys(commands).map(cmd => `${cmd}`).join("") + "
" } - }, - fullscreen: () => document.body.requestFullscreen(), mode: (mode?: string) => { if (!mode) { mode = document.body.dataset.mode === "tall" ? "cinema" : "tall" @@ -41,8 +22,6 @@ export const browserCommands: Record void | Promi resize() focusInput() }, - status: (msg: string) => status(msg), - reload: () => window.location.reload(), } export function cacheCommands(cmds: Commands) { diff --git a/src/js/shell.ts b/src/js/shell.ts index 63aa410..0764a7d 100644 --- a/src/js/shell.ts +++ b/src/js/shell.ts @@ -6,7 +6,7 @@ import { addInput, setStatus, addOutput } from "./scrollback" import { send } from "./websocket" import { randomId } from "../shared/utils" import { addToHistory } from "./history" -import { browserCommands } from "./commands" +import { browserCommands, commands } from "./commands" export async function runCommand(input: string) { if (!input.trim()) return @@ -23,7 +23,12 @@ export async function runCommand(input: string) { const [cmd = "", ...args] = input.split(" ") - if (browserCommands[cmd]) { + if (commands[cmd]?.type === "browser") { + const mod = await import(`/source/${cmd}?t=${Date.now()}`) + const result = mod.default(...args) + if (result) addOutput(id, result) + setStatus(id, "ok") + } else if (browserCommands[cmd]) { const result = await browserCommands[cmd](...args) if (result) addOutput(id, result) setStatus(id, "ok") diff --git a/src/server.tsx b/src/server.tsx index 0ec1f36..b2ed009 100644 --- a/src/server.tsx +++ b/src/server.tsx @@ -133,7 +133,12 @@ app.get("/source/:name", async c => { const sessionId = c.req.query("session") || "0" const path = await sessionRun(sessionId, () => commandPath(name)) if (!path) return c.text("Command not found", 404) - return new Response(await transpile(path), { + + let code = await transpile(path) + // rewrite imports so they work on the server and in the browser + code = code.replace(/from (["'])@\/js\//g, "from $1../js/") + + return new Response(code, { headers: { "Content-Type": "text/javascript" }