#!/usr/bin/env node // qs-scaffold.mjs: scaffold a new Quickshell config from an example. // Cross-platform: no shell, no POSIX assumptions. Pure Node builtins. // // Usage: // qs-scaffold [--from ] // // Default example: 02-per-monitor-bar (canonical per-monitor shape). import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync, writeFileSync, } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); const SKILL_ROOT = join(__dirname, ".."); const EXAMPLES_DIR = join(SKILL_ROOT, "examples"); const DEFAULT_EXAMPLE = "02-per-monitor-bar"; function usage() { process.stderr.write("usage: qs-scaffold [--from ]\n"); process.stderr.write( " example-name: e.g. 01-hello-bar, 07-composite-bar (omit the .qml)\n", ); } function parseArgs(argv) { const out = { name: null, from: DEFAULT_EXAMPLE }; for (let i = 2; i < argv.length; i++) { const a = argv[i]; if (a === "-h" || a === "--help") { usage(); process.exit(0); } if (a === "--from") { const v = argv[++i]; if (!v) { process.stderr.write("qs-scaffold: --from requires a value\n"); process.exit(1); } out.from = v; continue; } if (a.startsWith("--")) { process.stderr.write(`qs-scaffold: unknown flag ${a}\n`); process.exit(1); } if (!out.name) { out.name = a; continue; } process.stderr.write(`qs-scaffold: unexpected positional arg ${a}\n`); process.exit(1); } return out; } function xdgConfigHome() { if (process.env.XDG_CONFIG_HOME) return process.env.XDG_CONFIG_HOME; if (process.env.HOME) return join(process.env.HOME, ".config"); // Windows fallback. if (process.env.APPDATA) return process.env.APPDATA; process.stderr.write( "qs-scaffold: cannot determine config dir (set XDG_CONFIG_HOME or HOME)\n", ); process.exit(1); } const { name, from } = parseArgs(process.argv); if (!name) { usage(); process.exit(1); } function requirePathSegment(label, value) { if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(value)) { process.stderr.write( `qs-scaffold: ${label} must be a single path segment: ${value}\n`, ); process.exit(1); } } requirePathSegment("name", name); requirePathSegment("example name", from); // Validate example dir. const exampleDir = join(EXAMPLES_DIR, from); if (!existsSync(exampleDir) || !statSync(exampleDir).isDirectory()) { process.stderr.write( `qs-scaffold: example not found: ${from} (looked in ${EXAMPLES_DIR})\n`, ); process.exit(1); } const configRoot = join(xdgConfigHome(), "quickshell"); const target = join(configRoot, name); if (existsSync(target)) { process.stderr.write(`qs-scaffold: target already exists: ${target}\n`); process.exit(1); } mkdirSync(target, { recursive: true }); // Copy the example dir's QML + README. Skip hidden / non-source files. for (const entry of readdirSync(exampleDir, { withFileTypes: true })) { if (!entry.isFile()) continue; if (entry.name.startsWith(".")) continue; if (!(entry.name.endsWith(".qml") || entry.name.endsWith(".md"))) continue; copyFileSync(join(exampleDir, entry.name), join(target, entry.name)); } // Touch .qmlls.ini for LSP. writeFileSync(join(target, ".qmlls.ini"), ""); process.stdout.write(`Scaffolded ${target}\n`); process.stdout.write(`Run: qs -c ${name}\n`);