From d97456e0ef7ef30b900b23d53da22d81cf27351a Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 19 Oct 2026 13:05:00 +0000 Subject: [PATCH] readm3 update and readm3 uninstall (remove), via @profullstack/cli (v0.13.3) Both act the way this copy was installed: the curl installer (keeping --desktop when the desktop app came with it, and the install prefix), npm, bun, pnpm or yarn. update checks npm first (--check only reports); uninstall asks first (--yes). Co-Authored-By: Claude Opus 5.5 --- README.md | 4 +++- bun.lock | 5 ++++- desktop/package.json | 2 +- package.json | 3 ++- src/cli.ts | 18 +++++++++++++++--- test/cli.test.ts | 5 +++++ 6 files changed, 30 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f48b9a9..1908c98 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,9 @@ on the machine it fetches a private official Node first: curl -fsSL https://readm3.com/install.sh | sh ``` -Run it again to update; remove with `sh ~/.local/share/readm3/uninstall.sh`. +Update with `readm3 update` and remove with `readm3 uninstall`; both work the way readm3 +was installed (this installer, npm, bun, pnpm or yarn), through +[@profullstack/cli](https://github.com/profullstack/cli). Add `--desktop` to install the desktop editor as well (Linux x64; macOS and Windows download it, see [Desktop app](#desktop-app)): diff --git a/bun.lock b/bun.lock index 1d7f5f5..d870e00 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,8 @@ "name": "@profullstack/readm3", "dependencies": { "@modelcontextprotocol/sdk": "^1.30.0", - "@profullstack/footer": "^0.2.4", + "@profullstack/cli": "^0.1.0", + "@profullstack/footer": "latest", "@profullstack/hqtui": "^0.5.0", "@profullstack/synconfig": "^0.1.3", "highlight.js": "^11.12.0", @@ -141,6 +142,8 @@ "@playwright/test": ["@playwright/test@1.63.0", "", { "dependencies": { "playwright": "1.63.0" }, "bin": { "playwright": "cli.js" } }, "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ=="], + "@profullstack/cli": ["@profullstack/cli@0.1.0", "", {}, "sha512-kX2mMvxsfsz89u3RIj2cGRIb89TUVUgwNO1rlTowPuHQrsStsOCNtz8KQ/VqPgJ2FjpfAj0CCQwCRgXaehPL5w=="], + "@profullstack/footer": ["@profullstack/footer@0.2.4", "", { "peerDependencies": { "react": ">=18" }, "optionalPeers": ["react"], "bin": { "profullstack-footer": "bin/profullstack-footer.js" } }, "sha512-2ErIzHJCDA2baP8aK6f7rCHohW4EX+wnp7D1mrVMQTUxRcyZwKzmho96hhEFJndXRBGgxT3H/78BHOeBfLxTzg=="], "@profullstack/hqtui": ["@profullstack/hqtui@0.5.0", "", { "bin": { "hqtui": "bin/hqtui.mjs" } }, "sha512-jDwILBmdQx8pQA0ao/tzzty8u5nFNS4XhvU9xXhZvdIaMeiF3HBJIVtP6cS6kigS4lXmHR7sztbb9QJpaWwhig=="], diff --git a/desktop/package.json b/desktop/package.json index 93229aa..c7733a6 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,7 +1,7 @@ { "name": "readm3-desktop", "productName": "readm3", - "version": "0.13.2", + "version": "0.13.3", "private": true, "description": "readm3 as a desktop app: a plain, fast text editor for Markdown and code.", "author": "Profullstack, Inc. ", diff --git a/package.json b/package.json index 58bca00..953c887 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@profullstack/readm3", - "version": "0.13.2", + "version": "0.13.3", "description": "Markdown editing and sharing for people and teams. Web/PWA, CLI, API and MCP with version history, view/edit links, and a terminal reader.", "type": "module", "license": "MIT", @@ -43,6 +43,7 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "^1.30.0", + "@profullstack/cli": "^0.1.0", "@profullstack/footer": "latest", "@profullstack/hqtui": "^0.5.0", "@profullstack/synconfig": "^0.1.3", diff --git a/src/cli.ts b/src/cli.ts index 6d9de8d..27d4f13 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,8 +1,9 @@ /** * Argument parsing and entry point. `readm3 --help` is the contract. */ -import { statSync } from "node:fs"; -import { dirname, resolve } from "node:path"; +import { existsSync, realpathSync, statSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { detectInstall, lifecycle, lifecycleUsage } from "@profullstack/cli"; import { pathToFileURL } from "node:url"; import { themeList } from "@profullstack/hqtui"; import { FLAVORS, isFlavor, type Flavor } from "./flavors.ts"; @@ -11,7 +12,7 @@ import { run, type ViewerOptions } from "./viewer.ts"; import { isCloudCommand, cloudMain, CLOUD_USAGE } from "./cloud-cli.ts"; import { isDesktopCommand, desktopMain, installedDesktop, prefersDesktop, DESKTOP_USAGE } from "./desktop-cli.ts"; -export const VERSION = "0.13.2"; +export const VERSION = "0.13.3"; /** The help text, exported so a test can hold it to the flags it documents. */ export const USAGE = `readm3 — a plain text editor for Markdown and code @@ -61,6 +62,7 @@ Editing ctrl+g keys for editing tab indent two spaces ${DESKTOP_USAGE} +${lifecycleUsage("readm3")} ${CLOUD_USAGE}`; export interface ParsedArgs extends ViewerOptions { @@ -186,7 +188,17 @@ export function parseArgs(argv: string[]): ParsedArgs { return parsed; } +/** An installer copy that also installed the desktop app keeps it on update. */ +function keepDesktop(): string[] { + let script: string | undefined; + try { script = process.argv[1] && realpathSync(process.argv[1]); } catch { return []; } + const { share } = detectInstall({ name: "readm3", script }); + return share && (existsSync(join(share, "desktop")) || existsSync(join(share, "readm3.AppImage"))) ? ["--desktop"] : []; +} + export async function main(argv: string[] = process.argv.slice(2)): Promise { + // readm3 update / uninstall (remove): done the way this copy was installed. + if (await lifecycle(argv, { name: "readm3", package: "@profullstack/readm3", version: VERSION, installer: "https://readm3.com/install.sh", installerArgs: keepDesktop })) return; const { accountCommand } = await import("./sync-cli.ts"); if (await accountCommand(argv)) return; if (isCloudCommand(argv[0])) { await cloudMain(argv); return; } diff --git a/test/cli.test.ts b/test/cli.test.ts index 662bce0..e2e8102 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -7,6 +7,11 @@ import { readFile } from "node:fs/promises"; import { parseArgs, USAGE, VERSION } from "../src/cli.ts"; import { FLAVORS } from "../src/flavors.ts"; +test("help names update and uninstall", () => { + assert.match(USAGE, /readm3 update \[--check\]/); + assert.match(USAGE, /readm3 uninstall \[--yes\]/); +}); + test("no arguments browse the working directory", () => { const args = parseArgs([]); assert.equal(args.root, process.cwd());