@manfred-kunze-dev/iot-cli 3.0.0-dev.1

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.
@@ -0,0 +1,183 @@
1
+ import { Command } from "commander";
2
+ import { readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { resolveConfig, isJsonOutput } from "../lib/config.js";
6
+ import { printTable, printJson, printKeyValue } from "../lib/output.js";
7
+ import { handleError, CLIError, EXIT } from "../lib/errors.js";
8
+ const HTTP_METHODS = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ /**
11
+ * Resolve the OpenAPI spec to use. Bundled by default; --live re-fetches
12
+ * from the active context's base URL.
13
+ */
14
+ async function loadSpec(command, live) {
15
+ if (!live) {
16
+ const bundled = join(__dirname, "..", "..", "openapi", "openapi.json");
17
+ return JSON.parse(readFileSync(bundled, "utf-8"));
18
+ }
19
+ const config = resolveConfig(command);
20
+ const url = `${config.baseUrl.replace(/\/+$/, "")}/v3/api-docs`;
21
+ const res = await fetch(url, {
22
+ headers: { Authorization: `Bearer ${config.apiKey}` },
23
+ });
24
+ if (!res.ok) {
25
+ throw new CLIError(`Failed to fetch live spec from ${url}: HTTP ${res.status}. ` +
26
+ `Is the backend running with SPRING_PROFILES_ACTIVE=spec?`, EXIT.GENERIC);
27
+ }
28
+ return (await res.json());
29
+ }
30
+ export function makeDocsCommand() {
31
+ const cmd = new Command("docs").description("Browse API documentation from the OpenAPI spec");
32
+ cmd
33
+ .command("list")
34
+ .description("List command groups (OpenAPI tags)")
35
+ .option("--live", "Fetch the spec from the server instead of the bundled copy")
36
+ .action(async (opts, command) => {
37
+ try {
38
+ const spec = await loadSpec(command, opts.live === true);
39
+ const groups = collectGroups(spec);
40
+ if (isJsonOutput(command)) {
41
+ printJson({ groups });
42
+ return;
43
+ }
44
+ printTable(groups, [
45
+ { key: "name", header: "GROUP" },
46
+ { key: "operations", header: "OPS" },
47
+ { key: "description", header: "DESCRIPTION" },
48
+ ]);
49
+ }
50
+ catch (err) {
51
+ handleError(err, { json: isJsonOutput(command) });
52
+ }
53
+ });
54
+ cmd
55
+ .command("sections")
56
+ .description("List all operationIds across the spec")
57
+ .option("--live", "Fetch the spec from the server")
58
+ .action(async (opts, command) => {
59
+ try {
60
+ const spec = await loadSpec(command, opts.live === true);
61
+ const ops = collectOperations(spec);
62
+ if (isJsonOutput(command)) {
63
+ printJson({ operations: ops });
64
+ return;
65
+ }
66
+ printTable(ops, [
67
+ { key: "operationId", header: "OPERATION" },
68
+ { key: "method", header: "METHOD" },
69
+ { key: "path", header: "PATH" },
70
+ { key: "tag", header: "GROUP" },
71
+ ]);
72
+ }
73
+ catch (err) {
74
+ handleError(err, { json: isJsonOutput(command) });
75
+ }
76
+ });
77
+ cmd
78
+ .command("show <operationId>")
79
+ .description("Show details for a single operation")
80
+ .option("--live", "Fetch the spec from the server")
81
+ .action(async (operationId, opts, command) => {
82
+ try {
83
+ const spec = await loadSpec(command, opts.live === true);
84
+ const ops = collectOperations(spec);
85
+ const op = ops.find((o) => o.operationId === operationId);
86
+ if (!op) {
87
+ throw new CLIError(`Operation "${operationId}" not found.`, EXIT.NOT_FOUND);
88
+ }
89
+ if (isJsonOutput(command)) {
90
+ printJson(op);
91
+ return;
92
+ }
93
+ printKeyValue({
94
+ Operation: op.operationId,
95
+ Method: op.method.toUpperCase(),
96
+ Path: op.path,
97
+ Group: op.tag ?? "(none)",
98
+ Summary: op.summary ?? "",
99
+ Description: op.description ?? "",
100
+ "Response codes": Object.keys(op.responses ?? {}).join(", ") || "(none)",
101
+ });
102
+ }
103
+ catch (err) {
104
+ handleError(err, { json: isJsonOutput(command) });
105
+ }
106
+ });
107
+ cmd
108
+ .command("search <query>")
109
+ .description("Search summaries, paths, and operationIds (case-insensitive)")
110
+ .option("--live", "Fetch the spec from the server")
111
+ .action(async (query, opts, command) => {
112
+ try {
113
+ const spec = await loadSpec(command, opts.live === true);
114
+ const q = query.toLowerCase();
115
+ const matches = collectOperations(spec).filter((op) => {
116
+ return (op.path.toLowerCase().includes(q) ||
117
+ (op.operationId?.toLowerCase().includes(q) ?? false) ||
118
+ (op.summary?.toLowerCase().includes(q) ?? false));
119
+ });
120
+ if (isJsonOutput(command)) {
121
+ printJson({ query, matches });
122
+ return;
123
+ }
124
+ if (matches.length === 0) {
125
+ process.stderr.write(`No matches for "${query}".\n`);
126
+ return;
127
+ }
128
+ printTable(matches, [
129
+ { key: "operationId", header: "OPERATION" },
130
+ { key: "method", header: "METHOD" },
131
+ { key: "path", header: "PATH" },
132
+ { key: "summary", header: "SUMMARY" },
133
+ ]);
134
+ }
135
+ catch (err) {
136
+ handleError(err, { json: isJsonOutput(command) });
137
+ }
138
+ });
139
+ return cmd;
140
+ }
141
+ function collectGroups(spec) {
142
+ const counts = new Map();
143
+ for (const [, item] of Object.entries(spec.paths ?? {})) {
144
+ for (const method of HTTP_METHODS) {
145
+ const op = item[method];
146
+ if (!op)
147
+ continue;
148
+ const tags = op.tags ?? ["(untagged)"];
149
+ for (const tag of tags) {
150
+ counts.set(tag, (counts.get(tag) ?? 0) + 1);
151
+ }
152
+ }
153
+ }
154
+ const descriptions = new Map((spec.tags ?? []).map((t) => [t.name, t.description ?? ""]));
155
+ return [...counts.entries()]
156
+ .sort((a, b) => a[0].localeCompare(b[0]))
157
+ .map(([name, operations]) => ({
158
+ name,
159
+ operations,
160
+ description: descriptions.get(name) ?? "",
161
+ }));
162
+ }
163
+ function collectOperations(spec) {
164
+ const out = [];
165
+ for (const [path, item] of Object.entries(spec.paths ?? {})) {
166
+ for (const method of HTTP_METHODS) {
167
+ const op = item[method];
168
+ if (!op)
169
+ continue;
170
+ out.push({
171
+ operationId: op.operationId ?? `${method.toUpperCase()} ${path}`,
172
+ method,
173
+ path,
174
+ tag: op.tags?.[0],
175
+ summary: op.summary,
176
+ description: op.description,
177
+ responses: op.responses,
178
+ });
179
+ }
180
+ }
181
+ return out.sort((a, b) => a.operationId.localeCompare(b.operationId));
182
+ }
183
+ //# sourceMappingURL=docs.js.map
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ import { Command } from "commander";
4
+ import { makeAuthCommand } from "./commands/auth.js";
5
+ import { makeContextCommand } from "./commands/context.js";
6
+ import { makeConfigCommand } from "./commands/config.js";
7
+ import { makeDocsCommand } from "./commands/docs.js";
8
+ import { checkForUpdates } from "./lib/update-notifier.js";
9
+ const require = createRequire(import.meta.url);
10
+ const pkg = require("../package.json");
11
+ const program = new Command();
12
+ program
13
+ .name("iot")
14
+ .description("CLI for the iot platform by Manfred Kunze Development " +
15
+ "(also installed as `mkd-iot` and `mki`)")
16
+ .version(pkg.version)
17
+ .option("--api-key <key>", "API key (overrides config)")
18
+ .option("--base-url <url>", "Base URL (overrides config)")
19
+ .option("--context <name>", "Use a named context for this invocation only")
20
+ .option("--json", "Output raw JSON instead of formatted tables")
21
+ .option("--no-color", "Disable colored output")
22
+ .option("--verbose", "Print resolved base URL, masked key, and request info on stderr");
23
+ program.addCommand(makeAuthCommand());
24
+ program.addCommand(makeContextCommand());
25
+ program.addCommand(makeConfigCommand());
26
+ program.addCommand(makeDocsCommand());
27
+ const json = process.argv.includes("--json");
28
+ const updater = checkForUpdates(pkg.version, { json });
29
+ program
30
+ .parseAsync()
31
+ .then(async () => {
32
+ await updater.notify();
33
+ })
34
+ .catch((err) => {
35
+ process.stderr.write(`Fatal: ${err.message}\n`);
36
+ process.exit(1);
37
+ });
38
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,15 @@
1
+ import { type Client } from "openapi-fetch";
2
+ import type { Command } from "commander";
3
+ import type { paths } from "../generated/openapi.js";
4
+ import { type IotConfig } from "./config.js";
5
+ export type IotClient = Client<paths>;
6
+ /**
7
+ * Build an openapi-fetch client bound to the resolved config. The middleware
8
+ * stack converts non-2xx responses into typed `ApiError`s so callers get a
9
+ * uniform error surface.
10
+ */
11
+ export declare function getClient(command: Command): {
12
+ client: IotClient;
13
+ config: IotConfig;
14
+ };
15
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1,55 @@
1
+ import createClient from "openapi-fetch";
2
+ import { resolveConfig, isVerbose } from "./config.js";
3
+ import { ApiError } from "./errors.js";
4
+ import { maskKey } from "./output.js";
5
+ const errorMiddleware = {
6
+ async onResponse({ request, response }) {
7
+ if (response.ok)
8
+ return undefined;
9
+ let body = {};
10
+ try {
11
+ body = (await response.clone().json());
12
+ }
13
+ catch {
14
+ // Empty or non-JSON body — leave defaults.
15
+ }
16
+ const url = new URL(request.url);
17
+ throw new ApiError({
18
+ status: body.status ?? response.status,
19
+ message: body.detail ??
20
+ body.message ??
21
+ body.error ??
22
+ `HTTP ${response.status}: ${response.statusText}`,
23
+ code: body.code,
24
+ errors: body.errors,
25
+ method: request.method,
26
+ path: url.pathname,
27
+ });
28
+ },
29
+ };
30
+ /**
31
+ * Build an openapi-fetch client bound to the resolved config. The middleware
32
+ * stack converts non-2xx responses into typed `ApiError`s so callers get a
33
+ * uniform error surface.
34
+ */
35
+ export function getClient(command) {
36
+ const config = resolveConfig(command);
37
+ const client = createClient({
38
+ baseUrl: config.baseUrl.replace(/\/+$/, ""),
39
+ headers: { Authorization: `Bearer ${config.apiKey}` },
40
+ });
41
+ if (isVerbose(command)) {
42
+ const verboseMiddleware = {
43
+ onRequest({ request }) {
44
+ const url = new URL(request.url);
45
+ process.stderr.write(`[verbose] ${request.method} ${url.pathname}${url.search} ` +
46
+ `(base=${config.baseUrl}, key=${maskKey(config.apiKey)})\n`);
47
+ return undefined;
48
+ },
49
+ };
50
+ client.use(verboseMiddleware);
51
+ }
52
+ client.use(errorMiddleware);
53
+ return { client, config };
54
+ }
55
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1,63 @@
1
+ import Conf from "conf";
2
+ import type { Command } from "commander";
3
+ export interface ContextEntry {
4
+ baseUrl: string;
5
+ apiKey: string;
6
+ }
7
+ export interface IotConfigStore {
8
+ currentContext: string;
9
+ contexts: Record<string, ContextEntry>;
10
+ }
11
+ export interface IotConfig {
12
+ apiKey: string;
13
+ baseUrl: string;
14
+ }
15
+ /**
16
+ * Fallback base URL when the user has no context, env var, or local config.
17
+ * Override at build time by setting IOT_DEFAULT_BASE_URL — e.g. for distributions
18
+ * targeting a different deployment.
19
+ */
20
+ export declare const DEFAULT_BASE_URL: string;
21
+ /**
22
+ * Conf store. The cwd override allows tests (and users who want strict env
23
+ * isolation) to redirect the store via IOT_CLI_CONFIG_DIR.
24
+ */
25
+ declare const store: Conf<IotConfigStore>;
26
+ export { store };
27
+ export declare function validateContextName(name: string): void;
28
+ export declare function getActiveContextName(): string;
29
+ export declare function getActiveContext(): ContextEntry | undefined;
30
+ export declare function getAllContexts(): Record<string, ContextEntry>;
31
+ export declare function getContext(name: string): ContextEntry | undefined;
32
+ export declare function getContextCount(): number;
33
+ export declare function setContext(name: string, entry: ContextEntry): void;
34
+ export declare function deleteContext(name: string): void;
35
+ export declare function renameContext(oldName: string, newName: string): void;
36
+ export declare function setActiveContext(name: string): void;
37
+ /**
38
+ * Resolve configuration with priority:
39
+ * 1. CLI flag (--api-key, --base-url)
40
+ * 2. Per-invocation context override (--context <name>)
41
+ * 3. Environment variables (IOT_API_KEY, IOT_BASE_URL)
42
+ * 4. Local .iot file (cwd or any ancestor)
43
+ * 5. Active context from config store
44
+ */
45
+ export declare function resolveConfig(command: Command): IotConfig;
46
+ /** Walk up Commander's parent chain to find root program */
47
+ export declare function getRootCommand(cmd: Command): Command;
48
+ /** Check if --json flag is set on root command */
49
+ export declare function isJsonOutput(command: Command): boolean;
50
+ /** Check if --verbose flag is set on root command */
51
+ export declare function isVerbose(command: Command): boolean;
52
+ /**
53
+ * Read user preference (config get/set). Distinct from contexts —
54
+ * preferences are global, not per-environment.
55
+ */
56
+ export type PreferenceKey = "defaultOutput" | "colorMode" | "pageSize";
57
+ export declare function isPreferenceKey(s: string): s is PreferenceKey;
58
+ export declare function getPreference<T = string>(key: PreferenceKey): T | undefined;
59
+ export declare function setPreference(key: PreferenceKey, value: string): void;
60
+ export declare function unsetPreference(key: PreferenceKey): void;
61
+ export declare function listPreferences(): Record<string, string>;
62
+ export declare function listPreferenceKeys(): readonly PreferenceKey[];
63
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1,188 @@
1
+ import Conf from "conf";
2
+ import { readFileSync, existsSync } from "node:fs";
3
+ import { dirname, resolve } from "node:path";
4
+ /**
5
+ * Fallback base URL when the user has no context, env var, or local config.
6
+ * Override at build time by setting IOT_DEFAULT_BASE_URL — e.g. for distributions
7
+ * targeting a different deployment.
8
+ */
9
+ export const DEFAULT_BASE_URL = process.env.IOT_DEFAULT_BASE_URL ?? "https://iot.manfred-kunze.dev/api";
10
+ const CONFIG_DEFAULTS = {
11
+ currentContext: "default",
12
+ contexts: {},
13
+ };
14
+ /**
15
+ * Conf store. The cwd override allows tests (and users who want strict env
16
+ * isolation) to redirect the store via IOT_CLI_CONFIG_DIR.
17
+ */
18
+ const store = new Conf({
19
+ projectName: "iot-cli",
20
+ projectSuffix: "",
21
+ cwd: process.env.IOT_CLI_CONFIG_DIR,
22
+ defaults: CONFIG_DEFAULTS,
23
+ });
24
+ export { store };
25
+ const CONTEXT_NAME_RE = /^[a-zA-Z0-9_-]+$/;
26
+ export function validateContextName(name) {
27
+ if (!CONTEXT_NAME_RE.test(name)) {
28
+ throw new Error("Context name must contain only letters, numbers, hyphens, and underscores.");
29
+ }
30
+ }
31
+ export function getActiveContextName() {
32
+ return store.get("currentContext") ?? "default";
33
+ }
34
+ export function getActiveContext() {
35
+ const name = getActiveContextName();
36
+ const contexts = store.get("contexts") ?? {};
37
+ return contexts[name];
38
+ }
39
+ export function getAllContexts() {
40
+ return store.get("contexts") ?? {};
41
+ }
42
+ export function getContext(name) {
43
+ return getAllContexts()[name];
44
+ }
45
+ export function getContextCount() {
46
+ return Object.keys(getAllContexts()).length;
47
+ }
48
+ export function setContext(name, entry) {
49
+ validateContextName(name);
50
+ const contexts = getAllContexts();
51
+ contexts[name] = entry;
52
+ store.set("contexts", contexts);
53
+ }
54
+ export function deleteContext(name) {
55
+ const contexts = getAllContexts();
56
+ if (!contexts[name]) {
57
+ throw new Error(`Context "${name}" does not exist.`);
58
+ }
59
+ if (Object.keys(contexts).length === 1) {
60
+ throw new Error("Cannot delete the last remaining context.");
61
+ }
62
+ delete contexts[name];
63
+ store.set("contexts", contexts);
64
+ if (getActiveContextName() === name) {
65
+ const remaining = Object.keys(contexts)[0];
66
+ store.set("currentContext", remaining);
67
+ }
68
+ }
69
+ export function renameContext(oldName, newName) {
70
+ validateContextName(newName);
71
+ const contexts = getAllContexts();
72
+ if (!contexts[oldName]) {
73
+ throw new Error(`Context "${oldName}" does not exist.`);
74
+ }
75
+ if (contexts[newName]) {
76
+ throw new Error(`Context "${newName}" already exists.`);
77
+ }
78
+ contexts[newName] = contexts[oldName];
79
+ delete contexts[oldName];
80
+ store.set("contexts", contexts);
81
+ if (getActiveContextName() === oldName) {
82
+ store.set("currentContext", newName);
83
+ }
84
+ }
85
+ export function setActiveContext(name) {
86
+ const contexts = getAllContexts();
87
+ if (!contexts[name]) {
88
+ const available = Object.keys(contexts).join(", ") || "(none)";
89
+ throw new Error(`Context "${name}" does not exist. Available contexts: ${available}`);
90
+ }
91
+ store.set("currentContext", name);
92
+ }
93
+ /**
94
+ * Walk up from cwd to filesystem root looking for a .iot JSON file.
95
+ */
96
+ function readLocalFile() {
97
+ let dir = process.cwd();
98
+ while (true) {
99
+ const candidate = resolve(dir, ".iot");
100
+ if (existsSync(candidate)) {
101
+ try {
102
+ const parsed = JSON.parse(readFileSync(candidate, "utf-8"));
103
+ return { apiKey: parsed.apiKey, baseUrl: parsed.baseUrl };
104
+ }
105
+ catch {
106
+ // Malformed file — fall through, don't keep searching ancestors.
107
+ return {};
108
+ }
109
+ }
110
+ const parent = dirname(dir);
111
+ if (parent === dir)
112
+ return {};
113
+ dir = parent;
114
+ }
115
+ }
116
+ /**
117
+ * Resolve configuration with priority:
118
+ * 1. CLI flag (--api-key, --base-url)
119
+ * 2. Per-invocation context override (--context <name>)
120
+ * 3. Environment variables (IOT_API_KEY, IOT_BASE_URL)
121
+ * 4. Local .iot file (cwd or any ancestor)
122
+ * 5. Active context from config store
123
+ */
124
+ export function resolveConfig(command) {
125
+ const root = getRootCommand(command);
126
+ const opts = root.opts();
127
+ const local = readLocalFile();
128
+ // --context <name> picks a specific stored context for this call without
129
+ // changing the persistent active context.
130
+ const overrideCtx = typeof opts.context === "string" ? getContext(opts.context) : undefined;
131
+ if (typeof opts.context === "string" && !overrideCtx) {
132
+ throw new Error(`Context "${opts.context}" does not exist. Run "iot context list" to see available contexts.`);
133
+ }
134
+ const fallbackCtx = overrideCtx ?? getActiveContext();
135
+ const apiKey = opts.apiKey ?? process.env.IOT_API_KEY ?? local.apiKey ?? fallbackCtx?.apiKey;
136
+ const baseUrl = opts.baseUrl ??
137
+ process.env.IOT_BASE_URL ??
138
+ local.baseUrl ??
139
+ fallbackCtx?.baseUrl ??
140
+ DEFAULT_BASE_URL;
141
+ if (!apiKey) {
142
+ throw new Error('No API key configured. Run "iot auth login".');
143
+ }
144
+ return { apiKey, baseUrl };
145
+ }
146
+ /** Walk up Commander's parent chain to find root program */
147
+ export function getRootCommand(cmd) {
148
+ let root = cmd;
149
+ while (root.parent)
150
+ root = root.parent;
151
+ return root;
152
+ }
153
+ /** Check if --json flag is set on root command */
154
+ export function isJsonOutput(command) {
155
+ return getRootCommand(command).opts().json === true;
156
+ }
157
+ /** Check if --verbose flag is set on root command */
158
+ export function isVerbose(command) {
159
+ return getRootCommand(command).opts().verbose === true;
160
+ }
161
+ const PREFERENCE_KEYS = ["defaultOutput", "colorMode", "pageSize"];
162
+ export function isPreferenceKey(s) {
163
+ return PREFERENCE_KEYS.includes(s);
164
+ }
165
+ export function getPreference(key) {
166
+ // conf stores preferences under a `preferences` map to keep them separate
167
+ // from contexts. We don't pre-declare them in the schema so the user can
168
+ // add their own arbitrary keys later (still validated through isPreferenceKey).
169
+ const prefs = (store.get("preferences") ?? {});
170
+ return prefs[key];
171
+ }
172
+ export function setPreference(key, value) {
173
+ const prefs = (store.get("preferences") ?? {});
174
+ prefs[key] = value;
175
+ store.set("preferences", prefs);
176
+ }
177
+ export function unsetPreference(key) {
178
+ const prefs = (store.get("preferences") ?? {});
179
+ delete prefs[key];
180
+ store.set("preferences", prefs);
181
+ }
182
+ export function listPreferences() {
183
+ return (store.get("preferences") ?? {});
184
+ }
185
+ export function listPreferenceKeys() {
186
+ return PREFERENCE_KEYS;
187
+ }
188
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Process exit codes. Stable contract for scripts piping into the CLI.
3
+ * Update README.md when these change.
4
+ */
5
+ export declare const EXIT: {
6
+ readonly OK: 0;
7
+ readonly GENERIC: 1;
8
+ readonly UNAUTHORIZED: 2;
9
+ readonly FORBIDDEN: 3;
10
+ readonly NOT_FOUND: 4;
11
+ readonly CONFLICT: 5;
12
+ readonly VALIDATION: 6;
13
+ readonly SERVER: 7;
14
+ readonly NETWORK: 8;
15
+ };
16
+ export type ExitCode = (typeof EXIT)[keyof typeof EXIT];
17
+ /**
18
+ * Anything the CLI throws on purpose. Carries the exit code we want the
19
+ * process to terminate with and an optional one-line suggestion the user
20
+ * can act on ("Run iot auth login").
21
+ */
22
+ export declare class CLIError extends Error {
23
+ readonly exitCode: ExitCode;
24
+ readonly suggestion?: string;
25
+ constructor(message: string, exitCode?: ExitCode, suggestion?: string);
26
+ }
27
+ export interface ApiErrorPayload {
28
+ status: number;
29
+ code?: string;
30
+ message: string;
31
+ errors?: unknown;
32
+ /** Original request (method + path) — useful in 404 messages. */
33
+ method?: string;
34
+ path?: string;
35
+ }
36
+ /**
37
+ * Thrown by the openapi-fetch error middleware when the server returns a
38
+ * non-2xx response. Carries the parsed response body and request info.
39
+ */
40
+ export declare class ApiError extends Error {
41
+ readonly status: number;
42
+ readonly code?: string;
43
+ readonly errors?: unknown;
44
+ readonly method?: string;
45
+ readonly path?: string;
46
+ constructor(payload: ApiErrorPayload);
47
+ }
48
+ /**
49
+ * Map an HTTP status to a CLIError with the spec-mandated exit code.
50
+ * Pulled out so test fixtures can assert the mapping without spinning a server.
51
+ */
52
+ export declare function apiErrorToCliError(err: ApiError): CLIError;
53
+ /**
54
+ * Detect a generic node-fetch network failure (DNS, refused, timeout).
55
+ * openapi-fetch surfaces these as raw `TypeError` / `Error` instances.
56
+ */
57
+ export declare function isNetworkError(err: unknown): boolean;
58
+ /**
59
+ * Convert any thrown value into a CLIError. Pure function for testability.
60
+ */
61
+ export declare function toCliError(err: unknown, baseUrl?: string): CLIError;
62
+ /**
63
+ * Single top-level error handler. Writes to stderr (never stdout) so
64
+ * pipelines like `iot foo --json | jq` keep working even on failure.
65
+ *
66
+ * In JSON mode the error is serialised as
67
+ * { "error": { "code": "...", "message": "...", "status": 401 } }
68
+ */
69
+ export declare function handleError(err: unknown, opts: {
70
+ json: boolean;
71
+ baseUrl?: string;
72
+ }): never;
73
+ //# sourceMappingURL=errors.d.ts.map