@hogsend/cli 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bin.ts","../src/eject.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { existsSync, realpathSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join, sep } from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport { EjectError, eject } from \"./eject.js\";\n\nconst USAGE = `hogsend — Hogsend project CLI\n\nUsage:\n hogsend eject <package> [--force] [--cwd <dir>]\n\nCommands:\n eject <package> Copy a @hogsend/* package's source into vendor/<name> and\n rewrite the consumer dependency to file:./vendor/<name>.\n Every other dependency keeps upgrading via pnpm up.\n\nOptions:\n --force Overwrite an existing vendor/<name>.\n --cwd <dir> Consumer repo root (defaults to the current directory).\n -h, --help Show this help.\n\nAfter ejecting, run: pnpm install`;\n\nconst RED = \"\\x1b[31m\";\nconst RESET = \"\\x1b[0m\";\n\nfunction fail(message: string): never {\n process.stderr.write(`${RED}error${RESET} ${message}\\n`);\n process.exit(1);\n}\n\n/**\n * Resolves the on-disk source directory for an installed package from the\n * consumer root. Works for pnpm's `.pnpm` layout and workspace symlinks.\n *\n * Strategy 1 (primary): probe `<consumerRoot>/node_modules/<pkg>/package.json`\n * directly, following the symlink to its real location. This is the common\n * layout and — unlike `require.resolve(\"<pkg>/package.json\")` — is NOT blocked\n * by the package's `exports` map (most packages don't expose `./package.json`).\n *\n * Strategy 2 (fallback): resolve the package's main entry via `createRequire`\n * and walk up to the nearest directory that contains a package.json whose\n * `name` matches.\n */\nfunction resolveSourceDir(pkg: string, consumerRoot: string): string {\n const direct = join(consumerRoot, \"node_modules\", pkg, \"package.json\");\n if (existsSync(direct)) {\n // realpath follows pnpm/workspace symlinks to the actual source dir.\n return dirname(realpathSync(direct));\n }\n\n const require = createRequire(`${consumerRoot}${sep}`);\n try {\n // Resolving the entry point works even when `./package.json` is not an\n // exported subpath; we then walk up to the package root.\n const entry = require.resolve(pkg);\n let dir = dirname(entry);\n while (dir !== dirname(dir)) {\n const candidate = join(dir, \"package.json\");\n if (existsSync(candidate)) {\n return dir;\n }\n dir = dirname(dir);\n }\n } catch {\n // fall through to the failure below\n }\n\n fail(\n `cannot resolve ${pkg} from ${consumerRoot}. Is it installed? Run pnpm install first.`,\n );\n}\n\nasync function runEject(args: string[]): Promise<void> {\n const { values, positionals } = parseArgs({\n args,\n allowPositionals: true,\n options: {\n force: { type: \"boolean\", default: false },\n cwd: { type: \"string\" },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n });\n\n if (values.help) {\n process.stdout.write(`${USAGE}\\n`);\n return;\n }\n\n const pkg = positionals[0];\n if (!pkg) {\n fail(\"eject requires a package name, e.g. hogsend eject @hogsend/engine\");\n }\n\n const consumerRoot = values.cwd ?? process.cwd();\n const sourceDir = resolveSourceDir(pkg, consumerRoot);\n\n try {\n const result = await eject({\n pkg,\n consumerRoot,\n sourceDir,\n force: values.force,\n });\n process.stdout.write(\n `Ejected ${result.pkg}\\n` +\n ` copied ${result.copiedFiles} files -> ${result.vendorPath}\\n` +\n ` dependency ${result.depSpecBefore} -> ${result.depSpecAfter}\\n` +\n `\\nNow run: ${result.followUp}\\n`,\n );\n } catch (error) {\n if (error instanceof EjectError) {\n fail(error.message);\n }\n throw error;\n }\n}\n\nasync function main(): Promise<void> {\n const [command, ...rest] = process.argv.slice(2);\n\n if (!command || command === \"--help\" || command === \"-h\") {\n process.stdout.write(`${USAGE}\\n`);\n return;\n }\n\n switch (command) {\n case \"eject\":\n await runEject(rest);\n break;\n default:\n fail(`unknown command \"${command}\"\\n\\n${USAGE}`);\n }\n}\n\nmain().catch((error) => {\n process.stderr.write(`${RED}error${RESET} ${String(error)}\\n`);\n process.exit(1);\n});\n","import { existsSync } from \"node:fs\";\nimport { cp, readFile, rm, stat, writeFile } from \"node:fs/promises\";\nimport { basename, join, relative, sep } from \"node:path\";\n\n/** Options for {@link eject}. */\nexport interface EjectOptions {\n /** Scoped package name to eject, e.g. \"@hogsend/engine\". */\n pkg: string;\n /** Consumer repo root (the dir containing the consumer package.json). */\n consumerRoot: string;\n /**\n * Where the package source currently lives (the workspace/registry copy).\n * In-monorepo: <repoRoot>/packages/<name>. In a scaffolded app it is the\n * resolved node_modules path. The caller resolves this; eject() never\n * guesses it.\n */\n sourceDir: string;\n /** Overwrite an existing vendor/<name>. */\n force?: boolean;\n}\n\n/** Result of a successful {@link eject}. */\nexport interface EjectResult {\n pkg: string;\n /** Absolute path to vendor/<name>. */\n vendorPath: string;\n /** The dep spec before the rewrite, e.g. \"workspace:^\". */\n depSpecBefore: string;\n /** The dep spec after the rewrite, \"file:./vendor/<name>\". */\n depSpecAfter: string;\n /** Number of files copied into vendor/<name>. */\n copiedFiles: number;\n /** The install command the operator must run next. */\n followUp: string;\n}\n\n/** Typed failure thrown by {@link eject} for expected, user-facing errors. */\nexport class EjectError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"EjectError\";\n }\n}\n\n/** Directory/file names excluded from the vendor copy. */\nconst EXCLUDED_NAMES = new Set([\n \"node_modules\",\n \"dist\",\n \".turbo\",\n \".changeset\",\n \"CHANGELOG.md\",\n]);\n\ninterface PackageJson {\n private?: boolean;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n [key: string]: unknown;\n}\n\ntype DepMap = \"dependencies\" | \"devDependencies\";\n\nasync function readPackageJson(file: string): Promise<PackageJson> {\n const raw = await readFile(file, \"utf8\");\n return JSON.parse(raw) as PackageJson;\n}\n\nasync function writePackageJson(\n file: string,\n value: PackageJson,\n): Promise<void> {\n await writeFile(file, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n}\n\n/**\n * Pure eject: copies a single package's source into the consumer's\n * `vendor/<name>` and rewrites only that consumer dependency to a\n * `file:./vendor/<name>` link. Every other dependency is left untouched, so\n * the rest of the `@hogsend/*` set keeps upgrading via `pnpm up`.\n *\n * This function performs filesystem operations only — it never runs an install\n * and never resolves `sourceDir` itself, which keeps it hermetically testable.\n */\nexport async function eject(opts: EjectOptions): Promise<EjectResult> {\n const { pkg, consumerRoot, sourceDir, force = false } = opts;\n\n // 1. Resolve names.\n const vendorName = basename(pkg);\n const vendorPath = join(consumerRoot, \"vendor\", vendorName);\n\n // 2. Validate the consumer dependency exists (before any side effects).\n const consumerPkgPath = join(consumerRoot, \"package.json\");\n const consumerPkg = await readPackageJson(consumerPkgPath);\n let depMap: DepMap | undefined;\n let depSpecBefore: string | undefined;\n if (consumerPkg.dependencies?.[pkg] !== undefined) {\n depMap = \"dependencies\";\n depSpecBefore = consumerPkg.dependencies[pkg];\n } else if (consumerPkg.devDependencies?.[pkg] !== undefined) {\n depMap = \"devDependencies\";\n depSpecBefore = consumerPkg.devDependencies[pkg];\n }\n if (!depMap || depSpecBefore === undefined) {\n throw new EjectError(\n `${pkg} is not a dependency of the consumer package.json`,\n );\n }\n\n // 3. Guard the vendor dir.\n if (existsSync(vendorPath)) {\n if (!force) {\n throw new EjectError(\n `vendor/${vendorName} already exists; pass --force to overwrite`,\n );\n }\n await rm(vendorPath, { recursive: true, force: true });\n }\n\n // 4. Copy source with an exclude filter. Returning false for a directory\n // prunes the whole subtree (Node 22 fs.cp filter semantics).\n let copiedFiles = 0;\n await cp(sourceDir, vendorPath, {\n recursive: true,\n filter: (source) => {\n const rel = relative(sourceDir, source);\n if (rel === \"\") {\n return true;\n }\n const segments = rel.split(sep);\n const name = basename(rel);\n // Exclude any path segment that is an excluded name (prunes subtrees).\n if (segments.some((segment) => EXCLUDED_NAMES.has(segment))) {\n return false;\n }\n if (name.endsWith(\".test.ts\")) {\n return false;\n }\n return true;\n },\n });\n\n // Count copied files (directories excluded) for the result summary.\n copiedFiles = await countFiles(vendorPath);\n\n // 5. Sanitize the vendored package.json.\n const vendoredPkgPath = join(vendorPath, \"package.json\");\n const vendoredPkg = await readPackageJson(vendoredPkgPath);\n if (vendoredPkg.private === true) {\n delete vendoredPkg.private;\n }\n await writePackageJson(vendoredPkgPath, vendoredPkg);\n\n // 6. Rewrite the consumer dep in place (preserving key order).\n const depSpecAfter = `file:./vendor/${vendorName}`;\n // biome-ignore lint/style/noNonNullAssertion: depMap validated above.\n consumerPkg[depMap]![pkg] = depSpecAfter;\n await writePackageJson(consumerPkgPath, consumerPkg);\n\n // 7. Return the result.\n return {\n pkg,\n vendorPath,\n depSpecBefore,\n depSpecAfter,\n copiedFiles,\n followUp: \"pnpm install\",\n };\n}\n\n/** Recursively counts regular files under a directory. */\nasync function countFiles(dir: string): Promise<number> {\n const { readdir } = await import(\"node:fs/promises\");\n let count = 0;\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n count += await countFiles(full);\n } else if (entry.isFile()) {\n count += 1;\n } else {\n const info = await stat(full);\n if (info.isFile()) {\n count += 1;\n }\n }\n }\n return count;\n}\n"],"mappings":"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","names":["existsSync","join","sep","join","existsSync","require","sep"]}
1
+ {"version":3,"sources":["../src/bin.ts","../src/commands/contacts.ts","../src/lib/http.ts","../src/lib/output.ts","../src/commands/doctor.ts","../src/commands/eject.ts","../src/eject.ts","../src/commands/events.ts","../src/commands/journeys.ts","../src/commands/patch.ts","../src/commands/setup.ts","../src/lib/prompt.ts","../src/commands/skills.ts","../src/commands/stats.ts","../src/commands/index.ts","../src/lib/config.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { createRequire } from \"node:module\";\nimport { commands } from \"./commands/index.js\";\nimport type { Command } from \"./commands/types.js\";\nimport { parseGlobalFlags, resolveConfig } from \"./lib/config.js\";\nimport { createAdminClient } from \"./lib/http.js\";\nimport { color, createOutput } from \"./lib/output.js\";\n\nfunction version(): string {\n try {\n const require = createRequire(import.meta.url);\n const pkg = require(\"../package.json\") as { version?: string };\n return pkg.version ?? \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\nfunction rootUsage(): string {\n const longest = commands.reduce((n, c) => Math.max(n, c.name.length), 0);\n const list = commands\n .map((c) => ` ${color.cyan(c.name.padEnd(longest))} ${c.summary}`)\n .join(\"\\n\");\n return `${color.bold(\"hogsend\")} — the agent-native Hogsend CLI\n\n${color.dim(\"Usage:\")} hogsend <command> [options]\n\n${color.dim(\"Commands:\")}\n${list}\n\n${color.dim(\"Global options:\")}\n --url <baseUrl> Target instance (default HOGSEND_API_URL or http://localhost:3002)\n --admin-key <key> Admin bearer token (default HOGSEND_ADMIN_KEY / ADMIN_API_KEY)\n --json Emit machine-readable JSON only (for agents)\n -h, --help Show help (use after a command for command help)\n -v, --version Show version\n\nRun ${color.cyan(\"hogsend <command> --help\")} for command-specific options.`;\n}\n\nfunction findCommand(name: string): Command | undefined {\n return commands.find((c) => c.name === name);\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n const [token, ...afterToken] = argv;\n\n // Version is a top-level concern (before flag parsing).\n if (token === \"-v\" || token === \"--version\") {\n process.stdout.write(`${version()}\\n`);\n return;\n }\n\n // No command, or a root-level help request.\n if (!token || token === \"-h\" || token === \"--help\") {\n process.stdout.write(`${rootUsage()}\\n`);\n return;\n }\n\n const command = findCommand(token);\n if (!command) {\n // Unknown command: report on stderr and show usage. Not json-gated since\n // there's no resolved Output yet.\n process.stderr.write(\n `${color.red(\"error\")} unknown command \"${token}\"\\n\\n${rootUsage()}\\n`,\n );\n process.exit(1);\n }\n\n // Parse global flags off the post-token argv; the rest is the command's argv.\n const flags = parseGlobalFlags(afterToken);\n const out = createOutput({ json: flags.json });\n\n // `hogsend <cmd> --help` short-circuits to the command's usage block.\n if (flags.help) {\n out.log(command.usage);\n return;\n }\n\n const cfg = resolveConfig(flags);\n const http = createAdminClient(cfg);\n\n await command.run({\n argv: flags.rest,\n cfg,\n http,\n out,\n json: flags.json,\n });\n}\n\nmain().catch((error: unknown) => {\n const msg = error instanceof Error ? error.message : String(error);\n // Best-effort json detection for top-level failures (Output may not exist).\n if (process.argv.includes(\"--json\")) {\n process.stdout.write(`${JSON.stringify({ error: msg })}\\n`);\n } else {\n process.stderr.write(`${color.red(\"error\")} ${msg}\\n`);\n }\n process.exit(1);\n});\n","import { parseArgs } from \"node:util\";\nimport { isHttpError } from \"../lib/http.js\";\nimport { color } from \"../lib/output.js\";\nimport type { Command, CommandContext } from \"./types.js\";\n\nconst usage = `hogsend contacts <subcommand> [options]\n\nInspect contacts via the running app's admin API (/v1/admin/contacts).\n\nSubcommands:\n list List contacts (newest activity first).\n get <id> Get one contact (by id or externalId) + preferences.\n timeline <id> Merged event/email/journey activity for a contact.\n\nlist options:\n --search <q> Filter by email/externalId substring.\n --limit <n> Page size (1-100, default 50).\n --offset <n> Page offset (default 0).\n\ntimeline options:\n --type <t> Restrict to one of: event | journey | email.\n --limit <n> Page size (1-100, default 50).\n --offset <n> Page offset (default 0).\n\nGlobal options (handled by the router): --url, --admin-key, --json, -h/--help.\n\nExamples:\n hogsend contacts list --search acme@ --json\n hogsend contacts get user_123\n hogsend contacts timeline user_123 --type email --json`;\n\ntype ContactRecord = {\n id: string;\n externalId: string;\n email: string | null;\n properties: Record<string, unknown>;\n firstSeenAt: string;\n lastSeenAt: string;\n createdAt: string;\n updatedAt: string;\n};\n\ntype Preferences = {\n id: string;\n userId: string;\n email: string;\n unsubscribedAll: boolean;\n suppressed: boolean;\n bounceCount: number;\n categories: Record<string, boolean>;\n} | null;\n\ntype ListResponse = {\n contacts: ContactRecord[];\n total: number;\n limit: number;\n offset: number;\n};\n\ntype GetResponse = {\n contact: ContactRecord;\n preferences: Preferences;\n};\n\ntype TimelineEntry = {\n type: \"event\" | \"journey\" | \"email\";\n timestamp: string;\n data: Record<string, unknown>;\n};\n\ntype TimelineResponse = {\n timeline: TimelineEntry[];\n total: number;\n limit: number;\n offset: number;\n};\n\nconst badge = `${color.bgMagenta(color.black(\" hogsend \"))} contacts`;\n\n/** Run an HTTP call, mapping HttpError into a clean ctx.out.fail message. */\nasync function fetchOrFail<T>(\n ctx: CommandContext,\n label: string,\n fn: () => Promise<T>,\n): Promise<T> {\n try {\n return await ctx.out.step(label, fn);\n } catch (err) {\n if (isHttpError(err)) {\n if (err.status === 404) {\n ctx.out.fail(err.message || \"contact not found\");\n }\n ctx.out.fail(err.message);\n }\n throw err;\n }\n}\n\nasync function runList(ctx: CommandContext, argv: string[]): Promise<void> {\n const { values } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n search: { type: \"string\" },\n limit: { type: \"string\" },\n offset: { type: \"string\" },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n });\n\n if (values.help) {\n ctx.out.log(usage);\n return;\n }\n\n const query = {\n search: values.search,\n limit: values.limit,\n offset: values.offset,\n };\n\n if (!ctx.json) ctx.out.intro(`${badge} list`);\n\n const res = await fetchOrFail<ListResponse>(ctx, \"Fetching contacts\", () =>\n ctx.http.get<ListResponse>(\"/v1/admin/contacts\", query),\n );\n\n if (ctx.json) {\n ctx.out.json(res);\n return;\n }\n\n ctx.out.table(\n res.contacts.map((cnt) => ({\n id: cnt.id,\n externalId: cnt.externalId,\n email: cnt.email ?? color.dim(\"(none)\"),\n lastSeenAt: cnt.lastSeenAt,\n })),\n [\"id\", \"externalId\", \"email\", \"lastSeenAt\"],\n );\n ctx.out.outro(\n `${res.contacts.length} of ${res.total} contact(s) — offset ${res.offset}, limit ${res.limit}`,\n );\n}\n\nasync function runGet(ctx: CommandContext, argv: string[]): Promise<void> {\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n });\n\n if (values.help) {\n ctx.out.log(usage);\n return;\n }\n\n // positionals[0] is the \"get\" subcommand token; the id follows it.\n const id = positionals[1];\n if (!id) {\n ctx.out.fail(\n \"contacts get requires an id, e.g. hogsend contacts get user_123\",\n );\n }\n\n if (!ctx.json) ctx.out.intro(`${badge} get`);\n\n const res = await fetchOrFail<GetResponse>(ctx, \"Fetching contact\", () =>\n ctx.http.get<GetResponse>(`/v1/admin/contacts/${encodeURIComponent(id)}`),\n );\n\n if (ctx.json) {\n ctx.out.json(res);\n return;\n }\n\n const { contact, preferences } = res;\n ctx.out.kv(\n {\n id: contact.id,\n externalId: contact.externalId,\n email: contact.email ?? color.dim(\"(none)\"),\n firstSeenAt: contact.firstSeenAt,\n lastSeenAt: contact.lastSeenAt,\n properties: contact.properties,\n },\n \"Contact\",\n );\n\n if (preferences) {\n ctx.out.kv(\n {\n unsubscribedAll: preferences.unsubscribedAll,\n suppressed: preferences.suppressed,\n bounceCount: preferences.bounceCount,\n categories: preferences.categories,\n },\n \"Preferences\",\n );\n } else {\n ctx.out.log(color.dim(\"No email preferences on record.\"));\n }\n\n ctx.out.outro(`Contact ${color.cyan(contact.externalId)}`);\n}\n\nasync function runTimeline(ctx: CommandContext, argv: string[]): Promise<void> {\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n type: { type: \"string\" },\n limit: { type: \"string\" },\n offset: { type: \"string\" },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n });\n\n if (values.help) {\n ctx.out.log(usage);\n return;\n }\n\n // positionals[0] is the \"timeline\" subcommand token; the id follows it.\n const id = positionals[1];\n if (!id) {\n ctx.out.fail(\n \"contacts timeline requires an id, e.g. hogsend contacts timeline user_123\",\n );\n }\n\n if (values.type && ![\"event\", \"journey\", \"email\"].includes(values.type)) {\n ctx.out.fail(\"--type must be one of: event, journey, email\");\n }\n\n const query = {\n type: values.type,\n limit: values.limit,\n offset: values.offset,\n };\n\n if (!ctx.json) ctx.out.intro(`${badge} timeline`);\n\n const res = await fetchOrFail<TimelineResponse>(\n ctx,\n \"Fetching timeline\",\n () =>\n ctx.http.get<TimelineResponse>(\n `/v1/admin/contacts/${encodeURIComponent(id)}/timeline`,\n query,\n ),\n );\n\n if (ctx.json) {\n ctx.out.json(res);\n return;\n }\n\n ctx.out.table(\n res.timeline.map((entry) => ({\n timestamp: entry.timestamp,\n type: entry.type,\n summary: summarizeTimelineEntry(entry),\n })),\n [\"timestamp\", \"type\", \"summary\"],\n );\n ctx.out.outro(\n `${res.timeline.length} of ${res.total} entry(s) — offset ${res.offset}, limit ${res.limit}`,\n );\n}\n\n/** One-line human description of a timeline entry, by type. */\nfunction summarizeTimelineEntry(entry: TimelineEntry): string {\n const d = entry.data;\n if (entry.type === \"event\") {\n return String(d.event ?? \"\");\n }\n if (entry.type === \"journey\") {\n return `${String(d.journeyId ?? \"\")} (${String(d.status ?? \"\")})`;\n }\n // email\n const subject = d.subject ? String(d.subject) : String(d.templateKey ?? \"\");\n return `${subject} [${String(d.status ?? \"\")}]`;\n}\n\nasync function run(ctx: CommandContext): Promise<void> {\n const sub = ctx.argv[0];\n\n switch (sub) {\n case \"list\":\n return runList(ctx, ctx.argv);\n case \"get\":\n return runGet(ctx, ctx.argv);\n case \"timeline\":\n return runTimeline(ctx, ctx.argv);\n case undefined:\n ctx.out.fail(\n \"contacts requires a subcommand: list, get, or timeline (see hogsend contacts --help)\",\n );\n break;\n default:\n ctx.out.fail(\n `unknown contacts subcommand \"${sub}\" — expected list, get, or timeline`,\n );\n }\n}\n\nexport const contactsCommand: Command = {\n name: \"contacts\",\n summary: \"List, inspect, and trace contact activity\",\n usage,\n run,\n};\n","import type { ResolvedConfig } from \"./config.js\";\n\n/** A non-2xx response (or transport failure) from the admin API. */\nexport interface HttpError extends Error {\n /** HTTP status code, or 0 for a transport-level failure (DNS/connect). */\n status: number;\n /** Parsed JSON body when available, else the raw text, else undefined. */\n body: unknown;\n}\n\n/** Query params accepted by `get` — undefined values are dropped. */\nexport type Query = Record<string, string | number | undefined>;\n\n/**\n * Thin admin HTTP client over native fetch (Node 22). Hits `<base>/v1/...`,\n * sends `Authorization: Bearer <adminKey>` on admin paths, parses JSON, and\n * throws an {@link HttpError} on any non-2xx response.\n *\n * Path convention: pass the path AFTER the base URL, e.g. `/v1/admin/journeys`\n * or `/v1/health`. The unauthenticated health route is reached via the same\n * `get` — pass `{ auth: false }` so a missing admin key doesn't error.\n */\nexport interface AdminClient {\n get<T = unknown>(\n path: string,\n query?: Query,\n opts?: RequestExtras,\n ): Promise<T>;\n patch<T = unknown>(path: string, body: unknown): Promise<T>;\n post<T = unknown>(path: string, body: unknown): Promise<T>;\n /** The resolved config this client is bound to (for messages/JSON output). */\n readonly cfg: ResolvedConfig;\n}\n\n/** Per-request overrides. */\nexport interface RequestExtras {\n /** Set false for unauthenticated routes (e.g. /v1/health). Default true. */\n auth?: boolean;\n}\n\nfunction isHttpError(value: unknown): value is HttpError {\n return value instanceof Error && \"status\" in value;\n}\n\nfunction makeHttpError(\n message: string,\n status: number,\n body: unknown,\n): HttpError {\n const err = new Error(message) as HttpError;\n err.name = \"HttpError\";\n err.status = status;\n err.body = body;\n return err;\n}\n\nfunction buildUrl(baseUrl: string, path: string, query?: Query): string {\n const url = new URL(path.startsWith(\"/\") ? path : `/${path}`, `${baseUrl}/`);\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined) continue;\n url.searchParams.set(key, String(value));\n }\n }\n return url.toString();\n}\n\nfunction bodyMessage(status: number, body: unknown): string {\n if (\n body &&\n typeof body === \"object\" &&\n \"error\" in body &&\n typeof (body as { error: unknown }).error === \"string\"\n ) {\n return `${status}: ${(body as { error: string }).error}`;\n }\n return `request failed with status ${status}`;\n}\n\n/** Build an {@link AdminClient} bound to the given resolved config. */\nexport function createAdminClient(cfg: ResolvedConfig): AdminClient {\n async function request<T>(\n method: string,\n path: string,\n opts: { query?: Query; body?: unknown; auth: boolean },\n ): Promise<T> {\n if (opts.auth && !cfg.adminKey) {\n throw makeHttpError(\n \"no admin key configured — pass --admin-key, or set HOGSEND_ADMIN_KEY / ADMIN_API_KEY\",\n 0,\n undefined,\n );\n }\n\n const headers: Record<string, string> = { Accept: \"application/json\" };\n if (opts.auth && cfg.adminKey) {\n headers.Authorization = `Bearer ${cfg.adminKey}`;\n }\n if (opts.body !== undefined) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n const url = buildUrl(cfg.baseUrl, path, opts.query);\n\n let res: Response;\n try {\n res = await fetch(url, {\n method,\n headers,\n body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,\n });\n } catch (cause) {\n const msg = cause instanceof Error ? cause.message : String(cause);\n throw makeHttpError(`cannot reach ${cfg.baseUrl} (${msg})`, 0, undefined);\n }\n\n const text = await res.text();\n let parsed: unknown;\n if (text.length > 0) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = text;\n }\n }\n\n if (!res.ok) {\n throw makeHttpError(bodyMessage(res.status, parsed), res.status, parsed);\n }\n\n return parsed as T;\n }\n\n return {\n cfg,\n get: <T>(path: string, query?: Query, extras?: RequestExtras) =>\n request<T>(\"GET\", path, { query, auth: extras?.auth ?? true }),\n patch: <T>(path: string, body: unknown) =>\n request<T>(\"PATCH\", path, { body, auth: true }),\n post: <T>(path: string, body: unknown) =>\n request<T>(\"POST\", path, { body, auth: true }),\n };\n}\n\nexport { isHttpError };\n","import {\n cancel,\n intro as clackIntro,\n note as clackNote,\n outro as clackOutro,\n spinner,\n} from \"@clack/prompts\";\nimport color from \"picocolors\";\n\n/**\n * Unified output sink. Two modes:\n *\n * - human: TTY clack chrome (intro badge, spinners, notes, outro) + tables.\n * Falls back to plain console.log lines when stdout is not a TTY.\n * - json (`--json`): ALL chrome is a no-op; the command emits exactly one\n * JSON document via `out.json(payload)`. Nothing else touches stdout, so a\n * --json run is always a single valid JSON document — safe for agents.\n *\n * `interactive` is true only when human mode AND stdout is a TTY; commands key\n * spinner/prompt behaviour off it. `isJson` flips command control flow to the\n * non-interactive branch.\n */\nexport interface Output {\n /** True when human-mode AND stdout is a TTY (clack chrome is live). */\n readonly interactive: boolean;\n /** True when `--json` was passed. */\n readonly isJson: boolean;\n /** Session intro badge. No-op in json / non-TTY. */\n intro(title: string): void;\n /**\n * Run an async step with a spinner in interactive mode; a plain awaited call\n * otherwise. The label is logged (not spun) when non-interactive & not json.\n */\n step<T>(label: string, fn: () => Promise<T>): Promise<T>;\n /** Boxed note. No-op in json / non-TTY (prints plain lines in non-TTY human). */\n note(body: string, title?: string): void;\n /** Render an array of records as a table (human only; no-op in json). */\n table(rows: Record<string, unknown>[], columns?: string[]): void;\n /** Render a key/value object (human only; no-op in json). */\n kv(obj: Record<string, unknown>, title?: string): void;\n /** Plain human/plain-text line. No-op in json. */\n log(msg: string): void;\n /** Emit the single JSON document. Only meaningful in json mode. */\n json(payload: unknown): void;\n /** Session outro. No-op in json / non-TTY. */\n outro(msg: string): void;\n /**\n * Fail terminally. json: prints `{ \"error\": message }` to stdout, exit 1.\n * human (TTY): clack cancel(message), exit 1. human (non-TTY): stderr, exit 1.\n */\n fail(message: string): never;\n}\n\nfunction renderTable(\n rows: Record<string, unknown>[],\n columns?: string[],\n): string {\n if (rows.length === 0) return color.dim(\"(no rows)\");\n const cols =\n columns ??\n Array.from(\n rows.reduce<Set<string>>((set, row) => {\n for (const key of Object.keys(row)) set.add(key);\n return set;\n }, new Set<string>()),\n );\n const cell = (value: unknown): string => {\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n };\n const widths = cols.map((c) =>\n Math.max(c.length, ...rows.map((r) => cell(r[c]).length)),\n );\n const pad = (text: string, width: number) =>\n text + \" \".repeat(width - text.length);\n const header = cols\n .map((c, i) => color.bold(pad(c, widths[i] ?? 0)))\n .join(\" \");\n const sep = cols.map((_, i) => \"-\".repeat(widths[i] ?? 0)).join(\" \");\n const body = rows\n .map((r) => cols.map((c, i) => pad(cell(r[c]), widths[i] ?? 0)).join(\" \"))\n .join(\"\\n\");\n return `${header}\\n${color.dim(sep)}\\n${body}`;\n}\n\nfunction renderKv(obj: Record<string, unknown>): string {\n const keys = Object.keys(obj);\n if (keys.length === 0) return color.dim(\"(empty)\");\n const width = Math.max(...keys.map((k) => k.length));\n return keys\n .map((k) => {\n const v = obj[k];\n const str =\n v === null || v === undefined\n ? \"\"\n : typeof v === \"object\"\n ? JSON.stringify(v)\n : String(v);\n return `${color.dim(`${k}:`.padEnd(width + 1))} ${str}`;\n })\n .join(\"\\n\");\n}\n\n/** Build an {@link Output} for the given mode. */\nexport function createOutput(opts: { json: boolean }): Output {\n const isJson = opts.json;\n const interactive = !isJson && Boolean(process.stdout.isTTY);\n\n return {\n interactive,\n isJson,\n\n intro(title) {\n if (!interactive) return;\n clackIntro(title);\n },\n\n async step<T>(label: string, fn: () => Promise<T>): Promise<T> {\n if (interactive) {\n const s = spinner();\n s.start(label);\n try {\n const result = await fn();\n s.stop(`${color.green(\"✓\")} ${label}`);\n return result;\n } catch (err) {\n s.stop(`${color.red(\"✗\")} ${label}`);\n throw err;\n }\n }\n if (!isJson) console.log(` ${label} ...`);\n return fn();\n },\n\n note(body, title) {\n if (isJson) return;\n if (interactive) {\n clackNote(body, title);\n return;\n }\n if (title) console.log(`\\n${title}`);\n console.log(body);\n },\n\n table(rows, columns) {\n if (isJson) return;\n console.log(renderTable(rows, columns));\n },\n\n kv(obj, title) {\n if (isJson) return;\n if (title) console.log(color.bold(title));\n console.log(renderKv(obj));\n },\n\n log(msg) {\n if (isJson) return;\n console.log(msg);\n },\n\n json(payload) {\n // Only stdout write in json mode; pretty-printed, single document.\n process.stdout.write(`${JSON.stringify(payload, null, 2)}\\n`);\n },\n\n outro(msg) {\n if (!interactive) return;\n clackOutro(msg);\n },\n\n fail(message): never {\n if (isJson) {\n process.stdout.write(`${JSON.stringify({ error: message })}\\n`);\n } else if (interactive) {\n cancel(message);\n } else {\n process.stderr.write(`${color.red(\"error\")} ${message}\\n`);\n }\n process.exit(1);\n },\n };\n}\n\nexport { color };\n","import { parseArgs } from \"node:util\";\nimport { isHttpError } from \"../lib/http.js\";\nimport { color } from \"../lib/output.js\";\nimport type { Command, CommandContext } from \"./types.js\";\n\nconst usage = `hogsend doctor [--url <baseUrl>] [--admin-key <key>] [--json]\n\nProbe a running Hogsend instance via GET /v1/health and report its health:\ncomponent status (database, redis), two-track schema state (engine + client),\nand an overall verdict.\n\nThe health route is unauthenticated, so doctor works without an admin key.\n\nVerdict:\n ok service healthy, all components up, schema in sync\n degraded reachable but a component (database/redis) is down\n migration_pending reachable but a schema track is behind (pending migrations)\n unreachable the instance could not be reached at all\n\nExit code: 0 when ok, 1 when unreachable / degraded / migration_pending.\n\nOptions:\n --url <baseUrl> Target instance (default HOGSEND_API_URL / .env / :3002).\n --admin-key <key> Unused by doctor (health is unauthenticated).\n --json Emit machine-readable JSON only.\n -h, --help Show this help.`;\n\n/** Subset of the engine /v1/health response we render. */\ninterface HealthComponent {\n status: \"up\" | \"down\";\n latencyMs?: number;\n}\ninterface HealthTrack {\n applied: string | null;\n required: string | null;\n inSync: boolean;\n pending: string[];\n}\ninterface HealthResponse {\n status: \"healthy\" | \"degraded\" | \"migration_pending\";\n uptime: number;\n timestamp: string;\n version: string;\n components: {\n database: HealthComponent;\n redis: HealthComponent;\n };\n schema: {\n engine: HealthTrack;\n client: HealthTrack;\n };\n}\n\ntype Verdict = \"ok\" | \"degraded\" | \"migration_pending\" | \"unreachable\";\n\n/** Map the server's status onto the CLI verdict vocabulary. */\nfunction toVerdict(status: HealthResponse[\"status\"]): Verdict {\n switch (status) {\n case \"healthy\":\n return \"ok\";\n case \"degraded\":\n return \"degraded\";\n case \"migration_pending\":\n return \"migration_pending\";\n }\n}\n\nfunction componentSymbol(status: \"up\" | \"down\"): string {\n return status === \"up\" ? color.green(\"up\") : color.red(\"down\");\n}\n\nfunction trackLine(name: string, track: HealthTrack): string {\n const sync = track.inSync\n ? color.green(\"in sync\")\n : color.yellow(\n `behind (${track.pending.length} pending: ${\n track.pending.length > 0 ? track.pending.join(\", \") : \"n/a\"\n })`,\n );\n const applied = track.applied ?? color.dim(\"none\");\n const required = track.required ?? color.dim(\"none\");\n return `${color.bold(name.padEnd(7))} applied ${applied} -> required ${required} ${sync}`;\n}\n\nasync function run(ctx: CommandContext): Promise<void> {\n const { values } = parseArgs({\n args: ctx.argv,\n allowPositionals: true,\n options: {\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n // doctor takes no extra flags of its own; tolerate stray tokens.\n strict: false,\n });\n\n if (values.help) {\n ctx.out.log(usage);\n return;\n }\n\n const { baseUrl } = ctx.http.cfg;\n\n // Fetch health. A transport failure (status 0) means unreachable — we surface\n // that as a first-class verdict rather than a hard error so agents get a\n // structured answer. Any other HttpError (non-2xx) is genuinely exceptional.\n let health: HealthResponse | null = null;\n let reachError: string | null = null;\n try {\n health = await ctx.out.step(`GET ${baseUrl}/v1/health`, () =>\n ctx.http.get<HealthResponse>(\"/v1/health\", undefined, { auth: false }),\n );\n } catch (error) {\n if (isHttpError(error) && error.status === 0) {\n reachError = error.message;\n } else if (isHttpError(error)) {\n // A 4xx/5xx from /v1/health: the instance is up but answering badly.\n // Treat as unreachable-for-health so the verdict stays meaningful.\n reachError = error.message;\n } else {\n throw error;\n }\n }\n\n if (!health) {\n const verdict: Verdict = \"unreachable\";\n if (ctx.json) {\n ctx.out.json({\n ok: false,\n verdict,\n baseUrl,\n error: reachError ?? \"unreachable\",\n });\n process.exit(1);\n }\n ctx.out.note(\n [\n `${color.red(\"●\")} ${color.bold(\"unreachable\")}`,\n \"\",\n reachError ?? `could not reach ${baseUrl}`,\n \"\",\n color.dim(\"Is the instance running? Check --url / HOGSEND_API_URL.\"),\n ].join(\"\\n\"),\n \"Doctor\",\n );\n ctx.out.outro(color.red(\"doctor: unreachable\"));\n process.exit(1);\n }\n\n const verdict = toVerdict(health.status);\n const ok = verdict === \"ok\";\n\n if (ctx.json) {\n ctx.out.json({\n ok,\n verdict,\n baseUrl,\n version: health.version,\n uptime: health.uptime,\n timestamp: health.timestamp,\n components: health.components,\n schema: health.schema,\n });\n if (!ok) process.exit(1);\n return;\n }\n\n // Human render.\n const badge = `${color.bgMagenta(color.black(\" hogsend \"))} doctor`;\n ctx.out.intro(badge);\n\n const verdictColor =\n verdict === \"ok\"\n ? color.green\n : verdict === \"degraded\"\n ? color.red\n : color.yellow;\n\n const lines = [\n `${verdictColor(\"●\")} ${color.bold(verdict)}`,\n color.dim(\n `${baseUrl} v${health.version} up ${Math.round(health.uptime)}s`,\n ),\n \"\",\n color.bold(\"Components\"),\n ` database ${componentSymbol(health.components.database.status)}${\n health.components.database.latencyMs !== undefined\n ? color.dim(` ${health.components.database.latencyMs}ms`)\n : \"\"\n }`,\n ` redis ${componentSymbol(health.components.redis.status)}${\n health.components.redis.latencyMs !== undefined\n ? color.dim(` ${health.components.redis.latencyMs}ms`)\n : \"\"\n }`,\n \"\",\n color.bold(\"Schema\"),\n ` ${trackLine(\"engine\", health.schema.engine)}`,\n ` ${trackLine(\"client\", health.schema.client)}`,\n ];\n\n ctx.out.note(lines.join(\"\\n\"), \"Doctor\");\n\n if (ok) {\n ctx.out.outro(color.green(\"doctor: ok\"));\n return;\n }\n\n ctx.out.outro(verdictColor(`doctor: ${verdict}`));\n process.exit(1);\n}\n\nexport const doctorCommand: Command = {\n name: \"doctor\",\n summary: \"Probe a running instance's health (GET /v1/health)\",\n usage,\n run,\n};\n","import { existsSync, realpathSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join, sep } from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport { EjectError, eject } from \"../eject.js\";\nimport { color } from \"../lib/output.js\";\nimport type { Command, CommandContext } from \"./types.js\";\n\nconst usage = `hogsend eject <package> [--force] [--cwd <dir>]\n\nCopy a @hogsend/* package's source into vendor/<name> and rewrite the consumer\ndependency to file:./vendor/<name>. Every other dependency keeps upgrading.\n\nOptions:\n --force Overwrite an existing vendor/<name>.\n --cwd <dir> Consumer repo root (defaults to the current directory).\n -h, --help Show this help.\n\nAfter ejecting, run: pnpm install`;\n\n/**\n * Resolve the on-disk source directory for an installed package. Strategy 1:\n * probe node_modules/<pkg>/package.json (following pnpm/workspace symlinks).\n * Strategy 2: resolve the package entry via createRequire and walk up.\n */\nfunction resolveSourceDir(pkg: string, consumerRoot: string): string | null {\n const direct = join(consumerRoot, \"node_modules\", pkg, \"package.json\");\n if (existsSync(direct)) {\n return dirname(realpathSync(direct));\n }\n const require = createRequire(`${consumerRoot}${sep}`);\n try {\n const entry = require.resolve(pkg);\n let dir = dirname(entry);\n while (dir !== dirname(dir)) {\n if (existsSync(join(dir, \"package.json\"))) return dir;\n dir = dirname(dir);\n }\n } catch {\n // fall through\n }\n return null;\n}\n\nasync function run(ctx: CommandContext): Promise<void> {\n const { values, positionals } = parseArgs({\n args: ctx.argv,\n allowPositionals: true,\n options: {\n force: { type: \"boolean\", default: false },\n cwd: { type: \"string\" },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n });\n\n if (values.help) {\n ctx.out.log(usage);\n return;\n }\n\n const pkg = positionals[0];\n if (!pkg) {\n ctx.out.fail(\n \"eject requires a package name, e.g. hogsend eject @hogsend/engine\",\n );\n }\n\n const consumerRoot = values.cwd ?? process.cwd();\n const sourceDir = resolveSourceDir(pkg, consumerRoot);\n if (!sourceDir) {\n ctx.out.fail(\n `cannot resolve ${pkg} from ${consumerRoot}. Is it installed? Run pnpm install first.`,\n );\n }\n\n try {\n const result = await ctx.out.step(`Ejecting ${pkg}`, () =>\n eject({ pkg, consumerRoot, sourceDir, force: values.force }),\n );\n if (ctx.json) {\n ctx.out.json(result);\n return;\n }\n ctx.out.note(\n [\n `copied ${result.copiedFiles} files -> ${result.vendorPath}`,\n `dependency ${result.depSpecBefore} -> ${color.cyan(result.depSpecAfter)}`,\n \"\",\n `Now run: ${color.cyan(result.followUp)}`,\n ].join(\"\\n\"),\n `Ejected ${result.pkg}`,\n );\n } catch (error) {\n if (error instanceof EjectError) {\n ctx.out.fail(error.message);\n }\n throw error;\n }\n}\n\nexport const ejectCommand: Command = {\n name: \"eject\",\n summary: \"Vendor a @hogsend/* package into vendor/<name>\",\n usage,\n run,\n};\n","import { existsSync } from \"node:fs\";\nimport { cp, readFile, rm, stat, writeFile } from \"node:fs/promises\";\nimport { basename, join, relative, sep } from \"node:path\";\n\n/** Options for {@link eject}. */\nexport interface EjectOptions {\n /** Scoped package name to eject, e.g. \"@hogsend/engine\". */\n pkg: string;\n /** Consumer repo root (the dir containing the consumer package.json). */\n consumerRoot: string;\n /**\n * Where the package source currently lives (the workspace/registry copy).\n * In-monorepo: <repoRoot>/packages/<name>. In a scaffolded app it is the\n * resolved node_modules path. The caller resolves this; eject() never\n * guesses it.\n */\n sourceDir: string;\n /** Overwrite an existing vendor/<name>. */\n force?: boolean;\n}\n\n/** Result of a successful {@link eject}. */\nexport interface EjectResult {\n pkg: string;\n /** Absolute path to vendor/<name>. */\n vendorPath: string;\n /** The dep spec before the rewrite, e.g. \"workspace:^\". */\n depSpecBefore: string;\n /** The dep spec after the rewrite, \"file:./vendor/<name>\". */\n depSpecAfter: string;\n /** Number of files copied into vendor/<name>. */\n copiedFiles: number;\n /** The install command the operator must run next. */\n followUp: string;\n}\n\n/** Typed failure thrown by {@link eject} for expected, user-facing errors. */\nexport class EjectError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"EjectError\";\n }\n}\n\n/** Directory/file names excluded from the vendor copy. */\nconst EXCLUDED_NAMES = new Set([\n \"node_modules\",\n \"dist\",\n \".turbo\",\n \".changeset\",\n \"CHANGELOG.md\",\n]);\n\ninterface PackageJson {\n private?: boolean;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n [key: string]: unknown;\n}\n\ntype DepMap = \"dependencies\" | \"devDependencies\";\n\nasync function readPackageJson(file: string): Promise<PackageJson> {\n const raw = await readFile(file, \"utf8\");\n return JSON.parse(raw) as PackageJson;\n}\n\nasync function writePackageJson(\n file: string,\n value: PackageJson,\n): Promise<void> {\n await writeFile(file, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n}\n\n/**\n * Pure eject: copies a single package's source into the consumer's\n * `vendor/<name>` and rewrites only that consumer dependency to a\n * `file:./vendor/<name>` link. Every other dependency is left untouched, so\n * the rest of the `@hogsend/*` set keeps upgrading via `pnpm up`.\n *\n * This function performs filesystem operations only — it never runs an install\n * and never resolves `sourceDir` itself, which keeps it hermetically testable.\n */\nexport async function eject(opts: EjectOptions): Promise<EjectResult> {\n const { pkg, consumerRoot, sourceDir, force = false } = opts;\n\n // 1. Resolve names.\n const vendorName = basename(pkg);\n const vendorPath = join(consumerRoot, \"vendor\", vendorName);\n\n // 2. Validate the consumer dependency exists (before any side effects).\n const consumerPkgPath = join(consumerRoot, \"package.json\");\n const consumerPkg = await readPackageJson(consumerPkgPath);\n let depMap: DepMap | undefined;\n let depSpecBefore: string | undefined;\n if (consumerPkg.dependencies?.[pkg] !== undefined) {\n depMap = \"dependencies\";\n depSpecBefore = consumerPkg.dependencies[pkg];\n } else if (consumerPkg.devDependencies?.[pkg] !== undefined) {\n depMap = \"devDependencies\";\n depSpecBefore = consumerPkg.devDependencies[pkg];\n }\n if (!depMap || depSpecBefore === undefined) {\n throw new EjectError(\n `${pkg} is not a dependency of the consumer package.json`,\n );\n }\n\n // 3. Guard the vendor dir.\n if (existsSync(vendorPath)) {\n if (!force) {\n throw new EjectError(\n `vendor/${vendorName} already exists; pass --force to overwrite`,\n );\n }\n await rm(vendorPath, { recursive: true, force: true });\n }\n\n // 4. Copy source with an exclude filter. Returning false for a directory\n // prunes the whole subtree (Node 22 fs.cp filter semantics).\n let copiedFiles = 0;\n await cp(sourceDir, vendorPath, {\n recursive: true,\n filter: (source) => {\n const rel = relative(sourceDir, source);\n if (rel === \"\") {\n return true;\n }\n const segments = rel.split(sep);\n const name = basename(rel);\n // Exclude any path segment that is an excluded name (prunes subtrees).\n if (segments.some((segment) => EXCLUDED_NAMES.has(segment))) {\n return false;\n }\n if (name.endsWith(\".test.ts\")) {\n return false;\n }\n return true;\n },\n });\n\n // Count copied files (directories excluded) for the result summary.\n copiedFiles = await countFiles(vendorPath);\n\n // 5. Sanitize the vendored package.json.\n const vendoredPkgPath = join(vendorPath, \"package.json\");\n const vendoredPkg = await readPackageJson(vendoredPkgPath);\n if (vendoredPkg.private === true) {\n delete vendoredPkg.private;\n }\n await writePackageJson(vendoredPkgPath, vendoredPkg);\n\n // 6. Rewrite the consumer dep in place (preserving key order).\n const depSpecAfter = `file:./vendor/${vendorName}`;\n // biome-ignore lint/style/noNonNullAssertion: depMap validated above.\n consumerPkg[depMap]![pkg] = depSpecAfter;\n await writePackageJson(consumerPkgPath, consumerPkg);\n\n // 7. Return the result.\n return {\n pkg,\n vendorPath,\n depSpecBefore,\n depSpecAfter,\n copiedFiles,\n followUp: \"pnpm install\",\n };\n}\n\n/** Recursively counts regular files under a directory. */\nasync function countFiles(dir: string): Promise<number> {\n const { readdir } = await import(\"node:fs/promises\");\n let count = 0;\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n count += await countFiles(full);\n } else if (entry.isFile()) {\n count += 1;\n } else {\n const info = await stat(full);\n if (info.isFile()) {\n count += 1;\n }\n }\n }\n return count;\n}\n","import { parseArgs } from \"node:util\";\nimport { isHttpError } from \"../lib/http.js\";\nimport { color } from \"../lib/output.js\";\nimport type { Command, CommandContext } from \"./types.js\";\n\nconst usage = `hogsend events <userId> [options]\n\nStream the event history for a single user, newest first. Wraps\nGET /v1/admin/events?userId=<userId>.\n\nArguments:\n <userId> The user (distinct) id to fetch events for. Required.\n\nOptions:\n --event <name> Filter to a single event name.\n --from <iso> Only events at/after this ISO-8601 timestamp.\n --to <iso> Only events at/before this ISO-8601 timestamp.\n --limit <n> Max events to return (1-100, default 50).\n --offset <n> Pagination offset (default 0).\n --json Emit machine-readable JSON only.\n -h, --help Show this help.\n\nExamples:\n hogsend events user_123\n hogsend events user_123 --event signup --limit 10\n hogsend events user_123 --from 2026-01-01T00:00:00Z --json`;\n\ninterface UserEvent {\n id: string;\n userId: string;\n event: string;\n properties: Record<string, unknown> | null;\n occurredAt: string;\n}\n\ninterface EventsResponse {\n events: UserEvent[];\n total: number;\n limit: number;\n offset: number;\n}\n\nasync function run(ctx: CommandContext): Promise<void> {\n const { values, positionals } = parseArgs({\n args: ctx.argv,\n allowPositionals: true,\n options: {\n event: { type: \"string\" },\n from: { type: \"string\" },\n to: { type: \"string\" },\n limit: { type: \"string\" },\n offset: { type: \"string\" },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n });\n\n if (values.help) {\n ctx.out.log(usage);\n return;\n }\n\n const userId = positionals[0];\n if (!userId) {\n ctx.out.fail(\"events requires a userId, e.g. hogsend events user_123\");\n }\n\n const limit = parseNumber(values.limit, \"limit\", ctx);\n const offset = parseNumber(values.offset, \"offset\", ctx);\n\n const query = {\n userId,\n event: values.event,\n from: values.from,\n to: values.to,\n limit,\n offset,\n };\n\n let data: EventsResponse;\n try {\n data = await ctx.out.step(`Fetching events for ${userId}`, () =>\n ctx.http.get<EventsResponse>(\"/v1/admin/events\", query),\n );\n } catch (error) {\n if (isHttpError(error)) {\n ctx.out.fail(error.message);\n }\n throw error;\n }\n\n if (ctx.json) {\n ctx.out.json(data);\n return;\n }\n\n ctx.out.intro(`${color.bgMagenta(color.black(\" hogsend \"))} events`);\n\n if (data.events.length === 0) {\n ctx.out.note(\n `No events found for ${color.cyan(userId)}.`,\n \"Empty event stream\",\n );\n ctx.out.outro(color.dim(\"Nothing to show.\"));\n return;\n }\n\n const rows = data.events.map((e) => ({\n occurredAt: e.occurredAt,\n event: e.event,\n properties: summarizeProps(e.properties),\n id: e.id,\n }));\n ctx.out.table(rows, [\"occurredAt\", \"event\", \"properties\", \"id\"]);\n\n const shown = data.events.length;\n const through = data.offset + shown;\n ctx.out.outro(\n `${color.green(String(shown))} event${shown === 1 ? \"\" : \"s\"} ` +\n color.dim(`(${data.offset + 1}-${through} of ${data.total})`),\n );\n}\n\n/**\n * Parse an optional numeric flag. Returns undefined when absent (lets the\n * server apply its default); fails on a non-numeric value.\n */\nfunction parseNumber(\n raw: string | undefined,\n name: string,\n ctx: CommandContext,\n): number | undefined {\n if (raw === undefined) return undefined;\n const n = Number(raw);\n if (!Number.isFinite(n)) {\n ctx.out.fail(`--${name} must be a number, got \"${raw}\"`);\n }\n return n;\n}\n\n/** Compact a properties object into a single-line preview for the table. */\nfunction summarizeProps(props: Record<string, unknown> | null): string {\n if (!props) return \"\";\n const keys = Object.keys(props);\n if (keys.length === 0) return \"\";\n const preview = JSON.stringify(props);\n return preview.length > 60 ? `${preview.slice(0, 57)}...` : preview;\n}\n\nexport const eventsCommand: Command = {\n name: \"events\",\n summary: \"Stream a single user's event history\",\n usage,\n run,\n};\n","import { parseArgs } from \"node:util\";\nimport { isHttpError } from \"../lib/http.js\";\nimport { color } from \"../lib/output.js\";\nimport type { Command, CommandContext } from \"./types.js\";\n\nconst usage = `hogsend journeys <subcommand> [options]\n\nInspect and toggle journeys via the admin API (/v1/admin/journeys).\n\nSubcommands:\n list List journeys with status, trigger, and state counts.\n get <id> Show one journey: trigger, exitOn, counts, recent states.\n enable <id> Enable a journey (PATCH { enabled: true }).\n disable <id> Disable a journey (PATCH { enabled: false }).\n\nOptions:\n list:\n --enabled <true|false> Filter by enabled state.\n --limit <n> Page size (1-100, default 50).\n --offset <n> Page offset (default 0).\n --json Emit machine-readable JSON only.\n -h, --help Show this help.\n\nExamples:\n hogsend journeys list --enabled true\n hogsend journeys get activation-welcome --json\n hogsend journeys disable churn-prevention`;\n\n/** Shape returned by GET /v1/admin/journeys. */\ninterface JourneyCounts {\n active: number;\n waiting: number;\n completed: number;\n failed: number;\n exited: number;\n}\n\ninterface JourneyListItem {\n id: string;\n name: string;\n description?: string;\n enabled: boolean;\n trigger: { event: string };\n entryLimit: string;\n counts: JourneyCounts;\n}\n\ninterface ListResponse {\n journeys: JourneyListItem[];\n total: number;\n limit: number;\n offset: number;\n}\n\ninterface JourneyState {\n id: string;\n userId: string;\n userEmail: string;\n journeyId: string;\n currentNodeId: string;\n status: string;\n errorMessage: string | null;\n entryCount: number;\n completedAt: string | null;\n exitedAt: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface JourneyDetail extends Omit<JourneyListItem, \"trigger\"> {\n trigger: { event: string; where?: Record<string, unknown>[] };\n exitOn?: { event: string; where?: Record<string, unknown>[] }[];\n suppress: Record<string, number>;\n recentStates: JourneyState[];\n}\n\ninterface GetResponse {\n journey: JourneyDetail;\n}\n\ninterface PatchResponse {\n journey: { id: string; name: string; enabled: boolean; updatedAt: string };\n}\n\nfunction badge(): string {\n return `${color.bgMagenta(color.black(\" hogsend \"))} journeys`;\n}\n\nfunction statusColor(enabled: boolean): string {\n return enabled ? color.green(\"enabled\") : color.yellow(\"disabled\");\n}\n\nasync function runList(ctx: CommandContext): Promise<void> {\n const { values } = parseArgs({\n args: ctx.argv,\n allowPositionals: true,\n options: {\n enabled: { type: \"string\" },\n limit: { type: \"string\" },\n offset: { type: \"string\" },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n });\n\n if (values.help) {\n ctx.out.log(usage);\n return;\n }\n\n if (\n values.enabled !== undefined &&\n ![\"true\", \"false\"].includes(values.enabled)\n ) {\n ctx.out.fail(\"--enabled must be 'true' or 'false'\");\n }\n\n const query = {\n enabled: values.enabled,\n limit: values.limit,\n offset: values.offset,\n };\n\n if (!ctx.json) ctx.out.intro(badge());\n\n const data = await ctx.out.step(\"Fetching journeys\", () =>\n ctx.http.get<ListResponse>(\"/v1/admin/journeys\", query),\n );\n\n if (ctx.json) {\n ctx.out.json(data);\n return;\n }\n\n if (data.journeys.length === 0) {\n ctx.out.note(\"No journeys matched.\", \"Journeys\");\n } else {\n ctx.out.table(\n data.journeys.map((j) => ({\n id: j.id,\n name: j.name,\n status: statusColor(j.enabled),\n trigger: j.trigger.event,\n active: j.counts.active,\n waiting: j.counts.waiting,\n completed: j.counts.completed,\n failed: j.counts.failed,\n })),\n [\n \"id\",\n \"name\",\n \"status\",\n \"trigger\",\n \"active\",\n \"waiting\",\n \"completed\",\n \"failed\",\n ],\n );\n }\n\n ctx.out.outro(\n `${data.journeys.length} of ${data.total} journey(s) — offset ${data.offset}, limit ${data.limit}`,\n );\n}\n\nasync function runGet(\n ctx: CommandContext,\n id: string | undefined,\n): Promise<void> {\n if (!id) {\n ctx.out.fail(\n \"journeys get requires a journey id, e.g. hogsend journeys get activation-welcome\",\n );\n }\n\n if (!ctx.json) ctx.out.intro(badge());\n\n const data = await ctx.out.step(`Fetching journey ${id}`, () =>\n ctx.http.get<GetResponse>(\n `/v1/admin/journeys/${encodeURIComponent(id as string)}`,\n ),\n );\n\n if (ctx.json) {\n ctx.out.json(data);\n return;\n }\n\n const j = data.journey;\n ctx.out.kv(\n {\n id: j.id,\n name: j.name,\n description: j.description ?? \"\",\n status: statusColor(j.enabled),\n trigger: j.trigger.event,\n entryLimit: j.entryLimit,\n exitOn: j.exitOn?.map((e) => e.event).join(\", \") ?? \"(none)\",\n },\n \"Journey\",\n );\n\n ctx.out.kv(\n {\n active: j.counts.active,\n waiting: j.counts.waiting,\n completed: j.counts.completed,\n failed: j.counts.failed,\n exited: j.counts.exited,\n },\n \"Counts\",\n );\n\n if (j.recentStates.length === 0) {\n ctx.out.note(\"No recent journey instances.\", \"Recent states\");\n } else {\n ctx.out.table(\n j.recentStates.map((s) => ({\n userId: s.userId,\n email: s.userEmail,\n status: s.status,\n node: s.currentNodeId,\n updatedAt: s.updatedAt,\n })),\n [\"userId\", \"email\", \"status\", \"node\", \"updatedAt\"],\n );\n }\n\n ctx.out.outro(`Journey ${j.id} is ${j.enabled ? \"enabled\" : \"disabled\"}.`);\n}\n\nasync function runToggle(\n ctx: CommandContext,\n id: string | undefined,\n enabled: boolean,\n): Promise<void> {\n const verb = enabled ? \"enable\" : \"disable\";\n if (!id) {\n ctx.out.fail(\n `journeys ${verb} requires a journey id, e.g. hogsend journeys ${verb} activation-welcome`,\n );\n }\n\n if (!ctx.json) ctx.out.intro(badge());\n\n const data = await ctx.out.step(\n `${enabled ? \"Enabling\" : \"Disabling\"} ${id}`,\n () =>\n ctx.http.patch<PatchResponse>(\n `/v1/admin/journeys/${encodeURIComponent(id as string)}`,\n { enabled },\n ),\n );\n\n if (ctx.json) {\n ctx.out.json(data);\n return;\n }\n\n const j = data.journey;\n ctx.out.note(\n [\n `${color.bold(j.name)} (${j.id})`,\n `status: ${statusColor(j.enabled)}`,\n `updated: ${j.updatedAt}`,\n ].join(\"\\n\"),\n `Journey ${enabled ? \"enabled\" : \"disabled\"}`,\n );\n ctx.out.outro(`${j.id} is now ${statusColor(j.enabled)}.`);\n}\n\nasync function run(ctx: CommandContext): Promise<void> {\n const sub = ctx.argv[0];\n // argv after the subcommand token — positionals/flags for the subcommand.\n const rest = ctx.argv.slice(1);\n const subCtx: CommandContext = { ...ctx, argv: rest };\n\n try {\n switch (sub) {\n case \"list\":\n await runList(subCtx);\n return;\n case \"get\": {\n const id = rest.find((a) => !a.startsWith(\"-\"));\n if (rest.includes(\"--help\") || rest.includes(\"-h\")) {\n ctx.out.log(usage);\n return;\n }\n await runGet(subCtx, id);\n return;\n }\n case \"enable\": {\n if (rest.includes(\"--help\") || rest.includes(\"-h\")) {\n ctx.out.log(usage);\n return;\n }\n await runToggle(\n subCtx,\n rest.find((a) => !a.startsWith(\"-\")),\n true,\n );\n return;\n }\n case \"disable\": {\n if (rest.includes(\"--help\") || rest.includes(\"-h\")) {\n ctx.out.log(usage);\n return;\n }\n await runToggle(\n subCtx,\n rest.find((a) => !a.startsWith(\"-\")),\n false,\n );\n return;\n }\n case undefined:\n ctx.out.fail(\n `journeys requires a subcommand (list|get|enable|disable). Run: hogsend journeys --help`,\n );\n return;\n default:\n ctx.out.fail(\n `unknown journeys subcommand '${sub}'. Expected list|get|enable|disable.`,\n );\n return;\n }\n } catch (error) {\n if (isHttpError(error)) {\n if (error.status === 404) {\n ctx.out.fail(\"journey not found\");\n }\n ctx.out.fail(error.message);\n }\n throw error;\n }\n}\n\nexport const journeysCommand: Command = {\n name: \"journeys\",\n summary: \"List, inspect, enable, and disable journeys\",\n usage,\n run,\n};\n","import { spawnSync } from \"node:child_process\";\nimport { parseArgs } from \"node:util\";\nimport { color } from \"../lib/output.js\";\nimport type { Command, CommandContext } from \"./types.js\";\n\nconst usage = `hogsend patch <package> [--cwd <dir>]\n\nThin wrapper over pnpm's native patch flow. Runs \\`pnpm patch <package>\\`, which\nextracts the package into a temp dir and prints the path to edit. After editing,\ncommit the patch with the command pnpm prints (\\`pnpm patch-commit <dir>\\`).\n\nThis does NOT replace scripts/patch-check.sh (the patch re-apply contract).\n\nOptions:\n --cwd <dir> Project root to run pnpm in (defaults to current directory).\n -h, --help Show this help.`;\n\nasync function run(ctx: CommandContext): Promise<void> {\n const { values, positionals } = parseArgs({\n args: ctx.argv,\n allowPositionals: true,\n options: {\n cwd: { type: \"string\" },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n });\n\n if (values.help) {\n ctx.out.log(usage);\n return;\n }\n\n const pkg = positionals[0];\n if (!pkg) {\n ctx.out.fail(\n \"patch requires a package name, e.g. hogsend patch @hogsend/engine\",\n );\n }\n\n const cwd = values.cwd ?? process.cwd();\n\n // pnpm patch is interactive-ish (prints an editable dir). Stream it through\n // unless --json, where we suppress chrome and report the spawn result only.\n const result = spawnSync(\"pnpm\", [\"patch\", pkg], {\n cwd,\n stdio: ctx.json ? \"ignore\" : \"inherit\",\n });\n\n if (ctx.json) {\n ctx.out.json({\n package: pkg,\n command: `pnpm patch ${pkg}`,\n status: result.status,\n ok: result.status === 0,\n });\n if (result.status !== 0) process.exit(1);\n return;\n }\n\n if (result.status !== 0) {\n ctx.out.fail(`pnpm patch ${pkg} exited with code ${result.status ?? \"?\"}`);\n }\n\n ctx.out.note(\n [\n \"pnpm extracted the package to a temp dir (printed above).\",\n \"Edit the files, then commit the patch:\",\n \"\",\n color.cyan(\"pnpm patch-commit <dir>\"),\n ].join(\"\\n\"),\n \"Next steps\",\n );\n}\n\nexport const patchCommand: Command = {\n name: \"patch\",\n summary: \"Patch a package via pnpm's native patch flow\",\n usage,\n run,\n};\n","import { spawnSync } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport { copyFileSync, existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport { confirm } from \"@clack/prompts\";\nimport { color } from \"../lib/output.js\";\nimport { bail } from \"../lib/prompt.js\";\nimport type { Command, CommandContext } from \"./types.js\";\n\nconst usage = `hogsend setup [--cwd <dir>] [--yes] [--json]\n\nInteractive local onboarding for a scaffolded Hogsend app. Mirrors the\ncreate-hogsend \"next steps\":\n\n 1. docker compose up -d # Postgres + Redis + Hatchet-Lite\n 2. cp .env.example .env (if missing)\n 3. generate a BETTER_AUTH_SECRET (if still the placeholder)\n 4. pnpm db:migrate # engine track then client track\n\nOptions:\n --cwd <dir> Project root to run in (defaults to the current directory).\n --yes, -y Skip confirmation prompts (assume yes). Implied by --json.\n --json Run non-interactively and emit a single JSON result document.\n -h, --help Show this help.\n\nRun ${color.cyan(\"hogsend doctor\")} afterwards to verify the instance is healthy.`;\n\n/** Generate a 64-char hex secret (32 bytes) for BETTER_AUTH_SECRET. */\nfunction generateSecret(): string {\n return randomBytes(32).toString(\"hex\");\n}\n\nconst SECRET_KEY = \"BETTER_AUTH_SECRET\";\nconst PLACEHOLDER_PREFIX = \"change-me\";\n\ninterface StepResult {\n step: string;\n status: \"ok\" | \"skipped\" | \"failed\";\n detail: string;\n}\n\n/**\n * Ensure a `.env` exists (copying `.env.example` when absent) and that\n * BETTER_AUTH_SECRET holds a real generated value rather than the placeholder.\n * Pure-ish: only touches the filesystem, returns a structured result.\n */\nfunction ensureEnv(cwd: string): { copied: StepResult; secret: StepResult } {\n const envPath = join(cwd, \".env\");\n const examplePath = join(cwd, \".env.example\");\n\n let copied: StepResult;\n if (existsSync(envPath)) {\n copied = {\n step: \"env\",\n status: \"skipped\",\n detail: \".env already exists\",\n };\n } else if (existsSync(examplePath)) {\n copyFileSync(examplePath, envPath);\n copied = {\n step: \"env\",\n status: \"ok\",\n detail: \"copied .env.example -> .env\",\n };\n } else {\n copied = {\n step: \"env\",\n status: \"failed\",\n detail: \"no .env and no .env.example to copy from\",\n };\n return {\n copied,\n secret: {\n step: \"secret\",\n status: \"skipped\",\n detail: \"skipped — no .env\",\n },\n };\n }\n\n // (Re)read the file we just ensured exists and refresh the secret if it is\n // missing or still the scaffold placeholder. Never overwrite a real secret.\n let raw: string;\n try {\n raw = readFileSync(envPath, \"utf8\");\n } catch (err) {\n return {\n copied,\n secret: {\n step: \"secret\",\n status: \"failed\",\n detail: `could not read .env: ${err instanceof Error ? err.message : String(err)}`,\n },\n };\n }\n\n const lines = raw.split(/\\r?\\n/);\n const idx = lines.findIndex((l) =>\n l\n .replace(/^export\\s+/, \"\")\n .trimStart()\n .startsWith(`${SECRET_KEY}=`),\n );\n const existingLine = idx === -1 ? undefined : lines[idx];\n const current =\n existingLine === undefined\n ? undefined\n : existingLine.slice(existingLine.indexOf(\"=\") + 1).trim();\n const isPlaceholder =\n current === undefined ||\n current === \"\" ||\n current.startsWith(PLACEHOLDER_PREFIX);\n\n if (!isPlaceholder) {\n return {\n copied,\n secret: {\n step: \"secret\",\n status: \"skipped\",\n detail: `${SECRET_KEY} already set`,\n },\n };\n }\n\n const secret = generateSecret();\n const newLine = `${SECRET_KEY}=${secret}`;\n if (idx === -1) {\n if (raw.length > 0 && !raw.endsWith(\"\\n\")) lines.push(\"\");\n lines.push(newLine);\n } else {\n lines[idx] = newLine;\n }\n writeFileSync(envPath, lines.join(\"\\n\"));\n\n return {\n copied,\n secret: {\n step: \"secret\",\n status: \"ok\",\n detail: `generated ${SECRET_KEY} (64-char hex)`,\n },\n };\n}\n\n/** Run a shell command, capturing exit status. */\nfunction runCmd(\n cmd: string,\n args: string[],\n cwd: string,\n json: boolean,\n): { status: number | null; ok: boolean } {\n const result = spawnSync(cmd, args, {\n cwd,\n // In json mode stay silent (we report structured status); otherwise stream\n // so the user sees docker / migration output inline.\n stdio: json ? \"ignore\" : \"inherit\",\n });\n return { status: result.status, ok: result.status === 0 };\n}\n\nasync function run(ctx: CommandContext): Promise<void> {\n const { values } = parseArgs({\n args: ctx.argv,\n allowPositionals: true,\n options: {\n cwd: { type: \"string\" },\n yes: { type: \"boolean\", short: \"y\", default: false },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n });\n\n if (values.help) {\n ctx.out.log(usage);\n return;\n }\n\n const cwd = values.cwd ?? process.cwd();\n\n if (!existsSync(join(cwd, \"package.json\"))) {\n ctx.out.fail(\n `no package.json in ${cwd} — run setup from a scaffolded Hogsend app (or pass --cwd).`,\n );\n }\n\n const hasCompose =\n existsSync(join(cwd, \"docker-compose.yml\")) ||\n existsSync(join(cwd, \"docker-compose.yaml\")) ||\n existsSync(join(cwd, \"compose.yml\")) ||\n existsSync(join(cwd, \"compose.yaml\"));\n\n // --json implies non-interactive; in TTY human mode we confirm first.\n const skipConfirm = ctx.json || values.yes;\n\n if (!ctx.json) {\n ctx.out.intro(\n `${color.bgMagenta(color.black(\" hogsend \"))} ${color.dim(\"local onboarding\")}`,\n );\n }\n\n if (ctx.out.interactive && !skipConfirm) {\n const proceed = bail(\n await confirm({\n message: `Set up local infra in ${color.cyan(cwd)}? (docker compose up, .env, db:migrate)`,\n }),\n );\n if (!proceed) {\n ctx.out.outro(color.dim(\"Nothing changed.\"));\n return;\n }\n }\n\n const results: StepResult[] = [];\n\n // 1. docker compose up -d\n if (hasCompose) {\n const docker = await ctx.out.step(\n \"Starting infra (docker compose up -d)\",\n async () => runCmd(\"docker\", [\"compose\", \"up\", \"-d\"], cwd, ctx.json),\n );\n results.push({\n step: \"docker\",\n status: docker.ok ? \"ok\" : \"failed\",\n detail: docker.ok\n ? \"Postgres + Redis + Hatchet-Lite up\"\n : `docker compose exited with code ${docker.status ?? \"?\"}`,\n });\n } else {\n results.push({\n step: \"docker\",\n status: \"skipped\",\n detail: \"no docker-compose file found\",\n });\n }\n\n // 2 + 3. .env + secret (synchronous fs work, wrapped in a step for the spinner)\n const env = await ctx.out.step(\"Preparing .env + auth secret\", async () =>\n ensureEnv(cwd),\n );\n results.push(env.copied, env.secret);\n\n // 4. db:migrate (only attempt if docker didn't hard-fail; still try if skipped)\n const dockerFailed = results.some(\n (r) => r.step === \"docker\" && r.status === \"failed\",\n );\n if (dockerFailed) {\n results.push({\n step: \"migrate\",\n status: \"skipped\",\n detail:\n \"skipped — docker compose failed; bring infra up then run pnpm db:migrate\",\n });\n } else {\n const migrate = await ctx.out.step(\n \"Running migrations (pnpm db:migrate)\",\n async () => runCmd(\"pnpm\", [\"db:migrate\"], cwd, ctx.json),\n );\n results.push({\n step: \"migrate\",\n status: migrate.ok ? \"ok\" : \"failed\",\n detail: migrate.ok\n ? \"engine + client migrations applied\"\n : `pnpm db:migrate exited with code ${migrate.status ?? \"?\"}`,\n });\n }\n\n const failed = results.filter((r) => r.status === \"failed\");\n const ok = failed.length === 0;\n\n if (ctx.json) {\n ctx.out.json({\n ok,\n cwd,\n steps: results,\n });\n if (!ok) process.exit(1);\n return;\n }\n\n // Human summary.\n ctx.out.table(\n results.map((r) => ({\n step: r.step,\n status:\n r.status === \"ok\"\n ? color.green(\"ok\")\n : r.status === \"skipped\"\n ? color.dim(\"skipped\")\n : color.red(\"failed\"),\n detail: r.detail,\n })),\n [\"step\", \"status\", \"detail\"],\n );\n\n ctx.out.note(\n [\n `${color.cyan(\"pnpm dev\")} ${color.dim(\"# HTTP API on :3002\")}`,\n `${color.cyan(\"pnpm worker:dev\")} ${color.dim(\"# Hatchet worker, 2nd terminal\")}`,\n \"\",\n `${color.dim(\"Verify with\")} ${color.cyan(\"hogsend doctor\")}${color.dim(\".\")}`,\n `${color.dim(\"Grab HATCHET_CLIENT_TOKEN at\")} ${color.cyan(\"http://localhost:8888\")} ${color.dim(\"and set it in .env.\")}`,\n ].join(\"\\n\"),\n \"Next steps\",\n );\n\n if (!ok) {\n ctx.out.fail(\n `${failed.length} step(s) failed — see the table above. Fix and re-run hogsend setup.`,\n );\n }\n\n ctx.out.outro(\n `${color.green(\"Done.\")} ${color.dim(\"Local infra is up — go write a journey.\")}`,\n );\n}\n\nexport const setupCommand: Command = {\n name: \"setup\",\n summary: \"Local onboarding: docker compose up, gen secret, db:migrate\",\n usage,\n run,\n};\n","import { cancel, isCancel } from \"@clack/prompts\";\n\n/**\n * Guard a clack prompt result. clack returns a cancellation symbol when the\n * user hits Ctrl-C / Esc; this unwraps the value or aborts the whole CLI\n * cleanly (exit 0 — a deliberate cancel, not an error).\n *\n * Re-export clack's `text`/`select`/`confirm`/`multiselect` from\n * `@clack/prompts` directly in command files; wrap each call in `bail()`.\n */\nexport function bail<T>(value: T | symbol): T {\n if (isCancel(value)) {\n cancel(\"Cancelled.\");\n process.exit(0);\n }\n return value as T;\n}\n","import {\n cpSync,\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n statSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { parseArgs } from \"node:util\";\nimport { multiselect } from \"@clack/prompts\";\nimport { color } from \"../lib/output.js\";\nimport { bail } from \"../lib/prompt.js\";\nimport type { Command, CommandContext } from \"./types.js\";\n\nconst usage = `hogsend skills <subcommand> [options]\n\nManage the Claude Code skills bundled with @hogsend/cli. Bundled skills teach\nagents how to drive the hogsend CLI; \\`add\\` copies them into your project's\n./.claude/skills/<name>/ so Claude Code can discover them.\n\nSubcommands:\n list List bundled skills + whether each is installed.\n add [name] [--force] Copy a bundled skill into ./.claude/skills/<name>/.\n Omit name for an interactive multiselect (human),\n or copy all bundled skills (--json / non-interactive).\n\nOptions:\n --force Overwrite an already-installed skill.\n --json Emit machine-readable JSON only (implies non-interactive).\n -h, --help Show this help.\n\nExamples:\n hogsend skills list\n hogsend skills list --json\n hogsend skills add\n hogsend skills add hogsend-cli --force`;\n\n/**\n * Resolve the directory holding the bundled skills shipped in the tarball.\n * At runtime bin.js lives at <pkg>/dist/bin.js, so the skills dir (shipped via\n * package.json files[]) is one level up at <pkg>/skills.\n */\nfunction bundledSkillsDir(): string {\n return fileURLToPath(new URL(\"../skills\", import.meta.url));\n}\n\n/** Target directory for installed skills in the consumer project. */\nfunction installDir(cwd: string): string {\n return join(cwd, \".claude\", \"skills\");\n}\n\ninterface BundledSkill {\n name: string;\n description: string;\n installed: boolean;\n}\n\n/** A single line `key: value` reader for SKILL.md YAML frontmatter. */\nfunction readFrontmatterField(skillDir: string, field: string): string {\n const skillFile = join(skillDir, \"SKILL.md\");\n if (!existsSync(skillFile)) return \"\";\n // Tiny frontmatter scan — avoids a YAML dep. Reads only the top block.\n const raw = readFileSyncSafe(skillFile);\n const fmMatch = raw.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!fmMatch) return \"\";\n const block = fmMatch[1] ?? \"\";\n for (const line of block.split(\"\\n\")) {\n const m = line.match(/^([A-Za-z0-9_-]+):\\s*(.*)$/);\n if (m && m[1] === field) {\n return (m[2] ?? \"\").replace(/^[\"']|[\"']$/g, \"\").trim();\n }\n }\n return \"\";\n}\n\n/** Read a file as utf8, returning \"\" on any error (never throws). */\nfunction readFileSyncSafe(path: string): string {\n try {\n return readFileSync(path, \"utf8\");\n } catch {\n return \"\";\n }\n}\n\n/** Enumerate bundled skills (each is a subdir with a SKILL.md). */\nfunction listBundledSkills(cwd: string): BundledSkill[] {\n const dir = bundledSkillsDir();\n if (!existsSync(dir)) return [];\n const target = installDir(cwd);\n const entries = readdirSync(dir).filter((name) => {\n const full = join(dir, name);\n return statSync(full).isDirectory() && existsSync(join(full, \"SKILL.md\"));\n });\n return entries.sort().map((name) => ({\n name,\n description: readFrontmatterField(join(dir, name), \"description\"),\n installed: existsSync(join(target, name)),\n }));\n}\n\nfunction runList(ctx: CommandContext): void {\n const skills = listBundledSkills(process.cwd());\n\n if (ctx.json) {\n ctx.out.json({\n bundledSkillsDir: bundledSkillsDir(),\n installDir: installDir(process.cwd()),\n skills,\n });\n return;\n }\n\n ctx.out.intro(`${color.bgMagenta(color.black(\" hogsend \"))} skills`);\n if (skills.length === 0) {\n ctx.out.note(\n \"No bundled skills found in this package build.\",\n \"skills list\",\n );\n ctx.out.outro(\"Nothing to install.\");\n return;\n }\n ctx.out.table(\n skills.map((s) => ({\n name: s.name,\n installed: s.installed ? color.green(\"yes\") : color.dim(\"no\"),\n description:\n s.description.length > 60\n ? `${s.description.slice(0, 57)}...`\n : s.description,\n })),\n [\"name\", \"installed\", \"description\"],\n );\n ctx.out.outro(\n `Install with ${color.cyan(\"hogsend skills add <name>\")} (or just ${color.cyan(\"hogsend skills add\")}).`,\n );\n}\n\ninterface CopyResult {\n name: string;\n installed: boolean;\n skipped: boolean;\n path: string;\n}\n\n/** Copy one bundled skill into the project, honouring --force. */\nfunction copySkill(name: string, cwd: string, force: boolean): CopyResult {\n const src = join(bundledSkillsDir(), name);\n const dest = join(installDir(cwd), name);\n const exists = existsSync(dest);\n if (exists && !force) {\n return { name, installed: false, skipped: true, path: dest };\n }\n mkdirSync(installDir(cwd), { recursive: true });\n cpSync(src, dest, { recursive: true, force: true });\n return { name, installed: true, skipped: false, path: dest };\n}\n\nasync function runAdd(ctx: CommandContext, argv: string[]): Promise<void> {\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n force: { type: \"boolean\", default: false },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n });\n\n if (values.help) {\n ctx.out.log(usage);\n return;\n }\n\n const cwd = process.cwd();\n const bundled = listBundledSkills(cwd);\n if (bundled.length === 0) {\n ctx.out.fail(\"no bundled skills found in this package build\");\n }\n\n const requested = positionals[0];\n const force = Boolean(values.force);\n\n // Resolve which skills to install.\n let names: string[];\n if (requested) {\n const match = bundled.find((s) => s.name === requested);\n if (!match) {\n ctx.out.fail(\n `unknown skill \"${requested}\". Available: ${bundled.map((s) => s.name).join(\", \")}`,\n );\n }\n names = [requested];\n } else if (ctx.out.interactive) {\n const picked = bail(\n await multiselect({\n message: \"Which skills do you want to install?\",\n options: bundled.map((s) => ({\n value: s.name,\n label: s.name,\n hint: s.installed ? \"installed\" : undefined,\n })),\n required: true,\n }),\n ) as string[];\n names = picked;\n } else {\n // Non-interactive (json or non-TTY) with no name => install all.\n names = bundled.map((s) => s.name);\n }\n\n const results = names.map((name) => copySkill(name, cwd, force));\n\n if (ctx.json) {\n ctx.out.json({\n installDir: installDir(cwd),\n force,\n results,\n });\n return;\n }\n\n ctx.out.intro(`${color.bgMagenta(color.black(\" hogsend \"))} skills add`);\n for (const r of results) {\n if (r.skipped) {\n ctx.out.log(\n `${color.yellow(\"skip\")} ${r.name} ${color.dim(\"(already installed; use --force to overwrite)\")}`,\n );\n } else {\n ctx.out.log(`${color.green(\"✓\")} ${r.name} ${color.dim(`-> ${r.path}`)}`);\n }\n }\n const installedCount = results.filter((r) => r.installed).length;\n const skippedCount = results.filter((r) => r.skipped).length;\n ctx.out.outro(\n `Installed ${installedCount} skill${installedCount === 1 ? \"\" : \"s\"}` +\n (skippedCount > 0 ? `, skipped ${skippedCount}.` : \".\"),\n );\n}\n\nasync function run(ctx: CommandContext): Promise<void> {\n const sub = ctx.argv[0];\n\n switch (sub) {\n case \"list\":\n runList(ctx);\n return;\n case \"add\":\n await runAdd(ctx, ctx.argv.slice(1));\n return;\n case undefined:\n case \"-h\":\n case \"--help\":\n ctx.out.log(usage);\n return;\n default:\n ctx.out.fail(\n `unknown skills subcommand \"${sub}\". Use: list | add. See hogsend skills --help.`,\n );\n }\n}\n\nexport const skillsCommand: Command = {\n name: \"skills\",\n summary: \"List + install bundled Claude Code skills into .claude/skills\",\n usage,\n run,\n};\n","import { parseArgs } from \"node:util\";\nimport { color } from \"../lib/output.js\";\nimport type { Command, CommandContext } from \"./types.js\";\n\nconst usage = `hogsend stats [--json]\n\nShow system-wide overview metrics from a running Hogsend instance.\nWraps GET /v1/admin/metrics/overview.\n\nFields:\n totalContacts Live (non-deleted) contacts.\n activeJourneys Journey states currently active or waiting.\n emailsSent24h Emails sent in the last 24 hours.\n emailsSent7d Emails sent in the last 7 days.\n emailsSent30d Emails sent in the last 30 days.\n bounceRate30d Bounced / sent over the last 30 days (0..1).\n unsubscribeRate Unsubscribed / total preferences (0..1).\n\nOptions:\n --url <baseUrl> API base URL (default HOGSEND_API_URL or http://localhost:3002).\n --admin-key <key> Admin bearer key (default HOGSEND_ADMIN_KEY / ADMIN_API_KEY).\n --json Emit machine-readable JSON only.\n -h, --help Show this help.`;\n\n/** Shape returned by GET /v1/admin/metrics/overview. */\ninterface OverviewMetrics {\n totalContacts: number;\n activeJourneys: number;\n emailsSent24h: number;\n emailsSent7d: number;\n emailsSent30d: number;\n bounceRate30d: number;\n unsubscribeRate: number;\n}\n\n/** Render a 0..1 rate as a percentage with two decimals, e.g. 0.0123 -> \"1.23%\". */\nfunction pct(rate: number): string {\n return `${(rate * 100).toFixed(2)}%`;\n}\n\nasync function run(ctx: CommandContext): Promise<void> {\n const { values } = parseArgs({\n args: ctx.argv,\n allowPositionals: true,\n options: {\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n });\n\n if (values.help) {\n ctx.out.log(usage);\n return;\n }\n\n const metrics = await ctx.out.step(\"Fetching overview metrics\", () =>\n ctx.http.get<OverviewMetrics>(\"/v1/admin/metrics/overview\"),\n );\n\n if (ctx.json) {\n ctx.out.json(metrics);\n return;\n }\n\n ctx.out.intro(`${color.bgMagenta(color.black(\" hogsend \"))} stats`);\n\n ctx.out.kv(\n {\n \"Total contacts\": metrics.totalContacts,\n \"Active journeys\": metrics.activeJourneys,\n \"Emails sent (24h)\": metrics.emailsSent24h,\n \"Emails sent (7d)\": metrics.emailsSent7d,\n \"Emails sent (30d)\": metrics.emailsSent30d,\n \"Bounce rate (30d)\": pct(metrics.bounceRate30d),\n \"Unsubscribe rate\": pct(metrics.unsubscribeRate),\n },\n \"Overview\",\n );\n\n ctx.out.outro(color.dim(ctx.http.cfg.baseUrl));\n}\n\nexport const statsCommand: Command = {\n name: \"stats\",\n summary: \"Show system-wide overview metrics\",\n usage,\n run,\n};\n","import { contactsCommand } from \"./contacts.js\";\nimport { doctorCommand } from \"./doctor.js\";\nimport { ejectCommand } from \"./eject.js\";\nimport { eventsCommand } from \"./events.js\";\nimport { journeysCommand } from \"./journeys.js\";\nimport { patchCommand } from \"./patch.js\";\nimport { setupCommand } from \"./setup.js\";\nimport { skillsCommand } from \"./skills.js\";\nimport { statsCommand } from \"./stats.js\";\nimport type { Command } from \"./types.js\";\n\n/**\n * The command registry. The router (src/bin.ts) matches the leading argv token\n * against each `command.name` and dispatches to `run()`.\n *\n * Order here is the order shown in root help. Data commands (agent-native,\n * wrapping the engine's /v1/admin/* routes) come first, then the local\n * scaffolding/maintenance commands (setup, skills, eject, patch).\n */\nexport const commands: Command[] = [\n doctorCommand,\n journeysCommand,\n contactsCommand,\n statsCommand,\n eventsCommand,\n setupCommand,\n skillsCommand,\n ejectCommand,\n patchCommand,\n];\n\nexport type { Command, CommandContext } from \"./types.js\";\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { parseArgs } from \"node:util\";\n\n/** Resolved target for the admin HTTP client. */\nexport interface ResolvedConfig {\n /** Base URL of the target instance, no trailing slash. */\n baseUrl: string;\n /** Admin bearer token, if resolvable. `doctor`/health works without it. */\n adminKey: string | undefined;\n}\n\n/** Global flags parsed off the front of any command's argv. */\nexport interface GlobalFlags {\n url?: string;\n adminKey?: string;\n json: boolean;\n help: boolean;\n /** The remaining args after global flags are stripped. */\n rest: string[];\n}\n\nconst DEFAULT_BASE_URL = \"http://localhost:3002\";\n\n/**\n * Parse the global flags that every command honours (`--url`, `--admin-key`,\n * `--json`, `-h`/`--help`) off an argv slice, returning the parsed values plus\n * the leftover `rest` (positionals + unknown flags) for the command to handle.\n *\n * `strict: false` so command-specific flags (e.g. `--enabled`, `--limit`) pass\n * through untouched in `rest` rather than throwing here.\n */\nexport function parseGlobalFlags(argv: string[]): GlobalFlags {\n const { values, tokens } = parseArgs({\n args: argv,\n allowPositionals: true,\n strict: false,\n tokens: true,\n options: {\n url: { type: \"string\" },\n \"admin-key\": { type: \"string\" },\n json: { type: \"boolean\", default: false },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n });\n\n // Rebuild `rest` from the token stream, dropping only the global flags we\n // own (and their values). Everything else — positionals and unknown option\n // tokens — is preserved verbatim for the command's own parser.\n const owned = new Set([\"url\", \"admin-key\", \"json\", \"help\", \"h\"]);\n const rest: string[] = [];\n for (const token of tokens) {\n if (token.kind === \"positional\") {\n rest.push(token.value);\n } else if (token.kind === \"option\") {\n if (owned.has(token.name)) continue;\n rest.push(token.rawName);\n if (token.value !== undefined && !token.inlineValue) {\n rest.push(token.value);\n } else if (token.inlineValue && token.value !== undefined) {\n // already captured in rawName? no — rebuild as --name=value\n rest[rest.length - 1] = `${token.rawName}=${token.value}`;\n }\n }\n }\n\n return {\n url: typeof values.url === \"string\" ? values.url : undefined,\n adminKey:\n typeof values[\"admin-key\"] === \"string\" ? values[\"admin-key\"] : undefined,\n json: values.json === true,\n help: values.help === true,\n rest,\n };\n}\n\n/**\n * Manually parse a `.env` file into a flat record. No dotenv dependency: a\n * small, forgiving parser (KEY=VALUE per line, `#` comments, optional quotes,\n * `export ` prefix tolerated). Never throws — a missing/unreadable file yields\n * an empty record so config resolution stays robust in any cwd.\n */\nexport function loadDotEnv(\n cwd: string = process.cwd(),\n): Record<string, string> {\n const out: Record<string, string> = {};\n const file = join(cwd, \".env\");\n if (!existsSync(file)) return out;\n let raw: string;\n try {\n raw = readFileSync(file, \"utf8\");\n } catch {\n return out;\n }\n for (const rawLine of raw.split(/\\r?\\n/)) {\n const line = rawLine.trim();\n if (line === \"\" || line.startsWith(\"#\")) continue;\n const withoutExport = line.startsWith(\"export \")\n ? line.slice(\"export \".length)\n : line;\n const eq = withoutExport.indexOf(\"=\");\n if (eq === -1) continue;\n const key = withoutExport.slice(0, eq).trim();\n if (key === \"\") continue;\n let value = withoutExport.slice(eq + 1).trim();\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n out[key] = value;\n }\n return out;\n}\n\n/**\n * Resolve the target config with precedence flags > process.env > .env, falling\n * back to the local-dev default base URL.\n *\n * baseUrl: --url > HOGSEND_API_URL (env) > HOGSEND_API_URL (.env) > localhost:3002\n * adminKey: --admin-key > HOGSEND_ADMIN_KEY|ADMIN_API_KEY (env) > (.env equiv)\n */\nexport function resolveConfig(\n flags: GlobalFlags,\n cwd: string = process.cwd(),\n): ResolvedConfig {\n const dotenv = loadDotEnv(cwd);\n\n const baseUrlRaw =\n flags.url ??\n process.env.HOGSEND_API_URL ??\n dotenv.HOGSEND_API_URL ??\n DEFAULT_BASE_URL;\n\n const adminKey =\n flags.adminKey ??\n process.env.HOGSEND_ADMIN_KEY ??\n process.env.ADMIN_API_KEY ??\n dotenv.HOGSEND_ADMIN_KEY ??\n dotenv.ADMIN_API_KEY;\n\n return {\n baseUrl: baseUrlRaw.replace(/\\/+$/, \"\"),\n adminKey: adminKey && adminKey.length > 0 ? adminKey : undefined,\n };\n}\n"],"mappings":"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","names":["createRequire","sep","parseArgs","usage","run","parseArgs","verdict","badge","existsSync","join","sep","parseArgs","usage","join","existsSync","require","sep","run","parseArgs","parseArgs","usage","run","parseArgs","parseArgs","usage","badge","runList","parseArgs","runGet","run","parseArgs","usage","run","parseArgs","spawnSync","existsSync","join","parseArgs","cancel","usage","join","existsSync","spawnSync","run","parseArgs","existsSync","readFileSync","join","parseArgs","usage","join","existsSync","readFileSync","runList","parseArgs","run","parseArgs","usage","run","parseArgs","existsSync","readFileSync","join","parseArgs","require","createRequire"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hogsend/cli",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -20,6 +20,7 @@
20
20
  "files": [
21
21
  "dist",
22
22
  "src",
23
+ "skills",
23
24
  "README.md"
24
25
  ],
25
26
  "publishConfig": {
@@ -35,6 +36,10 @@
35
36
  "engines": {
36
37
  "node": ">=22"
37
38
  },
39
+ "dependencies": {
40
+ "@clack/prompts": "^1.5.0",
41
+ "picocolors": "^1.1.1"
42
+ },
38
43
  "scripts": {
39
44
  "build": "tsup",
40
45
  "check-types": "tsc --noEmit",
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: hogsend-cli
3
+ description: Use when an agent needs to inspect or operate a running Hogsend lifecycle engine — querying metrics/contacts/events, listing or enabling/disabling journeys, checking health, or onboarding a local instance — by driving the consolidated `hogsend` CLI. Every data command supports --json for machine-readable output.
4
+ license: MIT
5
+ metadata:
6
+ author: withSeismic
7
+ version: "1.0.0"
8
+ ---
9
+
10
+ # Hogsend CLI
11
+
12
+ The `hogsend` CLI is the agent-native interface to a Hogsend app (the
13
+ code-first lifecycle orchestration engine on PostHog + Resend). It wraps the
14
+ app's `/v1/admin/*` and `/v1/health` HTTP routes, so it works against ANY
15
+ running instance — local (`http://localhost:3002`) or production — without
16
+ importing the database.
17
+
18
+ ## The --json contract (READ THIS FIRST)
19
+
20
+ Every data/read command takes a global `--json` flag. In `--json` mode the
21
+ command prints EXACTLY ONE valid JSON document to stdout and nothing else — no
22
+ spinners, no color, no prose. **Always pass `--json` when you are parsing the
23
+ output programmatically.** Without it, you get a human-pretty table/keyvalue
24
+ rendering meant for a terminal.
25
+
26
+ ```bash
27
+ hogsend stats --json
28
+ hogsend journeys list --json
29
+ hogsend contacts get user_123 --json
30
+ ```
31
+
32
+ On error in `--json` mode the CLI prints `{"error":"<message>"}` to stdout and
33
+ exits 1. On success it exits 0 (doctor is the exception — see below).
34
+
35
+ ## Connecting to an instance
36
+
37
+ Two global flags / env vars control which instance you talk to:
38
+
39
+ - Base URL: `--url <baseUrl>` > `HOGSEND_API_URL` env > `.env` `HOGSEND_API_URL`
40
+ > default `http://localhost:3002`.
41
+ - Admin key: `--admin-key <key>` > `HOGSEND_ADMIN_KEY` env > `ADMIN_API_KEY`
42
+ env > the `.env` equivalents. Sent as `Authorization: Bearer <key>`.
43
+
44
+ ```bash
45
+ hogsend stats --url https://api.example.com --admin-key "$ADMIN_API_KEY" --json
46
+ ```
47
+
48
+ `doctor` hits the unauthenticated `/v1/health` and needs no admin key. Every
49
+ other data command requires one.
50
+
51
+ ## Command map
52
+
53
+ | Command | Purpose |
54
+ |---------|---------|
55
+ | `hogsend doctor` | Health + schema-drift verdict (reachability check). |
56
+ | `hogsend stats` | Overview metrics (contacts, emails, bounce/unsub rates). |
57
+ | `hogsend journeys list/get/enable/disable` | Inspect + toggle journeys. |
58
+ | `hogsend contacts list/get/timeline` | Inspect contacts + their activity. |
59
+ | `hogsend events <userId>` | Raw event stream for one user. |
60
+ | `hogsend skills list/add` | Manage these bundled agent skills. |
61
+ | `hogsend setup` | Interactive LOCAL onboarding (docker, secret, migrate). |
62
+ | `hogsend eject <pkg>` | Vendor a `@hogsend/*` package (unchanged). |
63
+ | `hogsend patch <pkg>` | Wrap `pnpm patch` (unchanged). |
64
+
65
+ Run `hogsend <command> --help` for per-command usage.
66
+
67
+ ## Task playbooks (load the matching reference)
68
+
69
+ - **Query metrics / analyse data** → `references/query-stats.md`
70
+ - **List, inspect, enable or disable journeys** → `references/manage-journeys.md`
71
+ - **Debug why a user did / didn't enroll** → `references/debug-a-journey.md`
72
+ - **Set up a local instance** → `references/setup-local.md`
73
+
74
+ ## Golden rules for agents
75
+
76
+ 1. Pass `--json` whenever you will parse output. Never screen-scrape the table.
77
+ 2. Start a debugging session with `hogsend doctor --json` to confirm the
78
+ instance is reachable and the schema is in sync before trusting other reads.
79
+ 3. Enabling/disabling a journey is a write — confirm intent first.
80
+ 4. Use `--limit`/`--offset` for pagination instead of dumping everything.
@@ -0,0 +1,66 @@
1
+ # Debug a journey: why did (or didn't) a user enroll?
2
+
3
+ A repeatable trace using `doctor` + `journeys get` + `contacts timeline` +
4
+ `events`. Run every command with `--json` and read the output.
5
+
6
+ ## Step 0 — confirm the instance is healthy
7
+
8
+ ```bash
9
+ hogsend doctor --json
10
+ ```
11
+
12
+ Wraps `GET /v1/health`. Inspect the verdict:
13
+
14
+ - `ok` — healthy, proceed.
15
+ - `degraded` — a component (database / redis) is unhealthy; reads may be
16
+ unreliable. Exit code 1.
17
+ - `migration_pending` — the engine schema and the client schema are out of
18
+ sync (`schema.inSync = false`, `pending` migrations listed). Data may be
19
+ missing columns; fix the drift before trusting other queries.
20
+ - `unreachable` — could not connect (HTTP status 0). Check `--url` and that the
21
+ app is running. Exit code 1.
22
+
23
+ Do NOT trust downstream reads until doctor returns `ok`.
24
+
25
+ ## Step 1 — understand the journey's entry rules
26
+
27
+ ```bash
28
+ hogsend journeys get <journeyId> --json
29
+ ```
30
+
31
+ Read the `trigger` (which event enrolls a user) and any `trigger.where`
32
+ property conditions. Read `exitOn` rules (what removes a user). Enrollment is
33
+ gated, in order, by: `enabled` flag → trigger `where` conditions → entry limit
34
+ (once / once_per_period / unlimited) → email preferences (unsubscribed users
35
+ are skipped). Any failed gate means NO enrollment.
36
+
37
+ ## Step 2 — look at the user's lifecycle
38
+
39
+ ```bash
40
+ hogsend contacts get <userId> --json # subscribed? unsubscribed?
41
+ hogsend contacts timeline <userId> --json # merged events/emails/journeys
42
+ ```
43
+
44
+ The timeline shows whether the user already has an active/completed state for
45
+ this journey (entry limit may have blocked re-entry) and whether they're
46
+ unsubscribed (which skips email-sending journeys).
47
+
48
+ ## Step 3 — verify the trigger event actually fired with the right props
49
+
50
+ ```bash
51
+ hogsend events <userId> --event <triggerEvent> --json
52
+ ```
53
+
54
+ Wraps `GET /v1/admin/events?userId=<userId>`. Confirm the trigger event exists
55
+ for this user AND that its properties satisfy the journey's `trigger.where`
56
+ conditions. A missing event, or an event whose properties fail the `where`
57
+ check, is the most common reason a user did not enroll.
58
+
59
+ ## Decision tree
60
+
61
+ - No trigger event in `events` → upstream isn't sending it (PostHog / webhook).
62
+ - Trigger event present but `where` mismatch → property condition not met.
63
+ - Already has a journey state → entry limit blocked re-enrollment.
64
+ - Contact unsubscribed → email journeys skipped at the preferences gate.
65
+ - Journey `enabled: false` (from `journeys get`) → no enrollment at all.
66
+ - `doctor` not `ok` → fix infra/schema first; the data is suspect.
@@ -0,0 +1,53 @@
1
+ # Manage journeys
2
+
3
+ Inspect and toggle lifecycle journeys on a running Hogsend instance. `list` and
4
+ `get` are read-only; `enable`/`disable` are writes.
5
+
6
+ ## List
7
+
8
+ ```bash
9
+ hogsend journeys list --json
10
+ hogsend journeys list --enabled true --limit 50 --offset 0 --json
11
+ ```
12
+
13
+ Wraps `GET /v1/admin/journeys`. Filter with `--enabled <true|false>`, paginate
14
+ with `--limit`/`--offset`. Each row carries `id`, `name`, `enabled`, the
15
+ trigger event, and enrollment counts (active / completed / failed). Use the
16
+ `id` for every other journey command.
17
+
18
+ ## Get detail
19
+
20
+ ```bash
21
+ hogsend journeys get conversion-trial-upgrade --json
22
+ ```
23
+
24
+ Wraps `GET /v1/admin/journeys/{id}`. Returns the full definition view —
25
+ trigger (event + optional `where` conditions), `exitOn` rules, aggregate
26
+ counts, and a sample of recent `journeyStates` (so you can see who is currently
27
+ active / waiting / completed). This is your starting point before toggling a
28
+ journey.
29
+
30
+ ## Enable / disable
31
+
32
+ ```bash
33
+ hogsend journeys enable conversion-trial-upgrade --json
34
+ hogsend journeys disable conversion-trial-upgrade --json
35
+ ```
36
+
37
+ Wraps `PATCH /v1/admin/journeys/{id}` with `{ "enabled": true|false }`.
38
+
39
+ Safety notes:
40
+
41
+ - These are WRITES against a live system. Confirm the journey `id` with
42
+ `journeys get` first, and confirm intent with the human before disabling a
43
+ journey that has active enrollments.
44
+ - Disabling stops NEW enrollments. Contacts already mid-journey continue per
45
+ the engine's semantics — disabling is not a kill switch for in-flight states.
46
+ - After toggling, re-run `journeys get <id> --json` and confirm `enabled`
47
+ flipped as expected.
48
+
49
+ ## Counts cheatsheet
50
+
51
+ When reading counts: `active` = currently enrolled (may be sleeping/waiting),
52
+ `completed` = finished the run, `failed` = errored. A journey with rising
53
+ `failed` counts is worth a `debug-a-journey` pass.