clone

push a directory of html from the terminal and get a four-character url back. a deno cli on cloudflare

feat/site-feedback
nzip / cli / main.ts
main.ts 7.4K
1#!/usr/bin/env -S deno run --allow-net --allow-read --allow-write --allow-env2/**3 * `nzip` — push a directory of HTML from the terminal and get a four-character4 * URL back. This is the CLI entrypoint; it parses argv and dispatches to the5 * command handlers (`auth`, `status`, `app`, `vault`, `site`, and `notify`).6 *7 * Install and run it as a command rather than importing it:8 *9 * ```sh10 * deno install -g -A -f -n nzip jsr:@nzip/cli11 * nzip auth --server https://share.demo.dev12 * nzip site push ./site work:demo13 * ```14 *15 * @module16 */17// nzip — push HTML and get a tiny URL back.1819import { VERSION } from "@nzip/shared";20import { cmdAuth } from "./commands/auth.ts";21import { cmdApp } from "./commands/app.ts";22import { cmdNotify } from "./commands/notify.ts";23import { cmdSiteGroup } from "./commands/site.ts";24import { cmdStatus } from "./commands/sites.ts";25import { cmdVault } from "./commands/vault.ts";26import { requireConfig } from "./lib/config.ts";27import { parseCliArgs } from "./lib/args.ts";28import { fail, setJsonMode } from "./lib/fmt.ts";2930const HELP = `nzip — html share tool3132global:33 nzip --help show this command guide34 nzip --version [--json] show the installed CLI version3536commands:37 nzip38 ├─ auth [--server URL] [--token T] authenticate against the server39 ├─ status show server and vault status40 ├─ mcp [--root /absolute/path] serve stateless MCP over stdio41 ├─ app42 │ ├─ init <alias|vault:alias> reserve a stable app URL43 │ └─ deploy build and deploy the configured lofi app44 ├─ vault45 │ ├─ add <name> [--slot N] [--default-ttl 14d|forever|inherit]46 │ ├─ update <name> [--default-ttl 14d|forever|inherit]47 │ ├─ ls list vaults48 │ └─ default <temporary|permanent> <name>49 ├─ site50 │ ├─ push <dir|file> [target] [--new] publish content; reuse source by default51 │ ├─ cp <target> [dir] [--overwrite] copy a hosted bundle52 │ ├─ show <target> show site details53 │ ├─ policy <target> [--ttl ...] [--password PW | --no-password] [--feedback | --no-feedback]54 │ ├─ feedback <target> [--request [--message TEXT] [--callback URL]] read notes or request a review55 │ ├─ ls [vault] list sites56 │ ├─ where <target> print this machine's source directory57 │ ├─ rm <target> [--yes] delete a site58 │ └─ revert <target> [--to N] [--list] inspect or restore push history59 └─ notify60 ├─ send <body> [--title TEXT] [--open TARGET] [--tag TEXT]61 ├─ test queue a diagnostic notification62 ├─ pair allow device pairing for 10 minutes63 ├─ approve <code> --name NAME [--yes]64 ├─ devices [--all] list current notification devices65 └─ revoke <device-id> [--yes] revoke a notification device6667targets: 2a3f | work:demo | demo (alias in temporary default vault)6869agent mode: add --json to any command for one-line JSON on stdout;70errors go to stderr as {"ok":false,"error":…,"hint":…} with a suggested next step.7172vault guard: set "allowVaults": ["home"] in config.json to restrict this install73to named vaults — raw addresses are authenticated and mapped to a vault before use.7475vault policy flags (add/update): --max-ttl DAYS|none, --require-password | --allow-unprotected,76--default-password-file FILE | --no-default-password. New sites inherit vault defaults;77required protection and maximum TTL are enforced by the server.7879site feedback: push or policy with --feedback lets visitors of a password-protected site80save notes via /__feedback.js; --request opens a review round and notifies your devices;81--callback URL receives one POST (no note text) when the reviewer submits.8283notification privacy: titles and bodies may appear on a lock screen; never include84passwords, tokens, private URLs, or sensitive personal data.85`;8687/**88 * Runs the nzip command-line interface with the supplied arguments.89 *90 * @param argv Command-line arguments without the executable name. Defaults to91 * the current process arguments.92 * @throws If configuration, validation, or a command handler fails.93 */94export async function main(argv = Deno.args): Promise<void> {95 const args = parseCliArgs(argv);96 setJsonMode(args.json);97 const [command, ...rest] = args._.map(String);9899 if (args.version) {100 console.log(args.json ? JSON.stringify({ version: VERSION }) : `nzip ${VERSION}`);101 return;102 }103104 if (args.help || !command) {105 console.log(HELP);106 return;107 }108109 if (command === "auth") return await cmdAuth(args.server, args.token);110111 if (command === "mcp") {112 if (rest.length) fail("usage: nzip mcp [--root /absolute/path]...");113 // Lazy import keeps SDK initialization out of ordinary CLI commands.114 const { serveMcp } = await import("./mcp/server.ts");115 return await serveMcp(args.root ?? []);116 }117118 if (!["app", "site", "status", "notify", "vault"].includes(command)) {119 fail(`unknown command: ${command}\n\n${HELP}`);120 }121122 const config = await requireConfig();123 const slot = args.slot === undefined ? undefined : parseInt(args.slot, 10);124 const toSeq = args.to === undefined ? undefined : parseInt(args.to, 10);125126 switch (command) {127 case "site":128 return await cmdSiteGroup(config, rest, {129 ttl: args.ttl,130 password: args.password,131 noPassword: args["no-password"],132 newSite: args.new,133 overwrite: args.overwrite,134 yes: args.yes,135 toSeq,136 list: args.list,137 feedback: args.feedback && args["no-feedback"]138 ? fail("choose either --feedback or --no-feedback, not both")139 : args.feedback140 ? true141 : args["no-feedback"]142 ? false143 : undefined,144 request: args.request,145 message: args.message,146 callback: args.callback,147 });148 case "app":149 return await cmdApp(config, rest, {150 framework: args.framework,151 output: args.output,152 buildTask: args["build-task"],153 });154 case "status":155 return await cmdStatus(config);156 case "notify":157 return await cmdNotify(config, rest, {158 title: args.title,159 open: args.open,160 tag: args.tag,161 name: args.name,162 yes: args.yes,163 all: args.all,164 });165 case "vault":166 if (args["require-password"] && args["allow-unprotected"]) {167 fail("choose --require-password or --allow-unprotected");168 }169 return await cmdVault(config, rest, {170 slot,171 newName: args.name,172 description: args.description,173 clearDescription: args["no-description"],174 defaultTtl: args["default-ttl"],175 defaultFor: args["default-for"],176 clearDefaultFor: args["no-default-for"],177 maxTtl: args["max-ttl"],178 requirePassword: args["require-password"]179 ? true180 : args["allow-unprotected"]181 ? false182 : undefined,183 defaultPasswordFile: args["default-password-file"],184 clearDefaultPassword: args["no-default-password"],185 });186 default:187 fail(`unknown command: ${command}\n\n${HELP}`);188 }189}190191if (import.meta.main) {192 main().catch((e) => fail((e as Error).message));193}