@aubron/ankerts-cli 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/LICENSE +21 -0
- package/README.md +56 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1109 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
- package/skills/ankerts/SKILL.md +81 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/globals.ts","../src/describe.ts","../src/spec.ts","../src/commands/auth.ts","../src/runtime.ts","../src/commands/gcode.ts","../src/commands/jobs.ts","../src/commands/printer.ts","../src/commands/skills.ts","../src/commands/index.ts","../src/help.ts","../src/output.ts"],"sourcesContent":["/**\n * @aubron/ankerts-cli — the CLI entrypoint.\n *\n * A thin, deterministic shell over the @aubron/ankerts SDK: it parses arguments,\n * formats output (json/ndjson/text per §3), maps typed SDK errors to documented\n * exit codes, and routes data to stdout / logs+errors to stderr. It contains NO\n * protocol logic.\n */\nimport { AnkerClient, toAnkerError } from \"@aubron/ankerts\";\nimport { allCommands } from \"./commands/index.js\";\nimport { extractGlobals, parseCommandArgs } from \"./globals.js\";\nimport { renderCommandHelp, renderRootHelp } from \"./help.js\";\nimport { Output, resolveMode } from \"./output.js\";\nimport type { CommandSpec, Context } from \"./spec.js\";\n\n/** Match the longest command path that prefixes the leading non-flag tokens. */\nfunction matchCommand(argv: string[]): { spec?: CommandSpec; rest: string[] } {\n const nounTokens: string[] = [];\n for (const tok of argv) {\n if (tok.startsWith(\"-\")) break;\n nounTokens.push(tok);\n }\n let best: CommandSpec | undefined;\n for (const spec of allCommands) {\n if (spec.path.length > nounTokens.length) continue;\n if (spec.path.every((seg, i) => nounTokens[i] === seg)) {\n if (!best || spec.path.length > best.path.length) best = spec;\n }\n }\n if (!best) return { rest: argv };\n // Drop exactly the matched path tokens, preserving the rest (flags + args).\n const rest: string[] = [];\n let dropped = 0;\n for (const tok of argv) {\n if (dropped < best.path.length && !tok.startsWith(\"-\") && tok === best.path[dropped]) {\n dropped++;\n continue;\n }\n rest.push(tok);\n }\n return { spec: best, rest };\n}\n\nfunction readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n if (process.stdin.isTTY) return resolve(\"\");\n let data = \"\";\n process.stdin.setEncoding(\"utf8\");\n process.stdin.on(\"data\", (chunk) => (data += chunk));\n process.stdin.on(\"end\", () => resolve(data));\n process.stdin.on(\"error\", reject);\n });\n}\n\nasync function main(): Promise<number> {\n const argv = process.argv.slice(2);\n const isTty = !!process.stdout.isTTY;\n const noColor = process.env.NO_COLOR !== undefined;\n\n const { spec, rest } = matchCommand(argv);\n\n // Parse args against the command's flags (or just globals for help/root).\n let parsed;\n try {\n parsed = parseCommandArgs(rest, spec?.flags ?? []);\n } catch (err) {\n const out = new Output({ mode: isTty ? \"text\" : \"json\", isTty, noColor });\n const e = toAnkerError(err);\n out.emitError({ error: { ...e.body(), code: \"usage\", retriable: false } });\n return 2;\n }\n const globals = extractGlobals(parsed.values);\n\n const out = new Output({\n mode: resolveMode({\n flag: globals.output,\n json: globals.json,\n env: process.env.ANKER_OUTPUT,\n isTty,\n }),\n quiet: globals.quiet,\n fields: globals.fields,\n isTty,\n noColor,\n });\n\n // Help: root (no command / bare --help) or command-specific.\n if (!spec) {\n if (argv.length && !globals.help && argv[0] !== \"help\") {\n out.emitError({\n error: {\n code: \"unknown_command\",\n message: `unknown command: ${argv.filter((a) => !a.startsWith(\"-\")).join(\" \") || \"(none)\"}`,\n retriable: false,\n hint: \"Run `ankerts --help`, or `ankerts describe --json` to introspect the full tree.\",\n },\n });\n return 2;\n }\n process.stdout.write(`${renderRootHelp(allCommands)}\\n`);\n return 0;\n }\n if (globals.help) {\n process.stdout.write(`${renderCommandHelp(spec)}\\n`);\n return 0;\n }\n\n const ctx: Context = {\n out,\n args: parsed,\n globals,\n client: () =>\n AnkerClient.fromStoredConfig(undefined, {\n log: (m) => out.log(m),\n printer: globals.printer,\n insecure: globals.insecure,\n }),\n readStdin,\n };\n\n try {\n await spec.run(ctx);\n return 0;\n } catch (err) {\n const e = toAnkerError(err);\n out.emitError(e.toJSON());\n return e.exitCode;\n }\n}\n\nmain()\n .then((code) => process.exit(code))\n .catch((err) => {\n process.stderr.write(`${String(err)}\\n`);\n process.exit(1);\n });\n","/**\n * Global flags shared by every command (brief §3), plus argument parsing that\n * merges global + per-command flags and the documented exit-code contract.\n */\nimport { parseArgs } from \"node:util\";\nimport type { FlagSpec, GlobalFlags, ParsedArgs } from \"./spec.js\";\n\nexport const GLOBAL_FLAGS: FlagSpec[] = [\n {\n name: \"output\",\n type: \"string\",\n description: \"Output format: json | ndjson | text. Default: json when piped, text on a TTY.\",\n },\n { name: \"json\", type: \"boolean\", description: \"Alias for --output json.\" },\n {\n name: \"quiet\",\n type: \"boolean\",\n short: \"q\",\n description: \"Bare values, one per line — for $() capture and xargs.\",\n },\n {\n name: \"fields\",\n type: \"string\",\n description: \"Comma-separated field mask (dotted paths) to trim large objects.\",\n },\n { name: \"dry-run\", type: \"boolean\", description: \"Validate and report; mutate nothing.\" },\n { name: \"yes\", type: \"boolean\", description: \"Bypass confirmations / safety warnings.\" },\n {\n name: \"printer\",\n type: \"string\",\n description: \"Target printer by DUID, serial, name, or index. Default: selected printer.\",\n },\n { name: \"timeout\", type: \"string\", description: \"Override the command timeout, in seconds.\" },\n { name: \"no-input\", type: \"boolean\", description: \"Never prompt; fail fast instead.\" },\n { name: \"insecure\", type: \"boolean\", description: \"Disable TLS verification (testing only).\" },\n { name: \"help\", type: \"boolean\", short: \"h\", description: \"Show help for this command.\" },\n];\n\n/** The documented exit-code contract (printed in --help). */\nexport const EXIT_CODES: Record<number, string> = {\n 0: \"success\",\n 1: \"generic / unexpected failure\",\n 2: \"usage error (bad/missing args)\",\n 3: \"auth error (login required/expired/captcha)\",\n 4: \"printer not found / not selected\",\n 5: \"connectivity/timeout — RETRIABLE\",\n 6: \"transport unavailable for this op (e.g. upload needs LAN)\",\n 7: \"printer-side error (gcode rejected, job refused)\",\n};\n\ntype ParseArgsConfig = Parameters<typeof parseArgs>[0];\ntype ParseArgsOptions = NonNullable<ParseArgsConfig>[\"options\"];\n\n/** Convert FlagSpecs into a node:util parseArgs options object. */\nexport function buildParseOptions(flags: FlagSpec[]): ParseArgsOptions {\n const options: NonNullable<ParseArgsOptions> = {};\n for (const f of flags) {\n // number flags are parsed as strings and converted later.\n options[f.name] = {\n type: f.type === \"boolean\" ? \"boolean\" : \"string\",\n ...(f.short ? { short: f.short } : {}),\n ...(f.multiple ? { multiple: true } : {}),\n };\n }\n return options;\n}\n\n/** Parse argv against the merged global+command flag set. */\nexport function parseCommandArgs(argv: string[], flags: FlagSpec[]): ParsedArgs {\n const all = [\n ...GLOBAL_FLAGS,\n ...flags.filter((f) => !GLOBAL_FLAGS.some((g) => g.name === f.name)),\n ];\n const numberFlags = new Set(all.filter((f) => f.type === \"number\").map((f) => f.name));\n const { values, positionals } = parseArgs({\n args: argv,\n options: buildParseOptions(all),\n allowPositionals: true,\n strict: true,\n });\n const out: Record<string, string | boolean | number | string[]> = {};\n for (const [k, v] of Object.entries(values)) {\n out[k] = numberFlags.has(k) && typeof v === \"string\" ? Number(v) : (v as never);\n }\n return { values: out, positionals };\n}\n\n/** Extract the cross-cutting global flags from a parsed values map. */\nexport function extractGlobals(values: ParsedArgs[\"values\"]): GlobalFlags {\n const fields =\n typeof values.fields === \"string\" ? values.fields.split(\",\").map((s) => s.trim()) : undefined;\n return {\n output: typeof values.output === \"string\" ? values.output : undefined,\n json: values.json === true,\n quiet: values.quiet === true,\n fields,\n dryRun: values[\"dry-run\"] === true,\n yes: values.yes === true,\n printer: typeof values.printer === \"string\" ? values.printer : undefined,\n timeout: typeof values.timeout === \"string\" ? Number(values.timeout) : undefined,\n noInput: values[\"no-input\"] === true,\n reveal: values.reveal === true,\n insecure: values.insecure === true,\n help: values.help === true,\n };\n}\n","/**\n * `describe` — machine-readable introspection of the whole command tree (§3).\n * Lets an agent map the tool's full surface (every command, flag, type, default,\n * exit code, example) in a single call instead of crawling --help pages.\n */\nimport { EXIT_CODES, GLOBAL_FLAGS } from \"./globals.js\";\nimport type { CommandSpec } from \"./spec.js\";\n\nexport function buildDescribeTree(specs: CommandSpec[]): unknown {\n return {\n name: \"ankerts\",\n summary: \"Agent-first CLI for AnkerMake / eufyMake M5 printers.\",\n transports: {\n mqtt: { use: \"gcode, status, control\", reachability: \"anywhere (internet + creds)\" },\n pppp: { use: \"file upload, camera\", reachability: \"LAN only (needs discovered IP)\" },\n https: { use: \"login, account, printer list\", reachability: \"anywhere\" },\n },\n exitCodes: Object.fromEntries(Object.entries(EXIT_CODES).map(([k, v]) => [Number(k), v])),\n globalFlags: GLOBAL_FLAGS,\n commands: specs.map((s) => ({\n path: s.path,\n command: s.path.join(\" \"),\n summary: s.summary,\n description: s.description,\n transport: s.transport,\n args: s.args,\n flags: s.flags,\n exitCodes: s.exitCodes,\n examples: s.examples,\n })),\n };\n}\n","/**\n * Command specification types. Every command is described by one declarative\n * spec — summary, long description (with transport), flags, args, exit codes,\n * and worked examples. Help text, `describe --json` introspection, and argument\n * parsing all derive from these specs, so the documentation an agent reads is\n * generated from the same source of truth that runs the command (brief §3).\n */\nimport type { Output } from \"./output.js\";\nimport type { AnkerClient } from \"@aubron/ankerts\";\n\nexport type FlagType = \"string\" | \"boolean\" | \"number\";\n\nexport interface FlagSpec {\n name: string;\n type: FlagType;\n short?: string;\n default?: string | boolean | number;\n description: string;\n /** Allow repeating to build an array (e.g. multiple gcode args). */\n multiple?: boolean;\n}\n\nexport interface ArgSpec {\n name: string;\n description: string;\n required?: boolean;\n variadic?: boolean;\n}\n\nexport interface ExampleSpec {\n cmd: string;\n description?: string;\n output?: string;\n}\n\nexport type TransportTag = \"mqtt\" | \"pppp\" | \"https\" | \"none\";\n\nexport interface ParsedArgs {\n values: Record<string, string | boolean | number | string[]>;\n positionals: string[];\n}\n\nexport interface Context {\n out: Output;\n args: ParsedArgs;\n /** Global flags shared by every command. */\n globals: GlobalFlags;\n /** Lazily build the SDK client from stored config (logger → stderr). */\n client: () => AnkerClient;\n /** Read all of stdin as text (for `gcode -` and batch input). */\n readStdin: () => Promise<string>;\n}\n\nexport interface GlobalFlags {\n output?: string;\n json?: boolean;\n quiet?: boolean;\n fields?: string[];\n dryRun?: boolean;\n yes?: boolean;\n printer?: string;\n timeout?: number;\n noInput?: boolean;\n reveal?: boolean;\n insecure?: boolean;\n help?: boolean;\n}\n\nexport interface CommandSpec {\n /** Noun→verb path, e.g. `[\"printer\", \"status\"]`. */\n path: string[];\n summary: string;\n description: string;\n transport: TransportTag;\n flags: FlagSpec[];\n args: ArgSpec[];\n exitCodes: number[];\n examples: ExampleSpec[];\n run: (ctx: Context) => Promise<void> | void;\n}\n\nexport type CommandSpecInput = Partial<CommandSpec> & Pick<CommandSpec, \"path\" | \"summary\" | \"run\">;\n\n/** Fill defaults so every command has a complete, introspectable spec. */\nexport function defineCommand(input: CommandSpecInput): CommandSpec {\n return {\n description: input.summary,\n transport: \"none\",\n flags: [],\n args: [],\n exitCodes: [0, 1, 2],\n examples: [],\n ...input,\n };\n}\n","/**\n * Auth + config commands: login, logout, config show.\n */\nimport { AnkerClient, ConfigStore, redactConfig, UsageError } from \"@aubron/ankerts\";\nimport { defineCommand, type CommandSpec } from \"../spec.js\";\nimport { flagBool, flagStr, str } from \"../runtime.js\";\n\nconst login: CommandSpec = defineCommand({\n path: [\"login\"],\n summary: \"Authenticate to an AnkerMake/eufyMake account and store credentials.\",\n description:\n \"Logs in over the cloud HTTPS API (email/password → token; the password is \" +\n \"ECDH-encrypted in transit), then fetches the account's printer list and keys. \" +\n \"If the API returns a captcha challenge, you get a structured, actionable error \" +\n \"(captcha_id + image URL) — never a hang. Re-run with --captcha-answer to solve it.\",\n transport: \"https\",\n flags: [\n { name: \"email\", type: \"string\", description: \"Account email.\" },\n {\n name: \"password\",\n type: \"string\",\n description: \"Account password. Use `-` to read from stdin, or set ANKER_PASSWORD.\",\n },\n { name: \"country\", type: \"string\", description: \"2-letter country code selecting the region.\" },\n { name: \"save\", type: \"boolean\", description: \"Persist credentials to the config store.\" },\n { name: \"captcha-id\", type: \"string\", description: \"Captcha id from a prior captcha error.\" },\n { name: \"captcha-answer\", type: \"string\", description: \"Captcha answer text.\" },\n ],\n exitCodes: [0, 2, 3, 5],\n examples: [\n {\n description: \"Log in and store credentials\",\n cmd: \"ankerts login --email me@example.com --password hunter2 --country US --save\",\n output: '{ \"account\": { \"email\": \"me@example.com\", \"region\": \"us\" }, \"printers\": 1 }',\n },\n {\n description: \"Read the password from stdin (keeps it out of argv/history)\",\n cmd: \"echo $PW | ankerts login --email me@example.com --password - --country US --save\",\n },\n ],\n async run(ctx) {\n const email = str(ctx.args.values.email);\n const country = str(ctx.args.values.country);\n let password = flagStr(ctx.args, \"password\") ?? process.env.ANKER_PASSWORD ?? \"\";\n if (password === \"-\") password = (await ctx.readStdin()).trim();\n\n if (!email || !country) {\n throw new UsageError({\n message: \"--email and --country are required\",\n input: { email: email || null, country: country || null },\n });\n }\n if (!password) {\n throw new UsageError({\n message: \"no password provided\",\n hint: \"Pass --password <pw>, set ANKER_PASSWORD, or use --password - to read stdin.\",\n });\n }\n if (ctx.globals.dryRun) {\n ctx.out.emit({ dryRun: true, action: \"login\", email, country });\n return;\n }\n\n const client = await AnkerClient.login({\n email,\n password,\n country,\n captchaId: flagStr(ctx.args, \"captcha-id\"),\n captchaAnswer: flagStr(ctx.args, \"captcha-answer\"),\n save: flagBool(ctx.args, \"save\"),\n });\n const cfg = client.getConfig();\n if (!flagBool(ctx.args, \"save\")) {\n ctx.out.log(\"note: credentials were NOT saved (pass --save to persist).\");\n } else {\n ctx.out.log(\"login ok — credentials stored.\");\n }\n ctx.out.emit({ account: redactConfig(cfg).account, printers: cfg.printers.length });\n },\n});\n\nconst logout: CommandSpec = defineCommand({\n path: [\"logout\"],\n summary: \"Remove stored credentials and printer config.\",\n description: \"Clears the account token from the local config store. Printers are forgotten.\",\n transport: \"none\",\n exitCodes: [0, 1],\n examples: [{ cmd: \"ankerts logout\" }],\n run(ctx) {\n const store = new ConfigStore();\n if (ctx.globals.dryRun) {\n ctx.out.emit({ dryRun: true, action: \"logout\", path: store.path });\n return;\n }\n store.save({ account: null, printers: [] });\n ctx.out.emit({ ok: true, message: \"logged out\" });\n },\n});\n\nconst configShow: CommandSpec = defineCommand({\n path: [\"config\", \"show\"],\n summary: \"Show the current stored config (secrets redacted by default).\",\n description:\n \"Prints the account and per-printer records from the local config store. Secrets \" +\n \"(auth token, MQTT/PPPP keys) are redacted unless you pass --reveal.\",\n transport: \"none\",\n flags: [{ name: \"reveal\", type: \"boolean\", description: \"Show secret values in clear text.\" }],\n exitCodes: [0, 1],\n examples: [\n { description: \"Inspect config safely\", cmd: \"ankerts config show\" },\n {\n description: \"Get the first printer's DUID\",\n cmd: \"ankerts config show --json | jq -r '.printers[0].duid'\",\n },\n ],\n run(ctx) {\n const store = new ConfigStore();\n ctx.out.emit(redactConfig(store.load(), flagBool(ctx.args, \"reveal\")));\n },\n});\n\nexport const authCommands = [login, logout, configShow];\n","/**\n * Small helpers shared across command implementations.\n */\nimport { UsageError } from \"@aubron/ankerts\";\nimport type { Context, ParsedArgs } from \"./spec.js\";\n\nexport function str(v: unknown): string {\n return typeof v === \"string\" ? v : \"\";\n}\n\n/** A required positional, or a structured usage error (exit 2). */\nexport function requirePositional(ctx: Context, index: number, name: string): string {\n const v = ctx.args.positionals[index];\n if (!v) {\n throw new UsageError({\n message: `missing required argument <${name}>`,\n hint: `See \\`ankerts ${ctx.args.positionals.slice(0, index).join(\" \")} --help\\`.`,\n input: { argument: name },\n });\n }\n return v;\n}\n\n/** Resolve a timeout (ms): --timeout (seconds) overrides the fallback. */\nexport function timeoutMs(ctx: Context, fallbackMs: number): number {\n const t = ctx.globals.timeout;\n return typeof t === \"number\" && !Number.isNaN(t) ? t * 1000 : fallbackMs;\n}\n\nexport function flagBool(args: ParsedArgs, name: string): boolean {\n return args.values[name] === true;\n}\n\nexport function flagStr(args: ParsedArgs, name: string): string | undefined {\n const v = args.values[name];\n return typeof v === \"string\" ? v : undefined;\n}\n","/**\n * Gcode commands: gcode (single / batch / stdin), state snapshot, state restore.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { inspectGcode, type GcodeOptions } from \"@aubron/ankerts\";\nimport { defineCommand, type Context, type CommandSpec } from \"../spec.js\";\nimport { flagBool, flagStr, requirePositional, timeoutMs } from \"../runtime.js\";\n\n/** Warn about state-mutating gcode on stderr unless suppressed (§4, lesson #5). */\nfunction warnIfMutating(ctx: Context, command: string): void {\n if (ctx.globals.yes || ctx.globals.dryRun) return;\n const info = inspectGcode(command);\n if (info.mutatesState && info.note) ctx.out.log(`warning: ${info.note}`);\n}\n\nconst gcode: CommandSpec = defineCommand({\n path: [\"gcode\"],\n summary: \"Send gcode and return the parsed response (truncation-aware).\",\n description:\n \"Publishes gcode over MQTT and parses the reply into raw text, fields/reports, ok, \" +\n \"recognized, and timedOut (distinct from recognized:false — a timeout is not a \" +\n \"rejection). The firmware returns each reply as a single ~512-byte serial-buffer \" +\n \"snapshot, so long replies (or one caught mid-write, e.g. M900) come back partial: \" +\n \"those are flagged `truncated:true` (with a stderr warning) instead of being passed \" +\n \"off as complete like the reference's `echo:Ad` bug. Accepts one command as args, \" +\n \"many via --batch <file>, or `-` to read stdin (one per line, NDJSON result per line). \" +\n \"State-mutating commands print a volatility warning on stderr unless --yes. Default \" +\n \"timeouts are latency-class aware (M109/M190/G29/M303 get minutes); --wait-motion \" +\n \"appends M400 so the call returns on true motion completion, not queue-accept.\",\n transport: \"mqtt\",\n args: [\n {\n name: \"CMD\",\n description: \"Gcode to send (or `-` to read commands from stdin).\",\n variadic: true,\n },\n ],\n flags: [\n { name: \"no-wait\", type: \"boolean\", description: \"Fire-and-forget; don't collect a response.\" },\n {\n name: \"wait-motion\",\n type: \"boolean\",\n description: \"Append M400 — return on motion complete.\",\n },\n { name: \"batch\", type: \"string\", description: \"Read commands from a file (one per line).\" },\n ],\n exitCodes: [0, 1, 3, 4, 5, 7],\n examples: [\n {\n description: \"Read the firmware name (long reply → truncated:true, name still present)\",\n cmd: \"ankerts gcode M115 --json | jq '{firmware:.fields.FIRMWARE_NAME, truncated}'\",\n output: '{ \"firmware\": \"Marlin V8111_V3.2.2 (...)\", \"truncated\": true }',\n },\n {\n description: \"A short reply comes back whole\",\n cmd: \"ankerts gcode M105 --json | jq -r '.raw'\",\n output: \"ok T:29.12 /0.00 B:30.31 /0.00\",\n },\n {\n description: \"Detect an unknown command (not a truncation)\",\n cmd: \"ankerts gcode M9998 --json | jq '{recognized, truncated}'\",\n output: '{ \"recognized\": false, \"truncated\": false }',\n },\n {\n description: \"Batch from stdin, one NDJSON result per line\",\n cmd: \"printf 'M104 S200\\\\nM140 S60\\\\n' | ankerts gcode -\",\n },\n ],\n async run(ctx) {\n const client = ctx.client();\n const opts: GcodeOptions = {\n timeoutMs: timeoutMs(ctx, 0) || undefined,\n wait: !flagBool(ctx.args, \"no-wait\"),\n waitMotion: flagBool(ctx.args, \"wait-motion\"),\n };\n\n // Determine the command source: --batch file, stdin (`-`), or positionals.\n const batchFile = flagStr(ctx.args, \"batch\");\n let commands: string[];\n if (batchFile) {\n commands = (await readFile(batchFile, \"utf8\"))\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter(Boolean);\n } else if (ctx.args.positionals[0] === \"-\") {\n commands = (await ctx.readStdin())\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter(Boolean);\n } else {\n const single = ctx.args.positionals.join(\" \").trim();\n if (!single) {\n requirePositional(ctx, 0, \"CMD\"); // throws a structured usage error\n }\n commands = [single];\n }\n\n if (ctx.globals.dryRun) {\n ctx.out.emit(\n commands.map((command) => ({ dryRun: true, command, inspect: inspectGcode(command) })),\n );\n return;\n }\n\n for (const command of commands) warnIfMutating(ctx, command);\n\n if (commands.length === 1) {\n const result = await client.gcode(commands[0]!, opts);\n await client.close();\n if (result.truncated) {\n ctx.out.log(\n \"warning: reply was truncated by the firmware's ~512-byte serial buffer (truncated=true) — output may be incomplete.\",\n );\n }\n ctx.out.emit(result);\n return;\n }\n // Batch → one NDJSON result line per command, as each completes.\n for await (const result of client.gcodeBatch(commands, opts)) {\n ctx.out.ndjsonLine(result);\n }\n await client.close();\n },\n});\n\nconst stateSnapshot: CommandSpec = defineCommand({\n path: [\"state\", \"snapshot\"],\n summary: \"Capture machine settings (M503) as a parsed object.\",\n description:\n \"Sends M503 over MQTT and parses the settings dump into a structured object (linear \" +\n \"advance K, hotend PID, steps/mm, probe Z offset, and the report map). NOTE: M503's \" +\n \"output usually exceeds the firmware's ~512-byte reply window, so the snapshot can be \" +\n \"partial — check `result.truncated`. (A piecewise reader that stitches the full set \" +\n \"from short per-setting queries is tracked as a follow-up.) Pair with `state restore` \" +\n \"to undo volatile changes.\",\n transport: \"mqtt\",\n exitCodes: [0, 1, 3, 4, 5],\n examples: [\n {\n description: \"Snapshot before tuning\",\n cmd: \"ankerts state snapshot --json | jq '.hotendPid'\",\n },\n ],\n async run(ctx) {\n const client = ctx.client();\n const settings = await client.snapshotState();\n await client.close();\n ctx.out.emit(settings);\n },\n});\n\nconst stateRestore: CommandSpec = defineCommand({\n path: [\"state\", \"restore\"],\n summary: \"Reload settings from EEPROM (M501), discarding volatile changes.\",\n description:\n \"Sends M501 to reload the saved EEPROM settings, reverting any volatile (RAM) changes \" +\n \"made this power cycle (e.g. an `M900 K` that would otherwise contaminate the next print).\",\n transport: \"mqtt\",\n exitCodes: [0, 1, 3, 4, 5, 7],\n examples: [{ cmd: \"ankerts state restore\" }],\n async run(ctx) {\n const client = ctx.client();\n const result = await client.restoreState();\n await client.close();\n ctx.out.emit(result);\n },\n});\n\nexport const gcodeCommands = [gcode, stateSnapshot, stateRestore];\n","/**\n * Job/transport commands: discover, print, job cancel|pause|resume, camera capture.\n */\nimport { AnkerError } from \"@aubron/ankerts\";\nimport { defineCommand, type CommandSpec } from \"../spec.js\";\nimport { flagBool, requirePositional, timeoutMs } from \"../runtime.js\";\n\nconst discover: CommandSpec = defineCommand({\n path: [\"discover\"],\n summary: \"Find printers on the local network (PPPP LAN search).\",\n description:\n \"Broadcasts a PPPP LAN_SEARCH over UDP and collects replies. Discovery is flaky \" +\n \"(UDP broadcast), so it retries. With --store, the discovered IPs are written into \" +\n \"config — the prerequisite for LAN file upload. `print` runs this automatically when \" +\n \"a printer's IP is missing.\",\n transport: \"pppp\",\n flags: [\n { name: \"store\", type: \"boolean\", description: \"Write discovered IPs into config.\" },\n { name: \"retries\", type: \"string\", description: \"Discovery attempts (default 3).\" },\n ],\n exitCodes: [0, 1, 5],\n examples: [\n { description: \"Discover and store IPs\", cmd: \"ankerts discover --store\" },\n {\n description: \"Bare DUIDs of printers on the LAN\",\n cmd: \"ankerts discover -q | xargs -n1 echo found:\",\n },\n ],\n async run(ctx) {\n const client = ctx.client();\n const retries = Number(ctx.args.values.retries ?? 3) || 3;\n const found = await client.discoverLan({\n store: flagBool(ctx.args, \"store\"),\n retries,\n timeoutMs: timeoutMs(ctx, 1000),\n });\n ctx.out.emit(found, { quietProjection: [\"duid\"] });\n },\n});\n\nconst print: CommandSpec = defineCommand({\n path: [\"print\"],\n summary: \"Upload a gcode file over the LAN and start the print.\",\n description:\n \"Uploads via PPPP (LAN only) and, by default, starts the job. Auto-runs discovery \" +\n \"to find/store the printer IP if it is unknown. Off-LAN this exits 6 (transport \" +\n \"unavailable) with a structured error naming PPPP and the `discover --store` fix. \" +\n \"Upload progress streams as NDJSON on stderr. `print` returns a job handle by default \" +\n \"(it does NOT hold the process for the whole print) — use --wait-start to block until \" +\n \"the job actually starts, or detach and poll with `printer wait --until complete`. \" +\n \"By DEFAULT it auto-fixes the LCD ETA: a third-party slicer's embedded time/filament \" +\n \"estimate is transcoded into the Anker `;TIME:` header (auto-detected — a no-op on \" +\n \"natively-sliced files; always on a copy, never mutating your file). Pass \" +\n \"--no-fix-metadata to upload the file byte-for-byte untouched.\",\n transport: \"pppp\",\n args: [{ name: \"file.gcode\", description: \"Path to the gcode file to upload.\", required: true }],\n flags: [\n { name: \"no-start\", type: \"boolean\", description: \"Upload only; don't start the print.\" },\n { name: \"transport\", type: \"string\", description: \"lan | auto (default auto; LAN only here).\" },\n {\n name: \"no-fix-metadata\",\n type: \"boolean\",\n description: \"Upload the file untouched (skip the automatic slicer-metadata ETA fix).\",\n },\n {\n name: \"wait-start\",\n type: \"boolean\",\n description: \"Block until the job state flips to printing.\",\n },\n {\n name: \"wait-complete\",\n type: \"boolean\",\n description: \"Block until the print completes (long-running, resumable).\",\n },\n ],\n exitCodes: [0, 1, 4, 5, 6, 7],\n examples: [\n {\n description: \"Upload + start (auto-discovers IP if needed)\",\n cmd: \"ankerts print tower.gcode\",\n output: '{ \"name\": \"tower.gcode\", \"started\": true, \"transport\": \"lan\" }',\n },\n {\n description: \"Off-LAN failure is structured and actionable (exit 6)\",\n cmd: \"ankerts print tower.gcode --json | jq -r '.error.hint'\",\n output: \"Run `ankerts discover --store` while on the same LAN as the printer, then retry.\",\n },\n {\n description: \"Upload an OrcaSlicer file untouched (no automatic ETA fix)\",\n cmd: \"ankerts print tower.gcode --no-fix-metadata\",\n },\n ],\n async run(ctx) {\n const file = requirePositional(ctx, 0, \"file.gcode\");\n const client = ctx.client();\n\n if (ctx.globals.dryRun) {\n ctx.out.emit({\n dryRun: true,\n action: \"print\",\n file,\n start: !flagBool(ctx.args, \"no-start\"),\n fixMetadata: !flagBool(ctx.args, \"no-fix-metadata\"),\n });\n return;\n }\n\n const result = await client.uploadAndPrint(file, {\n start: !flagBool(ctx.args, \"no-start\"),\n fixMetadata: flagBool(ctx.args, \"fix-metadata\"),\n transport: (ctx.args.values.transport as \"lan\" | \"auto\") ?? \"auto\",\n onProgress: (p) => ctx.out.log(JSON.stringify({ event: \"upload\", ...p })),\n });\n\n if (flagBool(ctx.args, \"wait-start\") || flagBool(ctx.args, \"wait-complete\")) {\n const { parseWaitCondition } = await import(\"@aubron/ankerts\");\n const cond = flagBool(ctx.args, \"wait-complete\") ? \"complete\" : \"printing\";\n ctx.out.log(`waiting for job to reach \"${cond}\"…`);\n await client.waitFor(parseWaitCondition(cond), {\n timeoutMs: timeoutMs(ctx, flagBool(ctx.args, \"wait-complete\") ? 86400000 : 120000),\n onTick: (s) => ctx.out.log(JSON.stringify(s)),\n });\n }\n await client.close();\n ctx.out.emit(result);\n },\n});\n\nfunction jobControl(verb: \"cancel\" | \"pause\" | \"resume\"): CommandSpec {\n return defineCommand({\n path: [\"job\", verb],\n summary: `${verb[0]!.toUpperCase()}${verb.slice(1)} the current print job.`,\n description:\n `Sends the ${verb} control command to the printer over MQTT (PRINT_CONTROL). ` +\n \"The control values were reverse-engineered and confirmed live on an M5: cancel \" +\n \"returns the printer to idle with the heaters off; pause/resume toggle the print.\",\n transport: \"mqtt\",\n exitCodes: [0, 1, 3, 4, 5, 7],\n examples: [{ cmd: `ankerts job ${verb}` }],\n async run(ctx) {\n const client = ctx.client();\n if (ctx.globals.dryRun) {\n ctx.out.emit({ dryRun: true, action: `job ${verb}` });\n return;\n }\n if (verb === \"cancel\") await client.cancelJob();\n else if (verb === \"pause\") await client.pauseJob();\n else await client.resumeJob();\n await client.close();\n ctx.out.emit({ ok: true, action: verb });\n },\n });\n}\n\nconst camera: CommandSpec = defineCommand({\n path: [\"camera\", \"capture\"],\n summary: \"Capture the printer's video stream (PPPP, optional).\",\n description:\n \"Captures the H.264 video stream over PPPP. This is lower-priority and not yet wired \" +\n \"in this version; it returns a structured not-implemented error so agents can branch \" +\n \"cleanly rather than hang.\",\n transport: \"pppp\",\n args: [\n { name: \"out.h264\", description: \"Output file for the raw H.264 stream.\", required: true },\n ],\n flags: [{ name: \"max-size\", type: \"string\", description: \"Maximum bytes to capture.\" }],\n exitCodes: [0, 1, 4, 5],\n examples: [{ cmd: \"ankerts camera capture out.h264 --max-size 5000000\" }],\n run(ctx) {\n requirePositional(ctx, 0, \"out.h264\");\n throw new AnkerError({\n code: \"not_implemented\",\n message: \"Camera capture is not yet implemented in this version.\",\n transport: \"pppp\",\n hint: \"Use `ankerts printer status` for telemetry; camera streaming is planned.\",\n });\n },\n});\n\nexport const jobCommands = [\n discover,\n print,\n jobControl(\"cancel\"),\n jobControl(\"pause\"),\n jobControl(\"resume\"),\n camera,\n];\n","/**\n * Printer commands: list, status (+ --watch), select, wait.\n */\nimport { parseWaitCondition, type PrinterStatus } from \"@aubron/ankerts\";\nimport { defineCommand, type CommandSpec } from \"../spec.js\";\nimport { flagBool, requirePositional, timeoutMs } from \"../runtime.js\";\n\nconst list: CommandSpec = defineCommand({\n path: [\"printer\", \"list\"],\n summary: \"List the printers on the account.\",\n description:\n \"Reads the printer list from stored config (populated at login). No network call. \" +\n \"Each entry includes DUID, serial, model, and the discovered LAN IP (empty until \" +\n \"you run `ankerts discover --store`).\",\n transport: \"none\",\n exitCodes: [0, 1, 4],\n examples: [\n {\n description: \"Get the first printer's DUID, clean stdout\",\n cmd: \"ankerts printer list --json | jq -r '.[0].duid'\",\n output: \"USPRAKM-000994-YYLLG\",\n },\n { description: \"Bare DUIDs for scripting\", cmd: \"ankerts printer list -q\" },\n ],\n run(ctx) {\n const printers = ctx.client().listPrinters();\n ctx.out.emit(printers, { quietProjection: [\"duid\"] });\n },\n});\n\nconst status: CommandSpec = defineCommand({\n path: [\"printer\", \"status\"],\n summary: \"Show temperatures, job state, and progress.\",\n description:\n \"Queries server-authoritative status over MQTT and normalizes it: temperatures in \" +\n \"°C (converted from 1/100 °C), progress 0–100. For third-party-sliced gcode the \" +\n \"firmware's headline ETA is unreliable, so etaReliable is false and the bogus ETA is \" +\n \"omitted. With --watch, streams status objects as NDJSON (one per line) until interrupted.\",\n transport: \"mqtt\",\n flags: [\n { name: \"watch\", type: \"boolean\", description: \"Stream status as NDJSON until interrupted.\" },\n { name: \"poll\", type: \"string\", description: \"Watch poll interval in seconds (default 2).\" },\n ],\n exitCodes: [0, 1, 3, 4, 5],\n examples: [\n { description: \"One-shot status\", cmd: \"ankerts printer status --json | jq '.nozzle'\" },\n {\n description: \"Watch a print (NDJSON stream)\",\n cmd: \"ankerts printer status --watch | jq -r '.job.progressPct'\",\n },\n ],\n async run(ctx) {\n const client = ctx.client();\n if (!flagBool(ctx.args, \"watch\")) {\n const s = await client.getStatus();\n await client.close();\n ctx.out.emit(s);\n return;\n }\n const pollSec = Number(ctx.args.values.poll ?? 2) || 2;\n ctx.out.log(\"watching status (NDJSON on stdout); Ctrl-C to stop.\");\n await client.subscribeEvents(() => {});\n // Poll snapshots on an interval as the robust floor; emit NDJSON to stdout.\n for (;;) {\n const s = await client.getStatus();\n ctx.out.ndjsonLine(s);\n await new Promise((r) => setTimeout(r, pollSec * 1000));\n }\n },\n});\n\nconst select: CommandSpec = defineCommand({\n path: [\"printer\", \"select\"],\n summary: \"Set the default printer in config.\",\n description:\n \"Selects a printer (by DUID, serial, name, or index) as the default target for \" +\n \"subsequent commands. Persisted to the config store.\",\n transport: \"none\",\n args: [\n {\n name: \"duid|index\",\n description: \"Printer reference (DUID, serial, name, or index).\",\n required: true,\n },\n ],\n exitCodes: [0, 1, 2, 4],\n examples: [\n { description: \"Select by index\", cmd: \"ankerts printer select 0\" },\n { description: \"Select by DUID\", cmd: \"ankerts printer select USPRAKM-000994-YYLLG\" },\n ],\n run(ctx) {\n const ref = requirePositional(ctx, 0, \"duid|index\");\n const printer = ctx.client().selectPrinter(ref);\n ctx.out.emit({ selected: printer.duid, name: printer.name });\n },\n});\n\nconst wait: CommandSpec = defineCommand({\n path: [\"printer\", \"wait\"],\n summary: \"Block until a printer condition holds (re-attachable).\",\n description:\n \"Watches server-authoritative state and resolves once the condition holds — so a \" +\n \"wait killed mid-flight can simply be re-issued and re-derives current state. \" +\n \"Streams NDJSON status ticks on stderr while waiting; on success prints the final \" +\n \"status to stdout (exit 0); on timeout exits 5 (retriable). Conditions: connected | \" +\n \"lan | nozzle>=C | bed>=C | temp-stable | printing | idle | progress>=pct | layer>=n | \" +\n \"complete | failed | cancelled | runout.\",\n transport: \"mqtt\",\n flags: [\n { name: \"until\", type: \"string\", description: \"The condition to wait for (required).\" },\n { name: \"poll\", type: \"string\", description: \"Poll interval in seconds (default 2).\" },\n ],\n exitCodes: [0, 1, 3, 4, 5],\n examples: [\n {\n description: \"Wait for the job to actually start (not just upload-accepted)\",\n cmd: \"ankerts printer wait --until printing --timeout 120\",\n },\n {\n description: \"Cool down after the print completes\",\n cmd: 'ankerts printer wait --until complete && ankerts gcode \"M104 S0\"',\n },\n ],\n async run(ctx) {\n const until = ctx.args.values.until;\n if (typeof until !== \"string\" || until === \"\") {\n const { UsageError } = await import(\"@aubron/ankerts\");\n throw new UsageError({ message: \"--until <condition> is required\" });\n }\n const cond = parseWaitCondition(until);\n const client = ctx.client();\n const pollSec = Number(ctx.args.values.poll ?? 2) || 2;\n const final = await client.waitFor(cond, {\n timeoutMs: timeoutMs(ctx, 600000),\n pollMs: pollSec * 1000,\n onTick: (s: PrinterStatus) => ctx.out.log(JSON.stringify(s)),\n });\n await client.close();\n ctx.out.emit(final);\n },\n});\n\nexport const printerCommands = [list, status, select, wait];\n","/**\n * `ankerts skills install` — copy the bundled `ankerts` Agent Skill into a\n * skills directory so an agent (Claude Code, and any SKILL.md-compatible tool)\n * learns to drive ankerts. Explicit and on-demand — never a postinstall hook.\n */\nimport { cpSync, existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { UsageError } from \"@aubron/ankerts\";\nimport { defineCommand, type CommandSpec } from \"../spec.js\";\nimport { flagBool, flagStr } from \"../runtime.js\";\n\nconst SKILL_NAME = \"ankerts\";\n\n/** Resolve the skill bundled alongside the built CLI (`dist/` → `../skills`). */\nfunction bundledSkillDir(): string {\n return resolve(dirname(fileURLToPath(import.meta.url)), \"..\", \"skills\", SKILL_NAME);\n}\n\nconst install: CommandSpec = defineCommand({\n path: [\"skills\", \"install\"],\n summary: \"Install the bundled `ankerts` Agent Skill into a skills directory.\",\n description:\n \"Copies the SKILL.md that ships inside this package into a skills directory so an \" +\n \"agent learns how to drive ankerts. Defaults to the project's .claude/skills/; \" +\n \"use --global for ~/.claude/skills, or --dir to target any directory (works for any \" +\n \"SKILL.md-compatible agent). Explicit and idempotent — re-run with --force to overwrite.\",\n transport: \"none\",\n flags: [\n {\n name: \"global\",\n type: \"boolean\",\n description: \"Install into ~/.claude/skills instead of the project.\",\n },\n {\n name: \"dir\",\n type: \"string\",\n description: \"Target skills directory (overrides --global/project).\",\n },\n { name: \"force\", type: \"boolean\", description: \"Overwrite an existing install.\" },\n ],\n exitCodes: [0, 1, 2],\n examples: [\n { description: \"Install into the current project\", cmd: \"ankerts skills install\" },\n { description: \"Install for all projects\", cmd: \"ankerts skills install --global\" },\n {\n description: \"Install for another agent\",\n cmd: \"ankerts skills install --dir ~/.codex/skills\",\n },\n ],\n run(ctx) {\n const src = bundledSkillDir();\n if (!existsSync(src)) {\n throw new UsageError({\n code: \"skill_not_bundled\",\n message: \"Bundled skill not found next to the CLI\",\n hint: \"Reinstall @aubron/ankerts-cli — the published package ships skills/ankerts/SKILL.md.\",\n });\n }\n\n const dir = flagStr(ctx.args, \"dir\");\n const skillsDir = dir\n ? resolve(dir)\n : flagBool(ctx.args, \"global\")\n ? join(homedir(), \".claude\", \"skills\")\n : resolve(\".claude\", \"skills\");\n\n const dest = join(skillsDir, SKILL_NAME);\n if (existsSync(dest) && !flagBool(ctx.args, \"force\")) {\n throw new UsageError({\n code: \"skill_exists\",\n message: `Skill already installed at ${dest}`,\n hint: \"Re-run with --force to overwrite.\",\n input: { dest },\n });\n }\n\n if (ctx.globals.dryRun) {\n ctx.out.emit({ dryRun: true, action: \"skills install\", from: src, to: dest });\n return;\n }\n\n cpSync(src, dest, { recursive: true });\n ctx.out.log(`installed ${SKILL_NAME} skill → ${dest}`);\n ctx.out.emit({ installed: dest, skill: SKILL_NAME });\n },\n});\n\nexport const skillsCommands = [install];\n","/**\n * Command registry — aggregates every command spec and adds `describe`.\n */\nimport { buildDescribeTree } from \"../describe.js\";\nimport { defineCommand, type CommandSpec } from \"../spec.js\";\nimport { authCommands } from \"./auth.js\";\nimport { gcodeCommands } from \"./gcode.js\";\nimport { jobCommands } from \"./jobs.js\";\nimport { printerCommands } from \"./printer.js\";\nimport { skillsCommands } from \"./skills.js\";\n\nconst baseCommands: CommandSpec[] = [\n ...authCommands,\n ...printerCommands,\n ...gcodeCommands,\n ...jobCommands,\n ...skillsCommands,\n];\n\nconst describe: CommandSpec = defineCommand({\n path: [\"describe\"],\n summary: \"Print the full command tree as JSON (machine-readable introspection).\",\n description:\n \"Emits every command, flag, type, default, exit code, and example as one JSON \" +\n \"document — so an agent can map the tool's entire surface in a single call instead \" +\n \"of crawling --help pages.\",\n transport: \"none\",\n exitCodes: [0, 1],\n examples: [\n {\n description: \"List every command path\",\n cmd: \"ankerts describe --json | jq -r '.commands[].command'\",\n },\n ],\n run(ctx) {\n ctx.out.emit(buildDescribeTree(allCommands));\n },\n});\n\nexport const allCommands: CommandSpec[] = [...baseCommands, describe];\n","/**\n * Help rendering — the agent's primary learning channel (brief §3). Every\n * command's help shows a summary, a longer description naming the transport, all\n * flags with types/defaults, the relevant exit codes, and worked examples with\n * sample output. Generated from {@link CommandSpec} so it never drifts.\n */\nimport { EXIT_CODES, GLOBAL_FLAGS } from \"./globals.js\";\nimport type { CommandSpec, FlagSpec } from \"./spec.js\";\n\nconst BIN = \"ankerts\";\n\nconst TRANSPORT_BLURB: Record<string, string> = {\n mqtt: \"MQTT over TLS (Anker cloud broker) — works anywhere with internet + credentials.\",\n pppp: \"PPPP (P2P over UDP) — LAN only. Needs the printer's IP stored via discovery.\",\n https: \"HTTPS (Anker cloud API) — account auth and printer list; works anywhere.\",\n none: \"No printer transport (local/config operation).\",\n};\n\nfunction flagUsage(f: FlagSpec): string {\n const dashName = f.name;\n const short = f.short ? `-${f.short}, ` : \"\";\n const valued = f.type === \"boolean\" ? \"\" : ` <${f.type}>`;\n const def = f.default !== undefined ? ` (default: ${String(f.default)})` : \"\";\n return ` ${short}--${dashName}${valued}\\n ${f.description}${def}`;\n}\n\n/** Render full help for one command. */\nexport function renderCommandHelp(spec: CommandSpec): string {\n const path = spec.path.join(\" \");\n const argUsage = spec.args\n .map((a) =>\n a.required\n ? `<${a.name}${a.variadic ? \"...\" : \"\"}>`\n : `[${a.name}${a.variadic ? \"...\" : \"\"}]`,\n )\n .join(\" \");\n const lines: string[] = [];\n\n lines.push(`${BIN} ${path} — ${spec.summary}`, \"\");\n lines.push(\"USAGE\", ` ${BIN} ${path}${argUsage ? ` ${argUsage}` : \"\"} [flags]`, \"\");\n lines.push(\"DESCRIPTION\", ` ${spec.description}`, \"\");\n lines.push(\"TRANSPORT\", ` ${TRANSPORT_BLURB[spec.transport]}`, \"\");\n\n if (spec.args.length) {\n lines.push(\"ARGUMENTS\");\n for (const a of spec.args) {\n lines.push(` ${a.name}${a.variadic ? \"...\" : \"\"} ${a.description}`);\n }\n lines.push(\"\");\n }\n\n const cmdFlags = spec.flags.filter((f) => !GLOBAL_FLAGS.some((g) => g.name === f.name));\n if (cmdFlags.length) {\n lines.push(\"FLAGS\");\n for (const f of cmdFlags) lines.push(flagUsage(f));\n lines.push(\"\");\n }\n\n lines.push(\"GLOBAL FLAGS\");\n for (const f of GLOBAL_FLAGS) lines.push(flagUsage(f));\n lines.push(\"\");\n\n lines.push(\"EXIT CODES\");\n for (const code of spec.exitCodes) lines.push(` ${code} ${EXIT_CODES[code] ?? \"\"}`);\n lines.push(\"\");\n\n if (spec.examples.length) {\n lines.push(\"EXAMPLES\");\n for (const ex of spec.examples) {\n if (ex.description) lines.push(` # ${ex.description}`);\n lines.push(` $ ${ex.cmd}`);\n if (ex.output) for (const o of ex.output.split(\"\\n\")) lines.push(` ${o}`);\n lines.push(\"\");\n }\n }\n return lines.join(\"\\n\");\n}\n\n/** Render the top-level help: noun groups + the CONCEPTS section (§3). */\nexport function renderRootHelp(specs: CommandSpec[]): string {\n const lines: string[] = [];\n lines.push(\n `${BIN} — agent-first CLI for AnkerMake / eufyMake M5 printers`,\n \"\",\n \"USAGE\",\n ` ${BIN} <noun> <verb> [args] [flags]`,\n \"\",\n );\n\n // Group commands by their leading noun.\n const groups = new Map<string, CommandSpec[]>();\n for (const s of specs) {\n const noun = s.path[0]!;\n (groups.get(noun) ?? groups.set(noun, []).get(noun)!).push(s);\n }\n lines.push(\"COMMANDS\");\n for (const [noun, group] of groups) {\n lines.push(` ${noun}`);\n for (const s of group) {\n lines.push(` ${s.path.join(\" \").padEnd(22)} ${s.summary}`);\n }\n }\n lines.push(\"\");\n\n lines.push(\n \"CONCEPTS\",\n \" The M5 speaks THREE independent transports, split by reachability:\",\n \" • MQTT over TLS — gcode, status, control. Works ANYWHERE (internet + creds).\",\n \" • PPPP over UDP — file upload, camera. LAN ONLY; needs a discovered IP.\",\n \" • HTTPS cloud — login, account, printer list. Works anywhere.\",\n \" Cloud-reachable ≠ LAN-reachable: gcode can work while upload (exit 6) cannot,\",\n \" until you run `ankerts discover --store` on the same LAN as the printer.\",\n \"\",\n \"OUTPUT\",\n \" Data → stdout; logs/progress → stderr. JSON by default when piped (text on a\",\n \" TTY). Use --output, --quiet, and --fields to shape it. `ankerts describe --json`\",\n \" dumps the entire command tree for one-call introspection.\",\n \"\",\n `Run \\`${BIN} <command> --help\\` for details, transport, exit codes, and examples.`,\n );\n return lines.join(\"\\n\");\n}\n","/**\n * Output framework — the heart of the agent-first conventions (brief §3).\n *\n * Rules enforced here:\n * - Data → stdout. Logs, progress, diagnostics, errors → stderr. Always.\n * - `--output json|ndjson|text`; default json when stdout is not a TTY, text\n * when it is. `ANKER_OUTPUT` env honored. `--json` is an alias.\n * - No ANSI when not a TTY, when NO_COLOR is set, or in json/ndjson mode.\n * - `--quiet`: bare values, one per line, for `$()` capture and xargs.\n * - `--fields a,b.c`: field mask to trim large objects (protects context).\n * - Streaming commands emit NDJSON (one object per line, unbuffered) on stdout;\n * their progress ticks go to stderr.\n */\n\nexport type OutputMode = \"json\" | \"ndjson\" | \"text\";\n\nexport interface OutputOptions {\n mode?: OutputMode;\n quiet?: boolean;\n fields?: string[];\n /** Stream sinks (injectable for tests). */\n stdout?: (s: string) => void;\n stderr?: (s: string) => void;\n /** Whether stdout is a TTY (drives default mode + color). */\n isTty?: boolean;\n noColor?: boolean;\n}\n\n/** Resolve the effective output mode from flags + env + TTY. */\nexport function resolveMode(opts: {\n flag?: string;\n json?: boolean;\n env?: string;\n isTty: boolean;\n}): OutputMode {\n const pick = (v?: string): OutputMode | undefined =>\n v === \"json\" || v === \"ndjson\" || v === \"text\" ? v : undefined;\n if (opts.json) return \"json\";\n return pick(opts.flag) ?? pick(opts.env) ?? (opts.isTty ? \"text\" : \"json\");\n}\n\n/** Project an object/array to the given dotted field paths. */\nexport function pickFields<T>(value: T, paths: string[]): unknown {\n if (paths.length === 0) return value;\n if (Array.isArray(value)) return value.map((v) => pickFields(v, paths));\n if (value === null || typeof value !== \"object\") return value;\n const out: Record<string, unknown> = {};\n for (const path of paths) {\n const segs = path.split(\".\");\n let src: unknown = value;\n for (const s of segs) {\n src = src && typeof src === \"object\" ? (src as Record<string, unknown>)[s] : undefined;\n }\n if (src !== undefined) out[path] = src;\n }\n return out;\n}\n\nfunction getPath(obj: unknown, path: string): unknown {\n let cur = obj;\n for (const s of path.split(\".\")) {\n cur = cur && typeof cur === \"object\" ? (cur as Record<string, unknown>)[s] : undefined;\n }\n return cur;\n}\n\nfunction scalar(v: unknown): string {\n if (v === null || v === undefined) return \"\";\n if (typeof v === \"object\") return JSON.stringify(v);\n return String(v);\n}\n\nexport class Output {\n readonly mode: OutputMode;\n readonly quiet: boolean;\n readonly fields?: string[];\n private readonly out: (s: string) => void;\n private readonly err: (s: string) => void;\n readonly color: boolean;\n\n constructor(opts: OutputOptions = {}) {\n this.mode = opts.mode ?? \"json\";\n this.quiet = opts.quiet ?? false;\n this.fields = opts.fields;\n this.out = opts.stdout ?? ((s) => process.stdout.write(s));\n this.err = opts.stderr ?? ((s) => process.stderr.write(s));\n this.color = !!opts.isTty && !opts.noColor && this.mode === \"text\";\n }\n\n /** A diagnostic/progress line → stderr (never pollutes stdout). */\n log(message: string): void {\n this.err(`${message}\\n`);\n }\n\n /** A single NDJSON object → stdout, newline-terminated (for streams). */\n ndjsonLine(obj: unknown): void {\n const masked = this.fields ? pickFields(obj, this.fields) : obj;\n this.out(`${JSON.stringify(masked)}\\n`);\n }\n\n /**\n * Emit a command's primary result to stdout, honoring mode/quiet/fields.\n * `quietProjection` is the default field(s) printed in --quiet mode (e.g.\n * `[\"duid\"]` so `printer list -q` yields bare DUIDs).\n */\n emit(value: unknown, opts: { quietProjection?: string[] } = {}): void {\n if (this.quiet) return this.emitQuiet(value, opts.quietProjection);\n const masked = this.fields ? pickFields(value, this.fields) : value;\n if (this.mode === \"ndjson\") {\n const arr = Array.isArray(masked) ? masked : [masked];\n for (const item of arr) this.out(`${JSON.stringify(item)}\\n`);\n return;\n }\n if (this.mode === \"json\") {\n this.out(`${JSON.stringify(masked, null, 2)}\\n`);\n return;\n }\n this.out(`${this.renderText(masked)}\\n`);\n }\n\n private emitQuiet(value: unknown, projection?: string[]): void {\n const fields = this.fields ?? projection;\n const rows = Array.isArray(value) ? value : [value];\n for (const row of rows) {\n if (fields && fields.length && row && typeof row === \"object\") {\n this.out(`${fields.map((f) => scalar(getPath(row, f))).join(\"\\t\")}\\n`);\n } else {\n this.out(`${scalar(row)}\\n`);\n }\n }\n }\n\n /**\n * Emit a structured error (brief §3). In json/ndjson mode the `{ error: … }`\n * body goes to STDOUT so `… --json | jq '.error.hint'` works; in text mode a\n * readable rendering goes to STDERR. The exit code is set by the caller.\n */\n emitError(body: {\n error: {\n code: string;\n message: string;\n retriable: boolean;\n transport?: string;\n hint?: string;\n input?: Record<string, unknown>;\n };\n }): void {\n if (this.mode === \"json\") {\n this.out(`${JSON.stringify(body, null, 2)}\\n`);\n return;\n }\n if (this.mode === \"ndjson\") {\n this.out(`${JSON.stringify(body)}\\n`);\n return;\n }\n const e = body.error;\n const parts = [`error[${e.code}]: ${e.message}`];\n if (e.transport) parts.push(` transport: ${String(e.transport)}`);\n parts.push(` retriable: ${String(e.retriable)}`);\n if (e.hint) parts.push(` hint: ${String(e.hint)}`);\n if (e.input) parts.push(` input: ${JSON.stringify(e.input)}`);\n this.err(`${parts.join(\"\\n\")}\\n`);\n }\n\n /** Best-effort human rendering for text mode. */\n private renderText(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (Array.isArray(value)) {\n return value.map((v) => this.renderText(v)).join(\"\\n\");\n }\n if (typeof value === \"object\") {\n return Object.entries(value as Record<string, unknown>)\n .map(([k, v]) => `${k}: ${typeof v === \"object\" && v ? JSON.stringify(v) : scalar(v)}`)\n .join(\"\\n\");\n }\n return scalar(value);\n }\n}\n"],"mappings":"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","names":["AnkerClient","UsageError","UsageError","parseWaitCondition","UsageError","UsageError","UsageError","resolve","out","AnkerClient"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aubron/ankerts-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Agent-first CLI for AnkerMake / eufyMake M5 printers — a thin shell over @aubron/ankerts.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"skills"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@aubron/ankerts": "0.1.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^25.9.2",
|
|
22
|
+
"eslint": "^10.4.1",
|
|
23
|
+
"tsup": "^8.5.1",
|
|
24
|
+
"typescript": "^6.0.3",
|
|
25
|
+
"vitest": "^4.1.8",
|
|
26
|
+
"@aubron/eslint-config": "0.1.0",
|
|
27
|
+
"@aubron/prettier-config": "0.1.0",
|
|
28
|
+
"@aubron/tsconfig": "0.1.0",
|
|
29
|
+
"@aubron/tsup-config": "0.1.0"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public",
|
|
33
|
+
"provenance": true
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=22"
|
|
37
|
+
},
|
|
38
|
+
"bin": {
|
|
39
|
+
"ankerts": "./dist/index.js"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsup",
|
|
43
|
+
"dev": "tsup --watch",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"lint": "eslint .",
|
|
46
|
+
"typecheck": "tsc --noEmit"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ankerts
|
|
3
|
+
description: Drive an AnkerMake / eufyMake M5-class 3D printer with the `ankerts` CLI — send gcode and read its parsed response (truncation-aware), query status, discover the printer on the LAN, and upload + start prints. Use this whenever the task involves an AnkerMake or eufyMake M5 printer, the `ankerts` command, sending gcode (M-codes / G-codes) to a printer, checking nozzle/bed temps or print progress, or uploading a sliced .gcode file to print.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ankerts
|
|
7
|
+
|
|
8
|
+
`ankerts` is an agent-first CLI for AnkerMake / eufyMake M5 printers. It's built
|
|
9
|
+
to be driven by you: structured output, clean streams, documented exit codes.
|
|
10
|
+
|
|
11
|
+
**Before anything else, learn the full surface in one call:**
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
ankerts describe --json
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
That returns every command, flag, type, default, exit code, and worked example.
|
|
18
|
+
`ankerts --help` and `ankerts <command> --help` give the same per-command detail.
|
|
19
|
+
|
|
20
|
+
## Output contract (rely on this)
|
|
21
|
+
|
|
22
|
+
- **Data → stdout; logs/progress/errors → stderr.** Pipe stdout to `jq` freely.
|
|
23
|
+
- Output is **JSON by default when piped** (text on a TTY). Force with `--json`,
|
|
24
|
+
`--output ndjson`, or `--output text`. `--quiet`/`-q` prints bare values for
|
|
25
|
+
`$()`/`xargs`; `--fields a,b.c` trims large objects.
|
|
26
|
+
- On error in JSON mode, a `{ "error": { code, message, transport, retriable,
|
|
27
|
+
hint, input } }` object is printed to **stdout** — branch on `.error`.
|
|
28
|
+
|
|
29
|
+
## Exit codes
|
|
30
|
+
|
|
31
|
+
`0` ok · `2` usage · `3` auth · `4` no printer/not selected · `5` timeout
|
|
32
|
+
(**retriable**) · `6` transport unavailable (e.g. upload needs LAN) · `7` printer
|
|
33
|
+
rejected. Retry on `5`; do not retry on `2`/`3`.
|
|
34
|
+
|
|
35
|
+
## The three transports (cloud vs LAN)
|
|
36
|
+
|
|
37
|
+
- **MQTT** (cloud, works anywhere): gcode, status, job control.
|
|
38
|
+
- **PPPP** (LAN only): file upload, camera. Needs the printer's IP — run
|
|
39
|
+
`ankerts discover --store` on the same LAN first. Off-LAN upload fails with
|
|
40
|
+
exit 6 and a hint.
|
|
41
|
+
- **HTTPS** (cloud): login, account, printer list.
|
|
42
|
+
|
|
43
|
+
## Common tasks
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
# Auth + pick a printer
|
|
47
|
+
ankerts login --email "$EMAIL" --password - --country US --save # password via stdin
|
|
48
|
+
ankerts printer list --json | jq -r '.[0].duid'
|
|
49
|
+
|
|
50
|
+
# Gcode — check `.truncated`: the firmware caps a reply at a ~512B buffer snapshot
|
|
51
|
+
ankerts gcode M115 --json | jq '{firmware:.fields.FIRMWARE_NAME, truncated}'
|
|
52
|
+
ankerts gcode M105 --json | jq -r '.raw' # temps; short replies come back whole
|
|
53
|
+
ankerts gcode M9998 --json | jq .recognized # false for unknown commands
|
|
54
|
+
|
|
55
|
+
# Status (temps in °C, progress 0–100; bogus third-party-gcode ETA is suppressed)
|
|
56
|
+
ankerts printer status --json | jq '{nozzle,bed,job}'
|
|
57
|
+
|
|
58
|
+
# Upload + start a print (auto-discovers the LAN IP; exit 6 if off-LAN)
|
|
59
|
+
ankerts print model.gcode
|
|
60
|
+
ankerts printer wait --until printing --timeout 120 # block until it starts (exit 5 on timeout)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Gotchas worth knowing
|
|
64
|
+
|
|
65
|
+
- **Replies can be truncated.** A gcode reply is one ~512-byte snapshot of the
|
|
66
|
+
firmware's serial buffer, so a long reply (or one caught mid-write, e.g. `M900`)
|
|
67
|
+
comes back partial with `truncated: true` and often a `+ringbuf:` marker. Check
|
|
68
|
+
`.truncated` before trusting `.raw`/`.fields`; short reads (M105, M114) are fine.
|
|
69
|
+
- `ok` ≠ done. Marlin returns `ok` when a move is _queued_, not finished. For
|
|
70
|
+
"moved physically": `ankerts gcode "G28" --wait-motion` (appends M400).
|
|
71
|
+
- `M109`/`M190` block until temperature is reached — their default timeout is
|
|
72
|
+
generous; don't treat the wait as a hang.
|
|
73
|
+
- State-mutating gcode (e.g. `M900 K…`, `M92`, `M301`, `M851`) persists in RAM
|
|
74
|
+
until power-cycle. `ankerts state snapshot` before, `ankerts state restore`
|
|
75
|
+
(M501) to revert. The CLI warns on stderr unless `--yes`.
|
|
76
|
+
- Long prints: don't hold a process for hours. `ankerts print` returns a handle;
|
|
77
|
+
re-attach later with `ankerts printer wait --until complete` (waits are
|
|
78
|
+
re-derived from server state, so they survive a crash/restart).
|
|
79
|
+
|
|
80
|
+
For the SDK (importing `@aubron/ankerts` into code) see its README; the CLI is a
|
|
81
|
+
thin shell over it, so every CLI capability exists as a typed SDK method.
|