Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@zuroku/cli

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@zuroku/cli - npm Package Compare versions

Comparing version
0.1.0
to
0.1.1
+27
-9
dist/cli.js

@@ -114,2 +114,11 @@ #!/usr/bin/env node

var HTML_MAX_BYTES = 5 * 1024 * 1024;
function rewriteHtmlForRename(html, renameMap) {
let out = html;
for (const { from, to } of renameMap) {
if (from === to) continue;
const escaped = from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
out = out.replace(new RegExp(`(img/)${escaped}(?=["'\\s),])`, "g"), `$1${to}`);
}
return out;
}
function extractHtmlAssetRefs(html) {

@@ -150,3 +159,3 @@ const references = [];

lines.push(`HTML file: ${htmlPath}`);
lines.push(`Compress mode: ${compress ? "ON (sharp WebP, --no-compress to disable)" : "OFF (--no-compress)"}`);
lines.push(`Compress mode: ${compress ? "ON (sharp WebP; HTML img src extensions are rewritten automatically)" : "OFF (--no-compress)"}`);
lines.push("");

@@ -156,4 +165,4 @@ lines.push("zuroku layout requirement:");

lines.push(" - Each <filename> must match one of the provided asset arguments (basename)");
lines.push(" - With sharp compression (default), filename extension changes (e.g. .png -> .webp)");
lines.push(" so the HTML <img src> must use the post-compress filename, OR pass --no-compress.");
lines.push(" - The CLI auto-rewrites `img/foo.png` -> `img/foo.webp` when compress is ON;");
lines.push(" you only need to pre-align extensions when --no-compress is set.");
lines.push("");

@@ -179,6 +188,6 @@ if (missingFromAssets.length) {

lines.push("Suggested fixes (for AI agents to choose):");
lines.push(" A) Rerun with --no-compress to keep PNG/JPEG filenames as-is.");
lines.push(" B) Rewrite HTML <img src> extensions to .webp before publishing:");
lines.push(` sed -i '' -e 's|\\.png"|.webp"|g' -e 's|\\.jpg"|.webp"|g' your.html`);
lines.push(" C) Rename / re-export your asset files so basenames match the HTML refs.");
lines.push(' A) Ensure each <img src="img/foo.ext"> has a matching asset argument (foo.ext).');
lines.push(" B) If file extensions differ (e.g. HTML says foo.png but asset is foo.jpg),");
lines.push(" rename the asset or update the HTML to match.");
lines.push(" C) Use --no-compress if you must keep original PNG/JPEG filenames (skips WebP).");
lines.push("");

@@ -215,2 +224,3 @@ lines.push("To bypass this check (NOT recommended), set ZUROKU_SKIP_PREFLIGHT=1.");

const assets = [];
const renameMap = [];
for (const img of images) {

@@ -223,2 +233,5 @@ const abs = path.resolve(img);

);
if (label !== payload.filename) {
renameMap.push({ from: label, to: payload.filename });
}
assets.push({

@@ -230,4 +243,9 @@ filename: payload.filename,

}
const htmlOriginal = htmlBuf.toString("utf8");
const htmlText = renameMap.length > 0 ? rewriteHtmlForRename(htmlOriginal, renameMap) : htmlOriginal;
if (renameMap.length > 0 && htmlText !== htmlOriginal) {
info(`html: rewrote <img src> references for ${renameMap.length} compressed asset(s)`);
}
const htmlForUpload = htmlText !== htmlOriginal ? Buffer.from(htmlText, "utf8") : htmlBuf;
if (process.env.ZUROKU_SKIP_PREFLIGHT !== "1") {
const htmlText = htmlBuf.toString("utf8");
const { references, expectedFilenames } = extractHtmlAssetRefs(htmlText);

@@ -275,3 +293,3 @@ const providedFilenames = assets.map((a) => a.filename);

const input = {
html: htmlBuf,
html: htmlForUpload,
assets,

@@ -278,0 +296,0 @@ title: opts.title

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/index.ts","../src/commands/login.ts","../src/lib/console.ts","../src/commands/publish.ts","../src/lib/config.ts","../src/commands/list.ts","../src/commands/delete.ts","../src/commands/config.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { registerLoginCommand } from './commands/login.js';\nimport { registerPublishCommand } from './commands/publish.js';\nimport { registerListCommand } from './commands/list.js';\nimport { registerDeleteCommand } from './commands/delete.js';\nimport { registerConfigCommand } from './commands/config.js';\n\n// package.json version is read at build time. Hard-code the constant — keeping\n// it in sync with package.json is part of the release checklist.\nconst VERSION = '0.1.0';\n\nconst program = new Command();\n\nprogram\n .name('zuroku')\n .description('zuroku — AI-driven graphic-recording publish tool')\n .version(VERSION, '-v, --version', 'print version');\n\nregisterLoginCommand(program);\nregisterPublishCommand(program);\nregisterListCommand(program);\nregisterDeleteCommand(program);\nregisterConfigCommand(program);\n\nprogram.showHelpAfterError();\nprogram.parseAsync(process.argv).catch((e: unknown) => {\n // commander itself swallows handler errors via .action; this catches the\n // synchronous parse-failure path. fatal() exits with 1.\n process.stderr.write(`${e instanceof Error ? e.message : String(e)}\\n`);\n process.exit(1);\n});\n","import { Command } from 'commander';\nimport { loadConfig, saveConfig, DEFAULT_BASE_URL, getConfigPath } from '@zuroku/core';\nimport { fatal, info, maskToken, success } from '../lib/console.js';\n\ninterface LoginOpts {\n token: string;\n baseUrl?: string;\n}\n\nexport function registerLoginCommand(parent: Command): void {\n const auth = parent.commands.find((c) => c.name() === 'auth') ?? parent.command('auth')\n .description('Authentication management');\n\n auth\n .command('login')\n .description('Save an API token to ~/.config/zuroku/config.json')\n .requiredOption('-t, --token <token>', 'API token (Bearer key)')\n .option('-u, --base-url <url>', 'Override API base URL', undefined)\n .action(async (opts: LoginOpts) => {\n try {\n const existing = await loadConfig().catch(() => ({\n base_url: DEFAULT_BASE_URL,\n token: '',\n }));\n\n const baseUrl =\n opts.baseUrl && opts.baseUrl.length > 0\n ? opts.baseUrl\n : existing.base_url || DEFAULT_BASE_URL;\n\n await saveConfig({ base_url: baseUrl, token: opts.token });\n\n info(`config: ${getConfigPath()}`);\n info(`base_url: ${baseUrl}`);\n info(`token: ${maskToken(opts.token)}`);\n success('credentials saved (mode 0600)');\n } catch (e) {\n fatal(e);\n }\n });\n}\n","import kleur from 'kleur';\nimport { ZurokuError } from '@zuroku/core';\n\n/**\n * Console helpers — all human-readable progress goes to stderr, leaving\n * stdout reserved for machine-readable output (e.g. the final URL printed by\n * `publish`, or the table from `list`).\n */\n\nexport function info(msg: string): void {\n process.stderr.write(`${kleur.cyan('info')} ${msg}\\n`);\n}\n\nexport function warn(msg: string): void {\n process.stderr.write(`${kleur.yellow('warn')} ${msg}\\n`);\n}\n\nexport function success(msg: string): void {\n process.stderr.write(`${kleur.green('ok')} ${msg}\\n`);\n}\n\n/**\n * Print an error message and exit with code 1.\n *\n * Recognises `ZurokuError` and prefixes the machine-readable code so users\n * can grep / script against it. Suggests `zuroku auth login` for auth errors.\n */\nexport function fatal(err: unknown): never {\n if (err instanceof ZurokuError) {\n const code = kleur.red().bold(err.code);\n const status = err.status > 0 ? kleur.dim(` [HTTP ${err.status}]`) : '';\n process.stderr.write(`${kleur.red('error')} ${code}${status} ${err.message}\\n`);\n\n if (\n err.code === 'CONFIG' ||\n err.code === 'UNAUTHORIZED' ||\n err.code === 'AUTH' ||\n err.status === 401 ||\n err.status === 403\n ) {\n process.stderr.write(\n kleur.dim(\n ' hint: run `zuroku auth login --token <key>` to set credentials\\n',\n ),\n );\n }\n } else if (err instanceof Error) {\n process.stderr.write(`${kleur.red('error')} ${err.message}\\n`);\n } else {\n process.stderr.write(`${kleur.red('error')} ${String(err)}\\n`);\n }\n process.exit(1);\n}\n\n/** Mask a secret token for display (`sk_live_abcd...wxyz`). */\nexport function maskToken(token: string): string {\n if (token.length <= 8) return '*'.repeat(token.length);\n const head = token.slice(0, 6);\n const tail = token.slice(-4);\n return `${head}...${tail}`;\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport {\n compressForUpload,\n passthroughForUpload,\n ZurokuError,\n type AssetUpload,\n} from '@zuroku/core';\nimport { fatal, info, success, warn } from '../lib/console.js';\nimport { loadRuntimeConfig, makeClient } from '../lib/config.js';\n\nconst HTML_MAX_BYTES = 5 * 1024 * 1024; // 5 MiB\n\n/**\n * HTML 内の `<img src=\"...\">` / `<link href=\"...\">` / `<script src=\"...\">` /\n * `srcset` から、この project の asset として参照されている filename (basename) を\n * 抽出する。zuroku は `img/<basename>` を expected layout とするため、\n * `img/foo.png` 系を最優先で検出する。\n *\n * 戻り値: { references: 抽出した raw URL の配列、 expectedFilenames: img/ 直下の basename }\n */\nfunction extractHtmlAssetRefs(html: string): { references: string[]; expectedFilenames: Set<string> } {\n const references: string[] = [];\n const expectedFilenames = new Set<string>();\n const ATTR_RE = /(?:src|href|poster)\\s*=\\s*[\"']([^\"']+)[\"']/gi;\n let m: RegExpExecArray | null;\n while ((m = ATTR_RE.exec(html)) !== null) {\n const url = m[1]!;\n // 外部 URL / data URI / fragment はスキップ\n if (/^(https?:|data:|mailto:|#)/i.test(url)) continue;\n references.push(url);\n // zuroku の R2 layout: img/<filename>\n const imgMatch = url.match(/^(?:\\.\\/)?img\\/([^/?#]+)/);\n if (imgMatch) expectedFilenames.add(imgMatch[1]!);\n }\n // srcset (`a.png 1x, b.png 2x`) も簡易対応\n const SRCSET_RE = /srcset\\s*=\\s*[\"']([^\"']+)[\"']/gi;\n while ((m = SRCSET_RE.exec(html)) !== null) {\n for (const part of m[1]!.split(',')) {\n const url = part.trim().split(/\\s+/)[0];\n if (!url || /^(https?:|data:)/i.test(url)) continue;\n references.push(url);\n const imgMatch = url.match(/^(?:\\.\\/)?img\\/([^/?#]+)/);\n if (imgMatch) expectedFilenames.add(imgMatch[1]!);\n }\n }\n return { references, expectedFilenames };\n}\n\n/**\n * AI agent 向けの詳細エラーメッセージを生成。\n * preflight で HTML と提供 asset の不整合を検知したとき呼ぶ。\n */\nfunction preflightErrorMessage(\n htmlPath: string,\n htmlRefs: string[],\n htmlExpected: Set<string>,\n providedAssetFilenames: string[],\n compress: boolean,\n): string {\n const provided = new Set(providedAssetFilenames);\n const missingFromAssets = [...htmlExpected].filter((f) => !provided.has(f));\n const extraAssets = providedAssetFilenames.filter((f) => !htmlExpected.has(f));\n\n // 非 img/ 参照 (zuroku が期待する layout と違うもの)\n const nonImgRefs = htmlRefs.filter(\n (r) => !/^(?:\\.\\/)?img\\//.test(r) && !/^\\/?(style|css|js)/i.test(r),\n );\n\n const lines: string[] = [];\n lines.push('Preflight check failed: HTML asset references do not match provided files.');\n lines.push('');\n lines.push(`HTML file: ${htmlPath}`);\n lines.push(`Compress mode: ${compress ? 'ON (sharp WebP, --no-compress to disable)' : 'OFF (--no-compress)'}`);\n lines.push('');\n lines.push('zuroku layout requirement:');\n lines.push(' - HTML must reference assets as `img/<filename>` (relative to HTML location)');\n lines.push(' - Each <filename> must match one of the provided asset arguments (basename)');\n lines.push(' - With sharp compression (default), filename extension changes (e.g. .png -> .webp)');\n lines.push(' so the HTML <img src> must use the post-compress filename, OR pass --no-compress.');\n lines.push('');\n\n if (missingFromAssets.length) {\n lines.push('[MISSING] HTML refers to these img/ files but they were not provided as assets:');\n for (const f of missingFromAssets) lines.push(` - img/${f}`);\n lines.push('');\n }\n\n if (extraAssets.length) {\n lines.push('[UNUSED] These asset files were passed but not referenced from HTML <img src=\"img/...\">:');\n for (const f of extraAssets) lines.push(` - ${f}`);\n lines.push('');\n }\n\n if (nonImgRefs.length) {\n lines.push('[WRONG-PATH] HTML refers to these relative URLs that do not start with `img/`:');\n for (const r of nonImgRefs.slice(0, 8)) lines.push(` - ${r}`);\n if (nonImgRefs.length > 8) lines.push(` ... (${nonImgRefs.length - 8} more)`);\n lines.push(' Fix: rewrite to `img/<basename>` (e.g. `images/foo.png` -> `img/foo.png`).');\n lines.push(' sed example: sed -i \\'\\' \\'s|images/|img/|g\\' your.html');\n lines.push('');\n }\n\n lines.push('Suggested fixes (for AI agents to choose):');\n lines.push(' A) Rerun with --no-compress to keep PNG/JPEG filenames as-is.');\n lines.push(' B) Rewrite HTML <img src> extensions to .webp before publishing:');\n lines.push(' sed -i \\'\\' -e \\'s|\\\\.png\"|.webp\"|g\\' -e \\'s|\\\\.jpg\"|.webp\"|g\\' your.html');\n lines.push(' C) Rename / re-export your asset files so basenames match the HTML refs.');\n lines.push('');\n lines.push('To bypass this check (NOT recommended), set ZUROKU_SKIP_PREFLIGHT=1.');\n return lines.join('\\n');\n}\n\ninterface PublishOpts {\n title: string;\n slug?: string;\n description?: string;\n compress: boolean; // commander negates --no-compress -> compress: false\n baseUrl?: string;\n visibility?: string;\n private?: boolean;\n}\n\nexport function registerPublishCommand(parent: Command): void {\n parent\n .command('publish')\n .description('Upload an HTML file (and optional images) and publish a project')\n .argument('<html>', 'Path to the HTML file (<= 5 MiB)')\n .argument('[images...]', 'Image files to upload as assets')\n .requiredOption('-T, --title <title>', 'Project title')\n .option('-s, --slug <slug>', 'Custom slug (default: server-generated)')\n .option('-d, --description <text>', 'Project description')\n .option('--no-compress', 'Skip image compression (upload originals)')\n .option('-u, --base-url <url>', 'Override API base URL')\n .option(\n '-V, --visibility <mode>',\n \"Visibility: private | curator. Server reserves 'public' and rejects it. Falls back to --private then ~/.config/zuroku/config.json then server default (curator) when omitted.\",\n )\n .option('--private', 'Shortcut for --visibility private (wins over --visibility if both are set)')\n .action(async (htmlArg: string, images: string[], opts: PublishOpts) => {\n try {\n const config = await loadRuntimeConfig({ ...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}) });\n\n // ---- HTML ---------------------------------------------------------\n const htmlPath = path.resolve(htmlArg);\n let htmlStat;\n try {\n htmlStat = await fs.stat(htmlPath);\n } catch {\n throw new ZurokuError('NOT_FOUND', 0, `HTML file not found: ${htmlArg}`);\n }\n if (!htmlStat.isFile()) {\n throw new ZurokuError('INVALID_INPUT', 0, `Not a regular file: ${htmlArg}`);\n }\n if (htmlStat.size > HTML_MAX_BYTES) {\n throw new ZurokuError(\n 'PAYLOAD_TOO_LARGE',\n 413,\n `HTML file exceeds 5 MiB limit (${htmlStat.size} bytes)`,\n );\n }\n const htmlBuf = await fs.readFile(htmlPath);\n info(`html: ${path.basename(htmlPath)} (${htmlStat.size} bytes)`);\n\n // ---- Assets -------------------------------------------------------\n const assets: AssetUpload[] = [];\n for (const img of images) {\n const abs = path.resolve(img);\n const label = path.basename(abs);\n const payload = opts.compress\n ? await compressForUpload(abs)\n : await passthroughForUpload(abs);\n info(\n `asset: ${label} -> ${payload.filename} (${payload.buffer.byteLength} bytes, ${payload.contentType})`,\n );\n assets.push({\n filename: payload.filename,\n buffer: payload.buffer,\n contentType: payload.contentType,\n });\n }\n\n // ---- Preflight: HTML asset refs vs provided assets ----------------\n if (process.env.ZUROKU_SKIP_PREFLIGHT !== '1') {\n const htmlText = htmlBuf.toString('utf8');\n const { references, expectedFilenames } = extractHtmlAssetRefs(htmlText);\n const providedFilenames = assets.map((a) => a.filename);\n const provided = new Set(providedFilenames);\n const missing = [...expectedFilenames].filter((f) => !provided.has(f));\n const nonImg = references.filter(\n (r) => !/^(?:\\.\\/)?img\\//.test(r) && !/^\\/?(style|css|js|favicon)/i.test(r) && !/^[a-z]+:/i.test(r),\n );\n // 致命的とみなすケース: img/ 参照 (= zuroku layout) があるのに asset と一致しない\n // または HTML に img タグがあるが img/ prefix を一切使っていない (path mismatch)\n const hasImgTag = /<img\\b[^>]*src=/i.test(htmlText);\n const layoutBroken = hasImgTag && expectedFilenames.size === 0 && nonImg.length > 0;\n if (missing.length > 0 || layoutBroken) {\n throw new ZurokuError(\n 'INVALID_INPUT',\n 0,\n preflightErrorMessage(htmlPath, references, expectedFilenames, providedFilenames, opts.compress),\n );\n }\n }\n\n // ---- Visibility preflight -----------------------------------------\n // -V/--visibility と --private は両立可。--private が指定されたら 'private' に強制。\n // どちらの flag も無いときは config.default_visibility を fallback として採用する\n // (`zuroku config set default-visibility ...` で設定)。\n let visibility: 'private' | 'curator' | 'public' | undefined;\n if (opts.visibility !== undefined) {\n if (\n opts.visibility !== 'private' &&\n opts.visibility !== 'curator' &&\n opts.visibility !== 'public'\n ) {\n throw new ZurokuError(\n 'INVALID_INPUT',\n 0,\n `--visibility must be 'private' | 'curator' | 'public' (got '${opts.visibility}')`,\n );\n }\n visibility = opts.visibility;\n }\n if (opts.private) {\n // 両方指定された場合は --private が勝つ。silent override は事故の元なので warn 級で\n // ユーザに明示 (info の海に埋もれないように)。\n if (visibility !== undefined && visibility !== 'private') {\n warn(`--private overrides --visibility ${visibility}`);\n }\n visibility = 'private';\n }\n if (visibility === undefined && config.default_visibility !== undefined) {\n visibility = config.default_visibility;\n info(`visibility: ${visibility} (from config default_visibility)`);\n }\n\n // ---- Publish ------------------------------------------------------\n const client = makeClient(config);\n const visLabel = visibility ? ` (visibility=${visibility})` : '';\n info(`publishing as \"${opts.title}\"${opts.slug ? ` (slug=${opts.slug})` : ''}${visLabel}...`);\n\n const input: Parameters<typeof client.publishProject>[0] = {\n html: htmlBuf,\n assets,\n title: opts.title,\n };\n if (opts.slug !== undefined) input.slug = opts.slug;\n if (opts.description !== undefined) input.description = opts.description;\n if (visibility !== undefined) input.visibility = visibility;\n\n const res = await client.publishProject(input);\n\n success(`published: slug=${res.slug} id=${res.id}`);\n if (visibility === 'private') {\n info('(private — only you can view this project until you change visibility)');\n // app base URL は config.base_url。app.* → settings/projects はそのまま app の方。\n const appBase = config.base_url.replace(/\\/+$/, '');\n info(`manage visibility: ${appBase}/settings/projects`);\n }\n // Final stdout line: bare URL so tools (Claude Code etc.) can grab it.\n process.stdout.write(`${res.url}\\n`);\n } catch (e) {\n fatal(e);\n }\n });\n}\n","import { loadConfig as loadCoreConfig, ZurokuClient, ZurokuError } from '@zuroku/core';\nimport type { ZurokuConfig } from '@zuroku/core';\n\nexport interface RuntimeConfigOpts {\n /** Override base URL (e.g. from --base-url). */\n baseUrl?: string;\n /** When true, skip the empty-token guard (used by `auth login`). */\n allowMissingToken?: boolean;\n}\n\n/**\n * Load `~/.config/zuroku/config.json`, optionally overlaying CLI-supplied\n * `--base-url`. Throws `ZurokuError('CONFIG')` when the token is missing\n * (unless `allowMissingToken` is set), so the caller's `fatal()` can render\n * the standard `auth login` hint.\n */\nexport async function loadRuntimeConfig(\n opts: RuntimeConfigOpts = {},\n): Promise<ZurokuConfig> {\n let config: ZurokuConfig;\n try {\n config = await loadCoreConfig();\n } catch (e) {\n throw new ZurokuError(\n 'CONFIG',\n 0,\n `Failed to read config: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n\n if (opts.baseUrl && opts.baseUrl.length > 0) {\n config = { ...config, base_url: opts.baseUrl };\n }\n\n if (!opts.allowMissingToken && config.token === '') {\n throw new ZurokuError(\n 'CONFIG',\n 0,\n 'No API token configured. Run `zuroku auth login --token <key>` first.',\n );\n }\n\n return config;\n}\n\n/** Build a `ZurokuClient` from a fully-resolved runtime config. */\nexport function makeClient(config: ZurokuConfig): ZurokuClient {\n return new ZurokuClient({ baseUrl: config.base_url, token: config.token });\n}\n","import { Command } from 'commander';\nimport type { ProjectSummary } from '@zuroku/core';\nimport { fatal } from '../lib/console.js';\nimport { loadRuntimeConfig, makeClient } from '../lib/config.js';\n\ninterface ListOpts {\n baseUrl?: string;\n}\n\nfunction pad(s: string, n: number): string {\n if (s.length >= n) return s;\n return s + ' '.repeat(n - s.length);\n}\n\nfunction projectUrl(baseUrl: string, p: ProjectSummary): string {\n // Canonical URL: content origin の /p/<slug>。\n // baseUrl は app origin (Bearer 認証先) を指すので、`app.` → `content.` に置換する。\n // 置換できないカスタム base (localhost 等) はそのまま fallback。\n const root = baseUrl.replace(/\\/+$/, '');\n const contentRoot = /^https?:\\/\\/app\\./.test(root)\n ? root.replace(/^(https?:\\/\\/)app\\./, '$1content.')\n : root;\n return `${contentRoot}/p/${p.slug}`;\n}\n\nfunction fmtDate(unixSec: number | null | undefined): string {\n if (!unixSec) return '-';\n // server は unix sec で返す (plan §3.2 Response 例)。\n const d = new Date(unixSec * 1000);\n return d.toISOString().replace('T', ' ').replace(/\\.\\d+Z$/, '');\n}\n\nexport function registerListCommand(parent: Command): void {\n parent\n .command('list')\n .description('List your published projects')\n .option('-u, --base-url <url>', 'Override API base URL')\n .action(async (opts: ListOpts) => {\n try {\n const config = await loadRuntimeConfig({ ...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}) });\n const client = makeClient(config);\n const projects = await client.list();\n\n if (projects.length === 0) {\n process.stdout.write('(no projects)\\n');\n return;\n }\n\n // visibility は server 側で optional (旧 server 互換)。\n // 未返却 (旧 server) は '-' に倒して、private を curator と誤表示しないようにする。\n function visLabel(v: ProjectSummary['visibility']): string {\n if (v === 'private') return 'priv';\n if (v === 'curator') return 'cura';\n if (v === 'public') return 'pub';\n return '-';\n }\n\n const rows = projects.map((p) => ({\n slug: p.slug,\n title: p.title,\n vis: visLabel(p.visibility),\n published_at: fmtDate(p.published_at),\n url: projectUrl(config.base_url, p),\n }));\n\n const headers = {\n slug: 'slug',\n title: 'title',\n vis: 'vis',\n published_at: 'published_at',\n url: 'url',\n };\n const widths = {\n slug: Math.max(headers.slug.length, ...rows.map((r) => r.slug.length)),\n title: Math.max(headers.title.length, ...rows.map((r) => r.title.length)),\n vis: Math.max(headers.vis.length, ...rows.map((r) => r.vis.length)),\n published_at: Math.max(\n headers.published_at.length,\n ...rows.map((r) => r.published_at.length),\n ),\n url: Math.max(headers.url.length, ...rows.map((r) => r.url.length)),\n };\n\n const line = (r: typeof headers): string =>\n [\n pad(r.slug, widths.slug),\n pad(r.title, widths.title),\n pad(r.vis, widths.vis),\n pad(r.published_at, widths.published_at),\n r.url,\n ].join(' ');\n\n process.stdout.write(`${line(headers)}\\n`);\n process.stdout.write(\n `${[\n '-'.repeat(widths.slug),\n '-'.repeat(widths.title),\n '-'.repeat(widths.vis),\n '-'.repeat(widths.published_at),\n '-'.repeat(widths.url),\n ].join(' ')}\\n`,\n );\n for (const r of rows) {\n process.stdout.write(`${line(r)}\\n`);\n }\n } catch (e) {\n fatal(e);\n }\n });\n}\n","import readline from 'node:readline';\nimport { Command } from 'commander';\nimport { fatal, info, success, warn } from '../lib/console.js';\nimport { loadRuntimeConfig, makeClient } from '../lib/config.js';\n\ninterface DeleteOpts {\n yes?: boolean;\n baseUrl?: string;\n}\n\nfunction confirm(prompt: string): Promise<boolean> {\n return new Promise((resolve) => {\n const rl = readline.createInterface({ input: process.stdin, output: process.stderr });\n let settled = false;\n const finish = (ok: boolean) => {\n if (settled) return;\n settled = true;\n rl.close();\n resolve(ok);\n };\n rl.on('SIGINT', () => {\n rl.close();\n process.exit(130);\n });\n rl.question(prompt, (ans) => {\n finish(/^y(es)?$/i.test(ans.trim()));\n });\n });\n}\n\nexport function registerDeleteCommand(parent: Command): void {\n parent\n .command('delete')\n .description('Delete a project by slug or id')\n .argument('<slug-or-id>', 'Project slug or id')\n .option('-y, --yes', 'Skip confirmation prompt')\n .option('-u, --base-url <url>', 'Override API base URL')\n .action(async (target: string, opts: DeleteOpts) => {\n try {\n const config = await loadRuntimeConfig({ ...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}) });\n\n if (!opts.yes) {\n const ok = await confirm(`delete project \"${target}\"? [y/N] `);\n if (!ok) {\n warn('cancelled');\n process.exit(0);\n }\n }\n\n const client = makeClient(config);\n info(`deleting ${target}...`);\n await client.delete(target);\n success(`deleted: ${target}`);\n } catch (e) {\n fatal(e);\n }\n });\n}\n","import { Command } from 'commander';\nimport {\n getConfigPath,\n loadConfig,\n saveConfig,\n ZurokuError,\n type DefaultVisibility,\n type ZurokuConfig,\n} from '@zuroku/core';\nimport { fatal, info, success } from '../lib/console.js';\n\ntype ConfigKey = 'default-visibility';\n\nconst KEYS: readonly ConfigKey[] = ['default-visibility'] as const;\n\nfunction parseKey(raw: string): ConfigKey {\n if ((KEYS as readonly string[]).includes(raw)) return raw as ConfigKey;\n throw new ZurokuError(\n 'INVALID_INPUT',\n 0,\n `unknown config key '${raw}'. valid keys: ${KEYS.join(', ')}`,\n );\n}\n\nexport function parseVisibility(raw: string): DefaultVisibility {\n if (raw === 'private' || raw === 'curator') return raw;\n if (raw === 'public') {\n // 'public' は server 予約語で reject される。default 保存は許可しない\n // (うっかり default 公開化の事故防止)。\n throw new ZurokuError(\n 'INVALID_INPUT',\n 0,\n \"'public' is reserved by the server and cannot be stored as default-visibility.\",\n );\n }\n throw new ZurokuError(\n 'INVALID_INPUT',\n 0,\n `--default-visibility must be 'private' or 'curator' (got '${raw}')`,\n );\n}\n\n/**\n * config を読み込む。core の `loadConfig` は ENOENT を default 値で吸収するので\n * ここで投げてくる例外は \"ファイルはあるが parse 不可\" or \"permission denied\" だけ。\n * そのまま `fatal()` に渡し、malformed JSON を `set` で黙って上書きしないようにする\n * (data loss 防止)。\n */\nasync function readConfigOrFatal(): Promise<ZurokuConfig> {\n try {\n return await loadConfig();\n } catch (e) {\n throw new ZurokuError(\n 'CONFIG',\n 0,\n `Failed to read config (refusing to overwrite a corrupt file): ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n}\n\nexport function registerConfigCommand(parent: Command): void {\n const config = parent\n .command('config')\n .description('Manage local CLI config (~/.config/zuroku/config.json)');\n\n config\n .command('get')\n .description('Print one config key, or all keys when no key is given')\n .argument('[key]', `Config key (${KEYS.join(' | ')})`)\n .action(async (rawKey: string | undefined) => {\n try {\n const cfg = await readConfigOrFatal();\n if (rawKey === undefined) {\n info(`path: ${getConfigPath()}`);\n process.stdout.write(`default-visibility=${cfg.default_visibility ?? '(unset, server default: curator)'}\\n`);\n return;\n }\n const key = parseKey(rawKey);\n if (key === 'default-visibility') {\n process.stdout.write(`${cfg.default_visibility ?? ''}\\n`);\n }\n } catch (e) {\n fatal(e);\n }\n });\n\n config\n .command('set')\n .description('Set a config key')\n .argument('<key>', `Config key (${KEYS.join(' | ')})`)\n .argument('<value>', \"Value (for default-visibility: 'private' | 'curator')\")\n .action(async (rawKey: string, rawValue: string) => {\n try {\n const key = parseKey(rawKey);\n const existing = await readConfigOrFatal();\n if (key === 'default-visibility') {\n const v = parseVisibility(rawValue);\n const next: ZurokuConfig = { ...existing, default_visibility: v };\n await saveConfig(next);\n success(`default-visibility = ${v}`);\n info(`config: ${getConfigPath()}`);\n }\n } catch (e) {\n fatal(e);\n }\n });\n\n config\n .command('unset')\n .description('Remove a config key (revert to server default)')\n .argument('<key>', `Config key (${KEYS.join(' | ')})`)\n .action(async (rawKey: string) => {\n try {\n const key = parseKey(rawKey);\n const existing = await readConfigOrFatal();\n if (key === 'default-visibility') {\n const next: ZurokuConfig = { ...existing };\n delete next.default_visibility;\n await saveConfig(next);\n success('default-visibility cleared (server default: curator)');\n }\n } catch (e) {\n fatal(e);\n }\n });\n}\n"],"mappings":"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","names":["ZurokuError","ZurokuError","ZurokuError","visLabel","getConfigPath","loadConfig","saveConfig","ZurokuError","ZurokuError","loadConfig","getConfigPath","saveConfig"]}
{"version":3,"sources":["../src/index.ts","../src/commands/login.ts","../src/lib/console.ts","../src/commands/publish.ts","../src/lib/config.ts","../src/commands/list.ts","../src/commands/delete.ts","../src/commands/config.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { registerLoginCommand } from './commands/login.js';\nimport { registerPublishCommand } from './commands/publish.js';\nimport { registerListCommand } from './commands/list.js';\nimport { registerDeleteCommand } from './commands/delete.js';\nimport { registerConfigCommand } from './commands/config.js';\n\n// package.json version is read at build time. Hard-code the constant — keeping\n// it in sync with package.json is part of the release checklist.\nconst VERSION = '0.1.0';\n\nconst program = new Command();\n\nprogram\n .name('zuroku')\n .description('zuroku — AI-driven graphic-recording publish tool')\n .version(VERSION, '-v, --version', 'print version');\n\nregisterLoginCommand(program);\nregisterPublishCommand(program);\nregisterListCommand(program);\nregisterDeleteCommand(program);\nregisterConfigCommand(program);\n\nprogram.showHelpAfterError();\nprogram.parseAsync(process.argv).catch((e: unknown) => {\n // commander itself swallows handler errors via .action; this catches the\n // synchronous parse-failure path. fatal() exits with 1.\n process.stderr.write(`${e instanceof Error ? e.message : String(e)}\\n`);\n process.exit(1);\n});\n","import { Command } from 'commander';\nimport { loadConfig, saveConfig, DEFAULT_BASE_URL, getConfigPath } from '@zuroku/core';\nimport { fatal, info, maskToken, success } from '../lib/console.js';\n\ninterface LoginOpts {\n token: string;\n baseUrl?: string;\n}\n\nexport function registerLoginCommand(parent: Command): void {\n const auth = parent.commands.find((c) => c.name() === 'auth') ?? parent.command('auth')\n .description('Authentication management');\n\n auth\n .command('login')\n .description('Save an API token to ~/.config/zuroku/config.json')\n .requiredOption('-t, --token <token>', 'API token (Bearer key)')\n .option('-u, --base-url <url>', 'Override API base URL', undefined)\n .action(async (opts: LoginOpts) => {\n try {\n const existing = await loadConfig().catch(() => ({\n base_url: DEFAULT_BASE_URL,\n token: '',\n }));\n\n const baseUrl =\n opts.baseUrl && opts.baseUrl.length > 0\n ? opts.baseUrl\n : existing.base_url || DEFAULT_BASE_URL;\n\n await saveConfig({ base_url: baseUrl, token: opts.token });\n\n info(`config: ${getConfigPath()}`);\n info(`base_url: ${baseUrl}`);\n info(`token: ${maskToken(opts.token)}`);\n success('credentials saved (mode 0600)');\n } catch (e) {\n fatal(e);\n }\n });\n}\n","import kleur from 'kleur';\nimport { ZurokuError } from '@zuroku/core';\n\n/**\n * Console helpers — all human-readable progress goes to stderr, leaving\n * stdout reserved for machine-readable output (e.g. the final URL printed by\n * `publish`, or the table from `list`).\n */\n\nexport function info(msg: string): void {\n process.stderr.write(`${kleur.cyan('info')} ${msg}\\n`);\n}\n\nexport function warn(msg: string): void {\n process.stderr.write(`${kleur.yellow('warn')} ${msg}\\n`);\n}\n\nexport function success(msg: string): void {\n process.stderr.write(`${kleur.green('ok')} ${msg}\\n`);\n}\n\n/**\n * Print an error message and exit with code 1.\n *\n * Recognises `ZurokuError` and prefixes the machine-readable code so users\n * can grep / script against it. Suggests `zuroku auth login` for auth errors.\n */\nexport function fatal(err: unknown): never {\n if (err instanceof ZurokuError) {\n const code = kleur.red().bold(err.code);\n const status = err.status > 0 ? kleur.dim(` [HTTP ${err.status}]`) : '';\n process.stderr.write(`${kleur.red('error')} ${code}${status} ${err.message}\\n`);\n\n if (\n err.code === 'CONFIG' ||\n err.code === 'UNAUTHORIZED' ||\n err.code === 'AUTH' ||\n err.status === 401 ||\n err.status === 403\n ) {\n process.stderr.write(\n kleur.dim(\n ' hint: run `zuroku auth login --token <key>` to set credentials\\n',\n ),\n );\n }\n } else if (err instanceof Error) {\n process.stderr.write(`${kleur.red('error')} ${err.message}\\n`);\n } else {\n process.stderr.write(`${kleur.red('error')} ${String(err)}\\n`);\n }\n process.exit(1);\n}\n\n/** Mask a secret token for display (`sk_live_abcd...wxyz`). */\nexport function maskToken(token: string): string {\n if (token.length <= 8) return '*'.repeat(token.length);\n const head = token.slice(0, 6);\n const tail = token.slice(-4);\n return `${head}...${tail}`;\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport {\n compressForUpload,\n passthroughForUpload,\n ZurokuError,\n type AssetUpload,\n} from '@zuroku/core';\nimport { fatal, info, success, warn } from '../lib/console.js';\nimport { loadRuntimeConfig, makeClient } from '../lib/config.js';\n\nconst HTML_MAX_BYTES = 5 * 1024 * 1024; // 5 MiB\n\n/**\n * 圧縮で rename された asset (foo.png → foo.webp) に合わせて、HTML 内の\n * `<img src=\"img/foo.png\">` 系の参照を `img/foo.webp` に置換する。\n *\n * - `img/` prefix を必須にすることで CSS/JS の同名 path を巻き込まない\n * (zuroku の R2 layout 規約)。\n * - 部分一致 (`foo.png.bak`) を避けるため境界 lookahead を入れる:\n * 引用符 / 空白 / `)` / `,` のいずれかが直後に来る場合のみ rewrite。\n * - renameMap が空のときは byte 一致で素通し (--no-compress 等)。\n */\nexport function rewriteHtmlForRename(\n html: string,\n renameMap: ReadonlyArray<{ from: string; to: string }>,\n): string {\n let out = html;\n for (const { from, to } of renameMap) {\n if (from === to) continue;\n const escaped = from.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n out = out.replace(new RegExp(`(img/)${escaped}(?=[\"'\\\\s),])`, 'g'), `$1${to}`);\n }\n return out;\n}\n\n/**\n * HTML 内の `<img src=\"...\">` / `<link href=\"...\">` / `<script src=\"...\">` /\n * `srcset` から、この project の asset として参照されている filename (basename) を\n * 抽出する。zuroku は `img/<basename>` を expected layout とするため、\n * `img/foo.png` 系を最優先で検出する。\n *\n * 戻り値: { references: 抽出した raw URL の配列、 expectedFilenames: img/ 直下の basename }\n */\nfunction extractHtmlAssetRefs(html: string): { references: string[]; expectedFilenames: Set<string> } {\n const references: string[] = [];\n const expectedFilenames = new Set<string>();\n const ATTR_RE = /(?:src|href|poster)\\s*=\\s*[\"']([^\"']+)[\"']/gi;\n let m: RegExpExecArray | null;\n while ((m = ATTR_RE.exec(html)) !== null) {\n const url = m[1]!;\n // 外部 URL / data URI / fragment はスキップ\n if (/^(https?:|data:|mailto:|#)/i.test(url)) continue;\n references.push(url);\n // zuroku の R2 layout: img/<filename>\n const imgMatch = url.match(/^(?:\\.\\/)?img\\/([^/?#]+)/);\n if (imgMatch) expectedFilenames.add(imgMatch[1]!);\n }\n // srcset (`a.png 1x, b.png 2x`) も簡易対応\n const SRCSET_RE = /srcset\\s*=\\s*[\"']([^\"']+)[\"']/gi;\n while ((m = SRCSET_RE.exec(html)) !== null) {\n for (const part of m[1]!.split(',')) {\n const url = part.trim().split(/\\s+/)[0];\n if (!url || /^(https?:|data:)/i.test(url)) continue;\n references.push(url);\n const imgMatch = url.match(/^(?:\\.\\/)?img\\/([^/?#]+)/);\n if (imgMatch) expectedFilenames.add(imgMatch[1]!);\n }\n }\n return { references, expectedFilenames };\n}\n\n/**\n * AI agent 向けの詳細エラーメッセージを生成。\n * preflight で HTML と提供 asset の不整合を検知したとき呼ぶ。\n */\nfunction preflightErrorMessage(\n htmlPath: string,\n htmlRefs: string[],\n htmlExpected: Set<string>,\n providedAssetFilenames: string[],\n compress: boolean,\n): string {\n const provided = new Set(providedAssetFilenames);\n const missingFromAssets = [...htmlExpected].filter((f) => !provided.has(f));\n const extraAssets = providedAssetFilenames.filter((f) => !htmlExpected.has(f));\n\n // 非 img/ 参照 (zuroku が期待する layout と違うもの)\n const nonImgRefs = htmlRefs.filter(\n (r) => !/^(?:\\.\\/)?img\\//.test(r) && !/^\\/?(style|css|js)/i.test(r),\n );\n\n const lines: string[] = [];\n lines.push('Preflight check failed: HTML asset references do not match provided files.');\n lines.push('');\n lines.push(`HTML file: ${htmlPath}`);\n lines.push(`Compress mode: ${compress ? 'ON (sharp WebP; HTML img src extensions are rewritten automatically)' : 'OFF (--no-compress)'}`);\n lines.push('');\n lines.push('zuroku layout requirement:');\n lines.push(' - HTML must reference assets as `img/<filename>` (relative to HTML location)');\n lines.push(' - Each <filename> must match one of the provided asset arguments (basename)');\n lines.push(' - The CLI auto-rewrites `img/foo.png` -> `img/foo.webp` when compress is ON;');\n lines.push(' you only need to pre-align extensions when --no-compress is set.');\n lines.push('');\n\n if (missingFromAssets.length) {\n lines.push('[MISSING] HTML refers to these img/ files but they were not provided as assets:');\n for (const f of missingFromAssets) lines.push(` - img/${f}`);\n lines.push('');\n }\n\n if (extraAssets.length) {\n lines.push('[UNUSED] These asset files were passed but not referenced from HTML <img src=\"img/...\">:');\n for (const f of extraAssets) lines.push(` - ${f}`);\n lines.push('');\n }\n\n if (nonImgRefs.length) {\n lines.push('[WRONG-PATH] HTML refers to these relative URLs that do not start with `img/`:');\n for (const r of nonImgRefs.slice(0, 8)) lines.push(` - ${r}`);\n if (nonImgRefs.length > 8) lines.push(` ... (${nonImgRefs.length - 8} more)`);\n lines.push(' Fix: rewrite to `img/<basename>` (e.g. `images/foo.png` -> `img/foo.png`).');\n lines.push(' sed example: sed -i \\'\\' \\'s|images/|img/|g\\' your.html');\n lines.push('');\n }\n\n lines.push('Suggested fixes (for AI agents to choose):');\n lines.push(' A) Ensure each <img src=\"img/foo.ext\"> has a matching asset argument (foo.ext).');\n lines.push(' B) If file extensions differ (e.g. HTML says foo.png but asset is foo.jpg),');\n lines.push(' rename the asset or update the HTML to match.');\n lines.push(' C) Use --no-compress if you must keep original PNG/JPEG filenames (skips WebP).');\n lines.push('');\n lines.push('To bypass this check (NOT recommended), set ZUROKU_SKIP_PREFLIGHT=1.');\n return lines.join('\\n');\n}\n\ninterface PublishOpts {\n title: string;\n slug?: string;\n description?: string;\n compress: boolean; // commander negates --no-compress -> compress: false\n baseUrl?: string;\n visibility?: string;\n private?: boolean;\n}\n\nexport function registerPublishCommand(parent: Command): void {\n parent\n .command('publish')\n .description('Upload an HTML file (and optional images) and publish a project')\n .argument('<html>', 'Path to the HTML file (<= 5 MiB)')\n .argument('[images...]', 'Image files to upload as assets')\n .requiredOption('-T, --title <title>', 'Project title')\n .option('-s, --slug <slug>', 'Custom slug (default: server-generated)')\n .option('-d, --description <text>', 'Project description')\n .option('--no-compress', 'Skip image compression (upload originals)')\n .option('-u, --base-url <url>', 'Override API base URL')\n .option(\n '-V, --visibility <mode>',\n \"Visibility: private | curator. Server reserves 'public' and rejects it. Falls back to --private then ~/.config/zuroku/config.json then server default (curator) when omitted.\",\n )\n .option('--private', 'Shortcut for --visibility private (wins over --visibility if both are set)')\n .action(async (htmlArg: string, images: string[], opts: PublishOpts) => {\n try {\n const config = await loadRuntimeConfig({ ...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}) });\n\n // ---- HTML ---------------------------------------------------------\n const htmlPath = path.resolve(htmlArg);\n let htmlStat;\n try {\n htmlStat = await fs.stat(htmlPath);\n } catch {\n throw new ZurokuError('NOT_FOUND', 0, `HTML file not found: ${htmlArg}`);\n }\n if (!htmlStat.isFile()) {\n throw new ZurokuError('INVALID_INPUT', 0, `Not a regular file: ${htmlArg}`);\n }\n if (htmlStat.size > HTML_MAX_BYTES) {\n throw new ZurokuError(\n 'PAYLOAD_TOO_LARGE',\n 413,\n `HTML file exceeds 5 MiB limit (${htmlStat.size} bytes)`,\n );\n }\n const htmlBuf = await fs.readFile(htmlPath);\n info(`html: ${path.basename(htmlPath)} (${htmlStat.size} bytes)`);\n\n // ---- Assets -------------------------------------------------------\n // 圧縮時 (default) は .png/.jpg/.jpeg の filename が .webp に rename される。\n // HTML 側の <img src=\"img/foo.png\"> をそのままにすると preflight が\n // [MISSING] で fail するので、rename map を取って後段で HTML を自動 rewrite する。\n const assets: AssetUpload[] = [];\n const renameMap: Array<{ from: string; to: string }> = [];\n for (const img of images) {\n const abs = path.resolve(img);\n const label = path.basename(abs);\n const payload = opts.compress\n ? await compressForUpload(abs)\n : await passthroughForUpload(abs);\n info(\n `asset: ${label} -> ${payload.filename} (${payload.buffer.byteLength} bytes, ${payload.contentType})`,\n );\n if (label !== payload.filename) {\n renameMap.push({ from: label, to: payload.filename });\n }\n assets.push({\n filename: payload.filename,\n buffer: payload.buffer,\n contentType: payload.contentType,\n });\n }\n\n // ---- HTML auto-rewrite for compressed assets ----------------------\n // --no-compress 時は renameMap が空なので no-op (byte 一致)。\n const htmlOriginal = htmlBuf.toString('utf8');\n const htmlText = renameMap.length > 0 ? rewriteHtmlForRename(htmlOriginal, renameMap) : htmlOriginal;\n if (renameMap.length > 0 && htmlText !== htmlOriginal) {\n info(`html: rewrote <img src> references for ${renameMap.length} compressed asset(s)`);\n }\n const htmlForUpload = htmlText !== htmlOriginal ? Buffer.from(htmlText, 'utf8') : htmlBuf;\n\n // ---- Preflight: HTML asset refs vs provided assets ----------------\n if (process.env.ZUROKU_SKIP_PREFLIGHT !== '1') {\n const { references, expectedFilenames } = extractHtmlAssetRefs(htmlText);\n const providedFilenames = assets.map((a) => a.filename);\n const provided = new Set(providedFilenames);\n const missing = [...expectedFilenames].filter((f) => !provided.has(f));\n const nonImg = references.filter(\n (r) => !/^(?:\\.\\/)?img\\//.test(r) && !/^\\/?(style|css|js|favicon)/i.test(r) && !/^[a-z]+:/i.test(r),\n );\n // 致命的とみなすケース: img/ 参照 (= zuroku layout) があるのに asset と一致しない\n // または HTML に img タグがあるが img/ prefix を一切使っていない (path mismatch)\n const hasImgTag = /<img\\b[^>]*src=/i.test(htmlText);\n const layoutBroken = hasImgTag && expectedFilenames.size === 0 && nonImg.length > 0;\n if (missing.length > 0 || layoutBroken) {\n throw new ZurokuError(\n 'INVALID_INPUT',\n 0,\n preflightErrorMessage(htmlPath, references, expectedFilenames, providedFilenames, opts.compress),\n );\n }\n }\n\n // ---- Visibility preflight -----------------------------------------\n // -V/--visibility と --private は両立可。--private が指定されたら 'private' に強制。\n // どちらの flag も無いときは config.default_visibility を fallback として採用する\n // (`zuroku config set default-visibility ...` で設定)。\n let visibility: 'private' | 'curator' | 'public' | undefined;\n if (opts.visibility !== undefined) {\n if (\n opts.visibility !== 'private' &&\n opts.visibility !== 'curator' &&\n opts.visibility !== 'public'\n ) {\n throw new ZurokuError(\n 'INVALID_INPUT',\n 0,\n `--visibility must be 'private' | 'curator' | 'public' (got '${opts.visibility}')`,\n );\n }\n visibility = opts.visibility;\n }\n if (opts.private) {\n // 両方指定された場合は --private が勝つ。silent override は事故の元なので warn 級で\n // ユーザに明示 (info の海に埋もれないように)。\n if (visibility !== undefined && visibility !== 'private') {\n warn(`--private overrides --visibility ${visibility}`);\n }\n visibility = 'private';\n }\n if (visibility === undefined && config.default_visibility !== undefined) {\n visibility = config.default_visibility;\n info(`visibility: ${visibility} (from config default_visibility)`);\n }\n\n // ---- Publish ------------------------------------------------------\n const client = makeClient(config);\n const visLabel = visibility ? ` (visibility=${visibility})` : '';\n info(`publishing as \"${opts.title}\"${opts.slug ? ` (slug=${opts.slug})` : ''}${visLabel}...`);\n\n const input: Parameters<typeof client.publishProject>[0] = {\n html: htmlForUpload,\n assets,\n title: opts.title,\n };\n if (opts.slug !== undefined) input.slug = opts.slug;\n if (opts.description !== undefined) input.description = opts.description;\n if (visibility !== undefined) input.visibility = visibility;\n\n const res = await client.publishProject(input);\n\n success(`published: slug=${res.slug} id=${res.id}`);\n if (visibility === 'private') {\n info('(private — only you can view this project until you change visibility)');\n // app base URL は config.base_url。app.* → settings/projects はそのまま app の方。\n const appBase = config.base_url.replace(/\\/+$/, '');\n info(`manage visibility: ${appBase}/settings/projects`);\n }\n // Final stdout line: bare URL so tools (Claude Code etc.) can grab it.\n process.stdout.write(`${res.url}\\n`);\n } catch (e) {\n fatal(e);\n }\n });\n}\n","import { loadConfig as loadCoreConfig, ZurokuClient, ZurokuError } from '@zuroku/core';\nimport type { ZurokuConfig } from '@zuroku/core';\n\nexport interface RuntimeConfigOpts {\n /** Override base URL (e.g. from --base-url). */\n baseUrl?: string;\n /** When true, skip the empty-token guard (used by `auth login`). */\n allowMissingToken?: boolean;\n}\n\n/**\n * Load `~/.config/zuroku/config.json`, optionally overlaying CLI-supplied\n * `--base-url`. Throws `ZurokuError('CONFIG')` when the token is missing\n * (unless `allowMissingToken` is set), so the caller's `fatal()` can render\n * the standard `auth login` hint.\n */\nexport async function loadRuntimeConfig(\n opts: RuntimeConfigOpts = {},\n): Promise<ZurokuConfig> {\n let config: ZurokuConfig;\n try {\n config = await loadCoreConfig();\n } catch (e) {\n throw new ZurokuError(\n 'CONFIG',\n 0,\n `Failed to read config: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n\n if (opts.baseUrl && opts.baseUrl.length > 0) {\n config = { ...config, base_url: opts.baseUrl };\n }\n\n if (!opts.allowMissingToken && config.token === '') {\n throw new ZurokuError(\n 'CONFIG',\n 0,\n 'No API token configured. Run `zuroku auth login --token <key>` first.',\n );\n }\n\n return config;\n}\n\n/** Build a `ZurokuClient` from a fully-resolved runtime config. */\nexport function makeClient(config: ZurokuConfig): ZurokuClient {\n return new ZurokuClient({ baseUrl: config.base_url, token: config.token });\n}\n","import { Command } from 'commander';\nimport type { ProjectSummary } from '@zuroku/core';\nimport { fatal } from '../lib/console.js';\nimport { loadRuntimeConfig, makeClient } from '../lib/config.js';\n\ninterface ListOpts {\n baseUrl?: string;\n}\n\nfunction pad(s: string, n: number): string {\n if (s.length >= n) return s;\n return s + ' '.repeat(n - s.length);\n}\n\nfunction projectUrl(baseUrl: string, p: ProjectSummary): string {\n // Canonical URL: content origin の /p/<slug>。\n // baseUrl は app origin (Bearer 認証先) を指すので、`app.` → `content.` に置換する。\n // 置換できないカスタム base (localhost 等) はそのまま fallback。\n const root = baseUrl.replace(/\\/+$/, '');\n const contentRoot = /^https?:\\/\\/app\\./.test(root)\n ? root.replace(/^(https?:\\/\\/)app\\./, '$1content.')\n : root;\n return `${contentRoot}/p/${p.slug}`;\n}\n\nfunction fmtDate(unixSec: number | null | undefined): string {\n if (!unixSec) return '-';\n // server は unix sec で返す (plan §3.2 Response 例)。\n const d = new Date(unixSec * 1000);\n return d.toISOString().replace('T', ' ').replace(/\\.\\d+Z$/, '');\n}\n\nexport function registerListCommand(parent: Command): void {\n parent\n .command('list')\n .description('List your published projects')\n .option('-u, --base-url <url>', 'Override API base URL')\n .action(async (opts: ListOpts) => {\n try {\n const config = await loadRuntimeConfig({ ...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}) });\n const client = makeClient(config);\n const projects = await client.list();\n\n if (projects.length === 0) {\n process.stdout.write('(no projects)\\n');\n return;\n }\n\n // visibility は server 側で optional (旧 server 互換)。\n // 未返却 (旧 server) は '-' に倒して、private を curator と誤表示しないようにする。\n function visLabel(v: ProjectSummary['visibility']): string {\n if (v === 'private') return 'priv';\n if (v === 'curator') return 'cura';\n if (v === 'public') return 'pub';\n return '-';\n }\n\n const rows = projects.map((p) => ({\n slug: p.slug,\n title: p.title,\n vis: visLabel(p.visibility),\n published_at: fmtDate(p.published_at),\n url: projectUrl(config.base_url, p),\n }));\n\n const headers = {\n slug: 'slug',\n title: 'title',\n vis: 'vis',\n published_at: 'published_at',\n url: 'url',\n };\n const widths = {\n slug: Math.max(headers.slug.length, ...rows.map((r) => r.slug.length)),\n title: Math.max(headers.title.length, ...rows.map((r) => r.title.length)),\n vis: Math.max(headers.vis.length, ...rows.map((r) => r.vis.length)),\n published_at: Math.max(\n headers.published_at.length,\n ...rows.map((r) => r.published_at.length),\n ),\n url: Math.max(headers.url.length, ...rows.map((r) => r.url.length)),\n };\n\n const line = (r: typeof headers): string =>\n [\n pad(r.slug, widths.slug),\n pad(r.title, widths.title),\n pad(r.vis, widths.vis),\n pad(r.published_at, widths.published_at),\n r.url,\n ].join(' ');\n\n process.stdout.write(`${line(headers)}\\n`);\n process.stdout.write(\n `${[\n '-'.repeat(widths.slug),\n '-'.repeat(widths.title),\n '-'.repeat(widths.vis),\n '-'.repeat(widths.published_at),\n '-'.repeat(widths.url),\n ].join(' ')}\\n`,\n );\n for (const r of rows) {\n process.stdout.write(`${line(r)}\\n`);\n }\n } catch (e) {\n fatal(e);\n }\n });\n}\n","import readline from 'node:readline';\nimport { Command } from 'commander';\nimport { fatal, info, success, warn } from '../lib/console.js';\nimport { loadRuntimeConfig, makeClient } from '../lib/config.js';\n\ninterface DeleteOpts {\n yes?: boolean;\n baseUrl?: string;\n}\n\nfunction confirm(prompt: string): Promise<boolean> {\n return new Promise((resolve) => {\n const rl = readline.createInterface({ input: process.stdin, output: process.stderr });\n let settled = false;\n const finish = (ok: boolean) => {\n if (settled) return;\n settled = true;\n rl.close();\n resolve(ok);\n };\n rl.on('SIGINT', () => {\n rl.close();\n process.exit(130);\n });\n rl.question(prompt, (ans) => {\n finish(/^y(es)?$/i.test(ans.trim()));\n });\n });\n}\n\nexport function registerDeleteCommand(parent: Command): void {\n parent\n .command('delete')\n .description('Delete a project by slug or id')\n .argument('<slug-or-id>', 'Project slug or id')\n .option('-y, --yes', 'Skip confirmation prompt')\n .option('-u, --base-url <url>', 'Override API base URL')\n .action(async (target: string, opts: DeleteOpts) => {\n try {\n const config = await loadRuntimeConfig({ ...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}) });\n\n if (!opts.yes) {\n const ok = await confirm(`delete project \"${target}\"? [y/N] `);\n if (!ok) {\n warn('cancelled');\n process.exit(0);\n }\n }\n\n const client = makeClient(config);\n info(`deleting ${target}...`);\n await client.delete(target);\n success(`deleted: ${target}`);\n } catch (e) {\n fatal(e);\n }\n });\n}\n","import { Command } from 'commander';\nimport {\n getConfigPath,\n loadConfig,\n saveConfig,\n ZurokuError,\n type DefaultVisibility,\n type ZurokuConfig,\n} from '@zuroku/core';\nimport { fatal, info, success } from '../lib/console.js';\n\ntype ConfigKey = 'default-visibility';\n\nconst KEYS: readonly ConfigKey[] = ['default-visibility'] as const;\n\nfunction parseKey(raw: string): ConfigKey {\n if ((KEYS as readonly string[]).includes(raw)) return raw as ConfigKey;\n throw new ZurokuError(\n 'INVALID_INPUT',\n 0,\n `unknown config key '${raw}'. valid keys: ${KEYS.join(', ')}`,\n );\n}\n\nexport function parseVisibility(raw: string): DefaultVisibility {\n if (raw === 'private' || raw === 'curator') return raw;\n if (raw === 'public') {\n // 'public' は server 予約語で reject される。default 保存は許可しない\n // (うっかり default 公開化の事故防止)。\n throw new ZurokuError(\n 'INVALID_INPUT',\n 0,\n \"'public' is reserved by the server and cannot be stored as default-visibility.\",\n );\n }\n throw new ZurokuError(\n 'INVALID_INPUT',\n 0,\n `--default-visibility must be 'private' or 'curator' (got '${raw}')`,\n );\n}\n\n/**\n * config を読み込む。core の `loadConfig` は ENOENT を default 値で吸収するので\n * ここで投げてくる例外は \"ファイルはあるが parse 不可\" or \"permission denied\" だけ。\n * そのまま `fatal()` に渡し、malformed JSON を `set` で黙って上書きしないようにする\n * (data loss 防止)。\n */\nasync function readConfigOrFatal(): Promise<ZurokuConfig> {\n try {\n return await loadConfig();\n } catch (e) {\n throw new ZurokuError(\n 'CONFIG',\n 0,\n `Failed to read config (refusing to overwrite a corrupt file): ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n}\n\nexport function registerConfigCommand(parent: Command): void {\n const config = parent\n .command('config')\n .description('Manage local CLI config (~/.config/zuroku/config.json)');\n\n config\n .command('get')\n .description('Print one config key, or all keys when no key is given')\n .argument('[key]', `Config key (${KEYS.join(' | ')})`)\n .action(async (rawKey: string | undefined) => {\n try {\n const cfg = await readConfigOrFatal();\n if (rawKey === undefined) {\n info(`path: ${getConfigPath()}`);\n process.stdout.write(`default-visibility=${cfg.default_visibility ?? '(unset, server default: curator)'}\\n`);\n return;\n }\n const key = parseKey(rawKey);\n if (key === 'default-visibility') {\n process.stdout.write(`${cfg.default_visibility ?? ''}\\n`);\n }\n } catch (e) {\n fatal(e);\n }\n });\n\n config\n .command('set')\n .description('Set a config key')\n .argument('<key>', `Config key (${KEYS.join(' | ')})`)\n .argument('<value>', \"Value (for default-visibility: 'private' | 'curator')\")\n .action(async (rawKey: string, rawValue: string) => {\n try {\n const key = parseKey(rawKey);\n const existing = await readConfigOrFatal();\n if (key === 'default-visibility') {\n const v = parseVisibility(rawValue);\n const next: ZurokuConfig = { ...existing, default_visibility: v };\n await saveConfig(next);\n success(`default-visibility = ${v}`);\n info(`config: ${getConfigPath()}`);\n }\n } catch (e) {\n fatal(e);\n }\n });\n\n config\n .command('unset')\n .description('Remove a config key (revert to server default)')\n .argument('<key>', `Config key (${KEYS.join(' | ')})`)\n .action(async (rawKey: string) => {\n try {\n const key = parseKey(rawKey);\n const existing = await readConfigOrFatal();\n if (key === 'default-visibility') {\n const next: ZurokuConfig = { ...existing };\n delete next.default_visibility;\n await saveConfig(next);\n success('default-visibility cleared (server default: curator)');\n }\n } catch (e) {\n fatal(e);\n }\n });\n}\n"],"mappings":";;;AAAA,SAAS,eAAe;;;ACCxB,SAAS,YAAY,YAAY,kBAAkB,qBAAqB;;;ACDxE,OAAO,WAAW;AAClB,SAAS,mBAAmB;AAQrB,SAAS,KAAK,KAAmB;AACtC,UAAQ,OAAO,MAAM,GAAG,MAAM,KAAK,MAAM,CAAC,KAAK,GAAG;AAAA,CAAI;AACxD;AAEO,SAAS,KAAK,KAAmB;AACtC,UAAQ,OAAO,MAAM,GAAG,MAAM,OAAO,MAAM,CAAC,KAAK,GAAG;AAAA,CAAI;AAC1D;AAEO,SAAS,QAAQ,KAAmB;AACzC,UAAQ,OAAO,MAAM,GAAG,MAAM,MAAM,IAAI,CAAC,OAAO,GAAG;AAAA,CAAI;AACzD;AAQO,SAAS,MAAM,KAAqB;AACzC,MAAI,eAAe,aAAa;AAC9B,UAAM,OAAO,MAAM,IAAI,EAAE,KAAK,IAAI,IAAI;AACtC,UAAM,SAAS,IAAI,SAAS,IAAI,MAAM,IAAI,UAAU,IAAI,MAAM,GAAG,IAAI;AACrE,YAAQ,OAAO,MAAM,GAAG,MAAM,IAAI,OAAO,CAAC,IAAI,IAAI,GAAG,MAAM,IAAI,IAAI,OAAO;AAAA,CAAI;AAE9E,QACE,IAAI,SAAS,YACb,IAAI,SAAS,kBACb,IAAI,SAAS,UACb,IAAI,WAAW,OACf,IAAI,WAAW,KACf;AACA,cAAQ,OAAO;AAAA,QACb,MAAM;AAAA,UACJ;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,WAAW,eAAe,OAAO;AAC/B,YAAQ,OAAO,MAAM,GAAG,MAAM,IAAI,OAAO,CAAC,IAAI,IAAI,OAAO;AAAA,CAAI;AAAA,EAC/D,OAAO;AACL,YAAQ,OAAO,MAAM,GAAG,MAAM,IAAI,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC;AAAA,CAAI;AAAA,EAC/D;AACA,UAAQ,KAAK,CAAC;AAChB;AAGO,SAAS,UAAU,OAAuB;AAC/C,MAAI,MAAM,UAAU,EAAG,QAAO,IAAI,OAAO,MAAM,MAAM;AACrD,QAAM,OAAO,MAAM,MAAM,GAAG,CAAC;AAC7B,QAAM,OAAO,MAAM,MAAM,EAAE;AAC3B,SAAO,GAAG,IAAI,MAAM,IAAI;AAC1B;;;ADnDO,SAAS,qBAAqB,QAAuB;AAC1D,QAAM,OAAO,OAAO,SAAS,KAAK,CAAC,MAAM,EAAE,KAAK,MAAM,MAAM,KAAK,OAAO,QAAQ,MAAM,EACnF,YAAY,2BAA2B;AAE1C,OACG,QAAQ,OAAO,EACf,YAAY,mDAAmD,EAC/D,eAAe,uBAAuB,wBAAwB,EAC9D,OAAO,wBAAwB,yBAAyB,MAAS,EACjE,OAAO,OAAO,SAAoB;AACjC,QAAI;AACF,YAAM,WAAW,MAAM,WAAW,EAAE,MAAM,OAAO;AAAA,QAC/C,UAAU;AAAA,QACV,OAAO;AAAA,MACT,EAAE;AAEF,YAAM,UACJ,KAAK,WAAW,KAAK,QAAQ,SAAS,IAClC,KAAK,UACL,SAAS,YAAY;AAE3B,YAAM,WAAW,EAAE,UAAU,SAAS,OAAO,KAAK,MAAM,CAAC;AAEzD,WAAK,WAAW,cAAc,CAAC,EAAE;AACjC,WAAK,aAAa,OAAO,EAAE;AAC3B,WAAK,UAAU,UAAU,KAAK,KAAK,CAAC,EAAE;AACtC,cAAQ,+BAA+B;AAAA,IACzC,SAAS,GAAG;AACV,YAAM,CAAC;AAAA,IACT;AAAA,EACF,CAAC;AACL;;;AExCA,SAAS,YAAY,UAAU;AAC/B,OAAO,UAAU;AAEjB;AAAA,EACE;AAAA,EACA;AAAA,EACA,eAAAA;AAAA,OAEK;;;ACRP,SAAS,cAAc,gBAAgB,cAAc,eAAAC,oBAAmB;AAgBxE,eAAsB,kBACpB,OAA0B,CAAC,GACJ;AACvB,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,eAAe;AAAA,EAChC,SAAS,GAAG;AACV,UAAM,IAAIA;AAAA,MACR;AAAA,MACA;AAAA,MACA,0BAA0B,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IACtE;AAAA,EACF;AAEA,MAAI,KAAK,WAAW,KAAK,QAAQ,SAAS,GAAG;AAC3C,aAAS,EAAE,GAAG,QAAQ,UAAU,KAAK,QAAQ;AAAA,EAC/C;AAEA,MAAI,CAAC,KAAK,qBAAqB,OAAO,UAAU,IAAI;AAClD,UAAM,IAAIA;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAGO,SAAS,WAAW,QAAoC;AAC7D,SAAO,IAAI,aAAa,EAAE,SAAS,OAAO,UAAU,OAAO,OAAO,MAAM,CAAC;AAC3E;;;ADpCA,IAAM,iBAAiB,IAAI,OAAO;AAY3B,SAAS,qBACd,MACA,WACQ;AACR,MAAI,MAAM;AACV,aAAW,EAAE,MAAM,GAAG,KAAK,WAAW;AACpC,QAAI,SAAS,GAAI;AACjB,UAAM,UAAU,KAAK,QAAQ,uBAAuB,MAAM;AAC1D,UAAM,IAAI,QAAQ,IAAI,OAAO,SAAS,OAAO,iBAAiB,GAAG,GAAG,KAAK,EAAE,EAAE;AAAA,EAC/E;AACA,SAAO;AACT;AAUA,SAAS,qBAAqB,MAAwE;AACpG,QAAM,aAAuB,CAAC;AAC9B,QAAM,oBAAoB,oBAAI,IAAY;AAC1C,QAAM,UAAU;AAChB,MAAI;AACJ,UAAQ,IAAI,QAAQ,KAAK,IAAI,OAAO,MAAM;AACxC,UAAM,MAAM,EAAE,CAAC;AAEf,QAAI,8BAA8B,KAAK,GAAG,EAAG;AAC7C,eAAW,KAAK,GAAG;AAEnB,UAAM,WAAW,IAAI,MAAM,0BAA0B;AACrD,QAAI,SAAU,mBAAkB,IAAI,SAAS,CAAC,CAAE;AAAA,EAClD;AAEA,QAAM,YAAY;AAClB,UAAQ,IAAI,UAAU,KAAK,IAAI,OAAO,MAAM;AAC1C,eAAW,QAAQ,EAAE,CAAC,EAAG,MAAM,GAAG,GAAG;AACnC,YAAM,MAAM,KAAK,KAAK,EAAE,MAAM,KAAK,EAAE,CAAC;AACtC,UAAI,CAAC,OAAO,oBAAoB,KAAK,GAAG,EAAG;AAC3C,iBAAW,KAAK,GAAG;AACnB,YAAM,WAAW,IAAI,MAAM,0BAA0B;AACrD,UAAI,SAAU,mBAAkB,IAAI,SAAS,CAAC,CAAE;AAAA,IAClD;AAAA,EACF;AACA,SAAO,EAAE,YAAY,kBAAkB;AACzC;AAMA,SAAS,sBACP,UACA,UACA,cACA,wBACA,UACQ;AACR,QAAM,WAAW,IAAI,IAAI,sBAAsB;AAC/C,QAAM,oBAAoB,CAAC,GAAG,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,CAAC;AAC1E,QAAM,cAAc,uBAAuB,OAAO,CAAC,MAAM,CAAC,aAAa,IAAI,CAAC,CAAC;AAG7E,QAAM,aAAa,SAAS;AAAA,IAC1B,CAAC,MAAM,CAAC,kBAAkB,KAAK,CAAC,KAAK,CAAC,sBAAsB,KAAK,CAAC;AAAA,EACpE;AAEA,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,4EAA4E;AACvF,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,cAAc,QAAQ,EAAE;AACnC,QAAM,KAAK,kBAAkB,WAAW,yEAAyE,qBAAqB,EAAE;AACxI,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,4BAA4B;AACvC,QAAM,KAAK,gFAAgF;AAC3F,QAAM,KAAK,+EAA+E;AAC1F,QAAM,KAAK,gFAAgF;AAC3F,QAAM,KAAK,sEAAsE;AACjF,QAAM,KAAK,EAAE;AAEb,MAAI,kBAAkB,QAAQ;AAC5B,UAAM,KAAK,iFAAiF;AAC5F,eAAW,KAAK,kBAAmB,OAAM,KAAK,WAAW,CAAC,EAAE;AAC5D,UAAM,KAAK,EAAE;AAAA,EACf;AAEA,MAAI,YAAY,QAAQ;AACtB,UAAM,KAAK,0FAA0F;AACrG,eAAW,KAAK,YAAa,OAAM,KAAK,OAAO,CAAC,EAAE;AAClD,UAAM,KAAK,EAAE;AAAA,EACf;AAEA,MAAI,WAAW,QAAQ;AACrB,UAAM,KAAK,gFAAgF;AAC3F,eAAW,KAAK,WAAW,MAAM,GAAG,CAAC,EAAG,OAAM,KAAK,OAAO,CAAC,EAAE;AAC7D,QAAI,WAAW,SAAS,EAAG,OAAM,KAAK,UAAU,WAAW,SAAS,CAAC,QAAQ;AAC7E,UAAM,KAAK,8EAA8E;AACzF,UAAM,KAAK,4DAAgE;AAC3E,UAAM,KAAK,EAAE;AAAA,EACf;AAEA,QAAM,KAAK,4CAA4C;AACvD,QAAM,KAAK,mFAAmF;AAC9F,QAAM,KAAK,+EAA+E;AAC1F,QAAM,KAAK,oDAAoD;AAC/D,QAAM,KAAK,mFAAmF;AAC9F,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,sEAAsE;AACjF,SAAO,MAAM,KAAK,IAAI;AACxB;AAYO,SAAS,uBAAuB,QAAuB;AAC5D,SACG,QAAQ,SAAS,EACjB,YAAY,iEAAiE,EAC7E,SAAS,UAAU,kCAAkC,EACrD,SAAS,eAAe,iCAAiC,EACzD,eAAe,uBAAuB,eAAe,EACrD,OAAO,qBAAqB,yCAAyC,EACrE,OAAO,4BAA4B,qBAAqB,EACxD,OAAO,iBAAiB,2CAA2C,EACnE,OAAO,wBAAwB,uBAAuB,EACtD;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,aAAa,4EAA4E,EAChG,OAAO,OAAO,SAAiB,QAAkB,SAAsB;AACtE,QAAI;AACF,YAAM,SAAS,MAAM,kBAAkB,EAAE,GAAI,KAAK,UAAU,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC,EAAG,CAAC;AAG7F,YAAM,WAAW,KAAK,QAAQ,OAAO;AACrC,UAAI;AACJ,UAAI;AACF,mBAAW,MAAM,GAAG,KAAK,QAAQ;AAAA,MACnC,QAAQ;AACN,cAAM,IAAIC,aAAY,aAAa,GAAG,wBAAwB,OAAO,EAAE;AAAA,MACzE;AACA,UAAI,CAAC,SAAS,OAAO,GAAG;AACtB,cAAM,IAAIA,aAAY,iBAAiB,GAAG,uBAAuB,OAAO,EAAE;AAAA,MAC5E;AACA,UAAI,SAAS,OAAO,gBAAgB;AAClC,cAAM,IAAIA;AAAA,UACR;AAAA,UACA;AAAA,UACA,kCAAkC,SAAS,IAAI;AAAA,QACjD;AAAA,MACF;AACA,YAAM,UAAU,MAAM,GAAG,SAAS,QAAQ;AAC1C,WAAK,SAAS,KAAK,SAAS,QAAQ,CAAC,KAAK,SAAS,IAAI,SAAS;AAMhE,YAAM,SAAwB,CAAC;AAC/B,YAAM,YAAiD,CAAC;AACxD,iBAAW,OAAO,QAAQ;AACxB,cAAM,MAAM,KAAK,QAAQ,GAAG;AAC5B,cAAM,QAAQ,KAAK,SAAS,GAAG;AAC/B,cAAM,UAAU,KAAK,WACjB,MAAM,kBAAkB,GAAG,IAC3B,MAAM,qBAAqB,GAAG;AAClC;AAAA,UACE,UAAU,KAAK,OAAO,QAAQ,QAAQ,KAAK,QAAQ,OAAO,UAAU,WAAW,QAAQ,WAAW;AAAA,QACpG;AACA,YAAI,UAAU,QAAQ,UAAU;AAC9B,oBAAU,KAAK,EAAE,MAAM,OAAO,IAAI,QAAQ,SAAS,CAAC;AAAA,QACtD;AACA,eAAO,KAAK;AAAA,UACV,UAAU,QAAQ;AAAA,UAClB,QAAQ,QAAQ;AAAA,UAChB,aAAa,QAAQ;AAAA,QACvB,CAAC;AAAA,MACH;AAIA,YAAM,eAAe,QAAQ,SAAS,MAAM;AAC5C,YAAM,WAAW,UAAU,SAAS,IAAI,qBAAqB,cAAc,SAAS,IAAI;AACxF,UAAI,UAAU,SAAS,KAAK,aAAa,cAAc;AACrD,aAAK,0CAA0C,UAAU,MAAM,sBAAsB;AAAA,MACvF;AACA,YAAM,gBAAgB,aAAa,eAAe,OAAO,KAAK,UAAU,MAAM,IAAI;AAGlF,UAAI,QAAQ,IAAI,0BAA0B,KAAK;AAC7C,cAAM,EAAE,YAAY,kBAAkB,IAAI,qBAAqB,QAAQ;AACvE,cAAM,oBAAoB,OAAO,IAAI,CAAC,MAAM,EAAE,QAAQ;AACtD,cAAM,WAAW,IAAI,IAAI,iBAAiB;AAC1C,cAAM,UAAU,CAAC,GAAG,iBAAiB,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,CAAC;AACrE,cAAM,SAAS,WAAW;AAAA,UACxB,CAAC,MAAM,CAAC,kBAAkB,KAAK,CAAC,KAAK,CAAC,8BAA8B,KAAK,CAAC,KAAK,CAAC,YAAY,KAAK,CAAC;AAAA,QACpG;AAGA,cAAM,YAAY,mBAAmB,KAAK,QAAQ;AAClD,cAAM,eAAe,aAAa,kBAAkB,SAAS,KAAK,OAAO,SAAS;AAClF,YAAI,QAAQ,SAAS,KAAK,cAAc;AACtC,gBAAM,IAAIA;AAAA,YACR;AAAA,YACA;AAAA,YACA,sBAAsB,UAAU,YAAY,mBAAmB,mBAAmB,KAAK,QAAQ;AAAA,UACjG;AAAA,QACF;AAAA,MACF;AAMA,UAAI;AACJ,UAAI,KAAK,eAAe,QAAW;AACjC,YACE,KAAK,eAAe,aACpB,KAAK,eAAe,aACpB,KAAK,eAAe,UACpB;AACA,gBAAM,IAAIA;AAAA,YACR;AAAA,YACA;AAAA,YACA,+DAA+D,KAAK,UAAU;AAAA,UAChF;AAAA,QACF;AACA,qBAAa,KAAK;AAAA,MACpB;AACA,UAAI,KAAK,SAAS;AAGhB,YAAI,eAAe,UAAa,eAAe,WAAW;AACxD,eAAK,oCAAoC,UAAU,EAAE;AAAA,QACvD;AACA,qBAAa;AAAA,MACf;AACA,UAAI,eAAe,UAAa,OAAO,uBAAuB,QAAW;AACvE,qBAAa,OAAO;AACpB,aAAK,eAAe,UAAU,mCAAmC;AAAA,MACnE;AAGA,YAAM,SAAS,WAAW,MAAM;AAChC,YAAM,WAAW,aAAa,gBAAgB,UAAU,MAAM;AAC9D,WAAK,kBAAkB,KAAK,KAAK,IAAI,KAAK,OAAO,UAAU,KAAK,IAAI,MAAM,EAAE,GAAG,QAAQ,KAAK;AAE5F,YAAM,QAAqD;AAAA,QACzD,MAAM;AAAA,QACN;AAAA,QACA,OAAO,KAAK;AAAA,MACd;AACA,UAAI,KAAK,SAAS,OAAW,OAAM,OAAO,KAAK;AAC/C,UAAI,KAAK,gBAAgB,OAAW,OAAM,cAAc,KAAK;AAC7D,UAAI,eAAe,OAAW,OAAM,aAAa;AAEjD,YAAM,MAAM,MAAM,OAAO,eAAe,KAAK;AAE7C,cAAQ,mBAAmB,IAAI,IAAI,OAAO,IAAI,EAAE,EAAE;AAClD,UAAI,eAAe,WAAW;AAC5B,aAAK,6EAAwE;AAE7E,cAAM,UAAU,OAAO,SAAS,QAAQ,QAAQ,EAAE;AAClD,aAAK,sBAAsB,OAAO,oBAAoB;AAAA,MACxD;AAEA,cAAQ,OAAO,MAAM,GAAG,IAAI,GAAG;AAAA,CAAI;AAAA,IACrC,SAAS,GAAG;AACV,YAAM,CAAC;AAAA,IACT;AAAA,EACF,CAAC;AACL;;;AExSA,SAAS,IAAI,GAAW,GAAmB;AACzC,MAAI,EAAE,UAAU,EAAG,QAAO;AAC1B,SAAO,IAAI,IAAI,OAAO,IAAI,EAAE,MAAM;AACpC;AAEA,SAAS,WAAW,SAAiB,GAA2B;AAI9D,QAAM,OAAO,QAAQ,QAAQ,QAAQ,EAAE;AACvC,QAAM,cAAc,oBAAoB,KAAK,IAAI,IAC7C,KAAK,QAAQ,uBAAuB,YAAY,IAChD;AACJ,SAAO,GAAG,WAAW,MAAM,EAAE,IAAI;AACnC;AAEA,SAAS,QAAQ,SAA4C;AAC3D,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,IAAI,IAAI,KAAK,UAAU,GAAI;AACjC,SAAO,EAAE,YAAY,EAAE,QAAQ,KAAK,GAAG,EAAE,QAAQ,WAAW,EAAE;AAChE;AAEO,SAAS,oBAAoB,QAAuB;AACzD,SACG,QAAQ,MAAM,EACd,YAAY,8BAA8B,EAC1C,OAAO,wBAAwB,uBAAuB,EACtD,OAAO,OAAO,SAAmB;AAChC,QAAI;AAYF,UAASC,YAAT,SAAkB,GAAyC;AACzD,YAAI,MAAM,UAAW,QAAO;AAC5B,YAAI,MAAM,UAAW,QAAO;AAC5B,YAAI,MAAM,SAAU,QAAO;AAC3B,eAAO;AAAA,MACT;AALS,qBAAAA;AAXT,YAAM,SAAS,MAAM,kBAAkB,EAAE,GAAI,KAAK,UAAU,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC,EAAG,CAAC;AAC7F,YAAM,SAAS,WAAW,MAAM;AAChC,YAAM,WAAW,MAAM,OAAO,KAAK;AAEnC,UAAI,SAAS,WAAW,GAAG;AACzB,gBAAQ,OAAO,MAAM,iBAAiB;AACtC;AAAA,MACF;AAWA,YAAM,OAAO,SAAS,IAAI,CAAC,OAAO;AAAA,QAChC,MAAM,EAAE;AAAA,QACR,OAAO,EAAE;AAAA,QACT,KAAKA,UAAS,EAAE,UAAU;AAAA,QAC1B,cAAc,QAAQ,EAAE,YAAY;AAAA,QACpC,KAAK,WAAW,OAAO,UAAU,CAAC;AAAA,MACpC,EAAE;AAEF,YAAM,UAAU;AAAA,QACd,MAAM;AAAA,QACN,OAAO;AAAA,QACP,KAAK;AAAA,QACL,cAAc;AAAA,QACd,KAAK;AAAA,MACP;AACA,YAAM,SAAS;AAAA,QACb,MAAM,KAAK,IAAI,QAAQ,KAAK,QAAQ,GAAG,KAAK,IAAI,CAAC,MAAM,EAAE,KAAK,MAAM,CAAC;AAAA,QACrE,OAAO,KAAK,IAAI,QAAQ,MAAM,QAAQ,GAAG,KAAK,IAAI,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC;AAAA,QACxE,KAAK,KAAK,IAAI,QAAQ,IAAI,QAAQ,GAAG,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,MAAM,CAAC;AAAA,QAClE,cAAc,KAAK;AAAA,UACjB,QAAQ,aAAa;AAAA,UACrB,GAAG,KAAK,IAAI,CAAC,MAAM,EAAE,aAAa,MAAM;AAAA,QAC1C;AAAA,QACA,KAAK,KAAK,IAAI,QAAQ,IAAI,QAAQ,GAAG,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,MAAM,CAAC;AAAA,MACpE;AAEA,YAAM,OAAO,CAAC,MACZ;AAAA,QACE,IAAI,EAAE,MAAM,OAAO,IAAI;AAAA,QACvB,IAAI,EAAE,OAAO,OAAO,KAAK;AAAA,QACzB,IAAI,EAAE,KAAK,OAAO,GAAG;AAAA,QACrB,IAAI,EAAE,cAAc,OAAO,YAAY;AAAA,QACvC,EAAE;AAAA,MACJ,EAAE,KAAK,IAAI;AAEb,cAAQ,OAAO,MAAM,GAAG,KAAK,OAAO,CAAC;AAAA,CAAI;AACzC,cAAQ,OAAO;AAAA,QACb,GAAG;AAAA,UACD,IAAI,OAAO,OAAO,IAAI;AAAA,UACtB,IAAI,OAAO,OAAO,KAAK;AAAA,UACvB,IAAI,OAAO,OAAO,GAAG;AAAA,UACrB,IAAI,OAAO,OAAO,YAAY;AAAA,UAC9B,IAAI,OAAO,OAAO,GAAG;AAAA,QACvB,EAAE,KAAK,IAAI,CAAC;AAAA;AAAA,MACd;AACA,iBAAW,KAAK,MAAM;AACpB,gBAAQ,OAAO,MAAM,GAAG,KAAK,CAAC,CAAC;AAAA,CAAI;AAAA,MACrC;AAAA,IACF,SAAS,GAAG;AACV,YAAM,CAAC;AAAA,IACT;AAAA,EACF,CAAC;AACL;;;AC7GA,OAAO,cAAc;AAUrB,SAAS,QAAQ,QAAkC;AACjD,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,KAAK,SAAS,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AACpF,QAAI,UAAU;AACd,UAAM,SAAS,CAAC,OAAgB;AAC9B,UAAI,QAAS;AACb,gBAAU;AACV,SAAG,MAAM;AACT,cAAQ,EAAE;AAAA,IACZ;AACA,OAAG,GAAG,UAAU,MAAM;AACpB,SAAG,MAAM;AACT,cAAQ,KAAK,GAAG;AAAA,IAClB,CAAC;AACD,OAAG,SAAS,QAAQ,CAAC,QAAQ;AAC3B,aAAO,YAAY,KAAK,IAAI,KAAK,CAAC,CAAC;AAAA,IACrC,CAAC;AAAA,EACH,CAAC;AACH;AAEO,SAAS,sBAAsB,QAAuB;AAC3D,SACG,QAAQ,QAAQ,EAChB,YAAY,gCAAgC,EAC5C,SAAS,gBAAgB,oBAAoB,EAC7C,OAAO,aAAa,0BAA0B,EAC9C,OAAO,wBAAwB,uBAAuB,EACtD,OAAO,OAAO,QAAgB,SAAqB;AAClD,QAAI;AACF,YAAM,SAAS,MAAM,kBAAkB,EAAE,GAAI,KAAK,UAAU,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC,EAAG,CAAC;AAE7F,UAAI,CAAC,KAAK,KAAK;AACb,cAAM,KAAK,MAAM,QAAQ,mBAAmB,MAAM,WAAW;AAC7D,YAAI,CAAC,IAAI;AACP,eAAK,WAAW;AAChB,kBAAQ,KAAK,CAAC;AAAA,QAChB;AAAA,MACF;AAEA,YAAM,SAAS,WAAW,MAAM;AAChC,WAAK,YAAY,MAAM,KAAK;AAC5B,YAAM,OAAO,OAAO,MAAM;AAC1B,cAAQ,YAAY,MAAM,EAAE;AAAA,IAC9B,SAAS,GAAG;AACV,YAAM,CAAC;AAAA,IACT;AAAA,EACF,CAAC;AACL;;;ACxDA;AAAA,EACE,iBAAAC;AAAA,EACA,cAAAC;AAAA,EACA,cAAAC;AAAA,EACA,eAAAC;AAAA,OAGK;AAKP,IAAM,OAA6B,CAAC,oBAAoB;AAExD,SAAS,SAAS,KAAwB;AACxC,MAAK,KAA2B,SAAS,GAAG,EAAG,QAAO;AACtD,QAAM,IAAIC;AAAA,IACR;AAAA,IACA;AAAA,IACA,uBAAuB,GAAG,kBAAkB,KAAK,KAAK,IAAI,CAAC;AAAA,EAC7D;AACF;AAEO,SAAS,gBAAgB,KAAgC;AAC9D,MAAI,QAAQ,aAAa,QAAQ,UAAW,QAAO;AACnD,MAAI,QAAQ,UAAU;AAGpB,UAAM,IAAIA;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,QAAM,IAAIA;AAAA,IACR;AAAA,IACA;AAAA,IACA,6DAA6D,GAAG;AAAA,EAClE;AACF;AAQA,eAAe,oBAA2C;AACxD,MAAI;AACF,WAAO,MAAMC,YAAW;AAAA,EAC1B,SAAS,GAAG;AACV,UAAM,IAAID;AAAA,MACR;AAAA,MACA;AAAA,MACA,iEAAiE,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IAC7G;AAAA,EACF;AACF;AAEO,SAAS,sBAAsB,QAAuB;AAC3D,QAAM,SAAS,OACZ,QAAQ,QAAQ,EAChB,YAAY,wDAAwD;AAEvE,SACG,QAAQ,KAAK,EACb,YAAY,wDAAwD,EACpE,SAAS,SAAS,eAAe,KAAK,KAAK,KAAK,CAAC,GAAG,EACpD,OAAO,OAAO,WAA+B;AAC5C,QAAI;AACF,YAAM,MAAM,MAAM,kBAAkB;AACpC,UAAI,WAAW,QAAW;AACxB,aAAK,SAASE,eAAc,CAAC,EAAE;AAC/B,gBAAQ,OAAO,MAAM,sBAAsB,IAAI,sBAAsB,kCAAkC;AAAA,CAAI;AAC3G;AAAA,MACF;AACA,YAAM,MAAM,SAAS,MAAM;AAC3B,UAAI,QAAQ,sBAAsB;AAChC,gBAAQ,OAAO,MAAM,GAAG,IAAI,sBAAsB,EAAE;AAAA,CAAI;AAAA,MAC1D;AAAA,IACF,SAAS,GAAG;AACV,YAAM,CAAC;AAAA,IACT;AAAA,EACF,CAAC;AAEH,SACG,QAAQ,KAAK,EACb,YAAY,kBAAkB,EAC9B,SAAS,SAAS,eAAe,KAAK,KAAK,KAAK,CAAC,GAAG,EACpD,SAAS,WAAW,uDAAuD,EAC3E,OAAO,OAAO,QAAgB,aAAqB;AAClD,QAAI;AACF,YAAM,MAAM,SAAS,MAAM;AAC3B,YAAM,WAAW,MAAM,kBAAkB;AACzC,UAAI,QAAQ,sBAAsB;AAChC,cAAM,IAAI,gBAAgB,QAAQ;AAClC,cAAM,OAAqB,EAAE,GAAG,UAAU,oBAAoB,EAAE;AAChE,cAAMC,YAAW,IAAI;AACrB,gBAAQ,wBAAwB,CAAC,EAAE;AACnC,aAAK,WAAWD,eAAc,CAAC,EAAE;AAAA,MACnC;AAAA,IACF,SAAS,GAAG;AACV,YAAM,CAAC;AAAA,IACT;AAAA,EACF,CAAC;AAEH,SACG,QAAQ,OAAO,EACf,YAAY,gDAAgD,EAC5D,SAAS,SAAS,eAAe,KAAK,KAAK,KAAK,CAAC,GAAG,EACpD,OAAO,OAAO,WAAmB;AAChC,QAAI;AACF,YAAM,MAAM,SAAS,MAAM;AAC3B,YAAM,WAAW,MAAM,kBAAkB;AACzC,UAAI,QAAQ,sBAAsB;AAChC,cAAM,OAAqB,EAAE,GAAG,SAAS;AACzC,eAAO,KAAK;AACZ,cAAMC,YAAW,IAAI;AACrB,gBAAQ,sDAAsD;AAAA,MAChE;AAAA,IACF,SAAS,GAAG;AACV,YAAM,CAAC;AAAA,IACT;AAAA,EACF,CAAC;AACL;;;APpHA,IAAM,UAAU;AAEhB,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,QAAQ,EACb,YAAY,wDAAmD,EAC/D,QAAQ,SAAS,iBAAiB,eAAe;AAEpD,qBAAqB,OAAO;AAC5B,uBAAuB,OAAO;AAC9B,oBAAoB,OAAO;AAC3B,sBAAsB,OAAO;AAC7B,sBAAsB,OAAO;AAE7B,QAAQ,mBAAmB;AAC3B,QAAQ,WAAW,QAAQ,IAAI,EAAE,MAAM,CAAC,MAAe;AAGrD,UAAQ,OAAO,MAAM,GAAG,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,CAAI;AACtE,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["ZurokuError","ZurokuError","ZurokuError","visLabel","getConfigPath","loadConfig","saveConfig","ZurokuError","ZurokuError","loadConfig","getConfigPath","saveConfig"]}
{
"name": "@zuroku/cli",
"version": "0.1.0",
"version": "0.1.1",
"type": "module",

@@ -5,0 +5,0 @@ "description": "Command-line publisher for zuroku — upload AI-generated graphic-recording HTML + assets to your zuroku instance.",

# @zuroku/cli
[![npm](https://img.shields.io/npm/v/@zuroku/cli.svg)](https://www.npmjs.com/package/@zuroku/cli)
[![skills.sh](https://skills.sh/b/AI-Driven-R-D-Dept/zuroku-cli)](https://skills.sh/AI-Driven-R-D-Dept/zuroku-cli)
Command-line publisher for [zuroku](https://github.com/MASAKASUNO1/zuroku) — uploads an HTML page plus its image assets to a zuroku instance and returns a shareable URL.

@@ -35,4 +38,6 @@

cd /tmp/zuroku-deploy
zuroku publish ./index.html ./*.png --title "..." --no-compress
# stdout last line is the public URL.
zuroku publish ./index.html ./*.png --title "..."
# - assets are compressed to WebP client-side (sharp, 85% quality, max 2000px long-edge)
# - <img src="img/foo.png"> in the HTML is auto-rewritten to .webp on the fly (since v0.1.1)
# - stdout last line is the public URL
```

@@ -56,3 +61,4 @@

- Supported image types: PNG / JPEG / WebP / GIF. SVG is rejected.
- Default behaviour is to convert PNG/JPEG to WebP at 85% quality, max 2000px long-edge. Pass `--no-compress` to keep filenames and bytes intact (you must then resize manually).
- Default behaviour is to convert PNG/JPEG to WebP at 85% quality, max 2000px long-edge. The CLI auto-rewrites HTML `<img src>` from `.png` / `.jpg` to `.webp` to keep references valid (since v0.1.1).
- Pass `--no-compress` to keep the original PNG/JPEG bytes and filenames intact (skip WebP). Useful when source images are already optimised.
- HTML and each asset are capped at 5 MiB. Daily quota: 50 publishes / 500 MB per user.

@@ -96,6 +102,21 @@

A ready-to-use Claude Code skill ships under [`skills/zuroku-publish/SKILL.md`](./skills/zuroku-publish/SKILL.md). Copy it into your project's `.claude/skills/` to let Claude Code drive this CLI directly.
A ready-to-use Claude Code skill ships under [`skills/zuroku-publish/`](./skills/zuroku-publish/SKILL.md). It lets an AI agent drive `zuroku publish` end-to-end (HTML rewrite, asset check, fail-fast preflight) without further prompting.
Install it once via [`skills`](https://skills.sh):
```bash
# install all skills from this repo to the agents you choose
npx skills add AI-Driven-R-D-Dept/zuroku-cli
# or pick just this skill
npx skills add AI-Driven-R-D-Dept/zuroku-cli --skill zuroku-publish
# global (~/<agent>/skills/) install for Claude Code only
npx skills add AI-Driven-R-D-Dept/zuroku-cli -s zuroku-publish -a claude-code -g
```
Manual install also works: copy [`skills/zuroku-publish/SKILL.md`](./skills/zuroku-publish/SKILL.md) into your project's `.claude/skills/zuroku-publish/SKILL.md`.
## License
MIT — see [LICENSE](./LICENSE).
---
name: zuroku-publish
description: HTML + 関連画像を zuroku CLI で publish したい場面で発動。「zuroku に上げて」「グラレコ publish」「explainer をデプロイ」で発動。
license: MIT
metadata:
author: AI-Driven-R-D-Dept
version: '0.1.1'
user-invocable: true

@@ -16,3 +20,3 @@ argument-hint: <html-path> [image-paths...] --title "..." [--no-compress] [--visibility private|curator] [--private]

```bash
# HTML 内の <img src> を `img/<basename>` に揃える
# HTML 内の <img src> を `img/<basename>` 形式に揃える (img/ prefix 必須)
mkdir -p /tmp/zuroku-deploy

@@ -23,8 +27,9 @@ sed 's|images/|img/|g' /path/to/index.html > /tmp/zuroku-deploy/index.html

cd /tmp/zuroku-deploy
zuroku publish ./index.html ./*.png --title "..." --no-compress
zuroku publish ./index.html ./*.png --title "..."
# - sharp で client-side WebP 圧縮 (85%、長辺 2000px) → 通信量 / 表示も軽量
# - HTML 内 <img src="img/foo.png"> は CLI が自動で .webp に rewrite (v0.1.1+)
# - 標準出力の最終行が公開 URL
# 自分だけが見える private で上げたい場合
zuroku publish ./index.html ./*.png --title "..." --private
# 標準出力の最終行が公開 URL
```

@@ -39,7 +44,7 @@

### R2. 圧縮 (`--no-compress`)
- default は sharp で PNG/JPEG → WebP 85% に変換し、長辺 max 2000px。**filename 拡張子も `.webp` に変わる**。
- HTML 側 `<img src="img/foo.png">` のままだと配信時 404 になる。圧縮するなら HTML を先に `.webp` に書き換える。
- `--no-compress` を付ければ filename 不変 (HTML を触らずに済む。サイズが大きい場合は事前リサイズ推奨)。
- GIF はアニメ保持のため compress しても passthrough される。
### R2. 圧縮 (default ON、client-side で軽量化される)
- default で sharp が PNG/JPEG → WebP 85%、長辺 max 2000px に変換 (帯域 / 表示の両方で大幅軽量化)。
- 拡張子 `.png` → `.webp` の rename が発生するが、**HTML 内 `<img src="img/foo.png">` は CLI が自動で `.webp` に rewrite する** (v0.1.1+)。AI agent 側で sed は不要。
- `--no-compress` を付けると original のまま upload (filename 不変、HTML も触らず)。サイズが大きい時は事前リサイズしないと R3 に当たる。
- GIF はアニメ保持のため compress でも passthrough (filename 不変)。

@@ -118,3 +123,3 @@ ### R3. サイズ上限

| `INVALID_INPUT [MISSING]` | HTML 内 src と asset の filename 不一致 | preflight メッセージの Suggested fixes を読む |
| 配信ページで画像 404 | sharp 圧縮で filename が `.webp` に変わったが HTML 未更新 | `--no-compress` で再 publish or HTML を sed で書き換え |
| 配信ページで画像 404 | (v0.1.1+ では自動 rewrite される。それ以前 / HTML を直接書き換えていた場合) basename 不一致。HTML の `<img src="img/...">` と asset 引数の filename を再確認 |
| `UNSUPPORTED_MEDIA 415` | SVG / Content-Type 偽装 | PNG/JPEG/WebP/GIF のみ |

@@ -121,0 +126,0 @@ | `BODY_TOO_LARGE 413` | ファイル 5 MiB 超過 | 事前リサイズ |