passwd-sso-cli 0.4.47 → 0.4.49

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.
@@ -7,6 +7,7 @@
7
7
  * revoke — Revoke an API key
8
8
  */
9
9
  import { apiRequest } from "../lib/api-client.js";
10
+ import { readMainApiErrorBody } from "../lib/api-error-body.js";
10
11
  import * as output from "../lib/output.js";
11
12
  export async function apiKeyListCommand(options = {}) {
12
13
  const res = await apiRequest("/api/api-keys");
@@ -73,8 +74,8 @@ export async function apiKeyCreateCommand(opts) {
73
74
  },
74
75
  });
75
76
  if (!res.ok) {
76
- const err = res.data;
77
- output.error(`Failed to create API key: ${err.error ?? `HTTP ${res.status}`}`);
77
+ const err = readMainApiErrorBody(res.data);
78
+ output.error(`Failed to create API key: ${err?.error ?? `HTTP ${res.status}`}`);
78
79
  return;
79
80
  }
80
81
  if (opts.json) {
@@ -99,12 +100,12 @@ export async function apiKeyCreateCommand(opts) {
99
100
  export async function apiKeyRevokeCommand(id, options = {}) {
100
101
  const res = await apiRequest(`/api/api-keys/${encodeURIComponent(id)}`, { method: "DELETE" });
101
102
  if (!res.ok) {
102
- const err = res.data;
103
+ const err = readMainApiErrorBody(res.data);
103
104
  if (options.json) {
104
- output.json({ success: false, error: err.error ?? `HTTP ${res.status}` });
105
+ output.json({ success: false, error: err?.error ?? `HTTP ${res.status}` });
105
106
  }
106
107
  else {
107
- output.error(`Failed to revoke API key: ${err.error ?? `HTTP ${res.status}`}`);
108
+ output.error(`Failed to revoke API key: ${err?.error ?? `HTTP ${res.status}`}`);
108
109
  }
109
110
  return;
110
111
  }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Typed error-body access for main-API responses.
3
+ *
4
+ * Mirrors `src/lib/http/api-response.ts` `MainApiErrorBody` — keep in sync.
5
+ * The CLI cannot import from the app's `src/` (separate tsconfig / `rootDir`),
6
+ * so this is a deliberate type duplicate with the same shape and invariants.
7
+ * See `docs/api/error-handling.md` for the canonical envelope.
8
+ *
9
+ * Keep in sync with the other 2 copies:
10
+ * - src/lib/http/read-api-error-body.ts (main, canonical)
11
+ * - extension/src/lib/api-error-body.ts (browser extension)
12
+ * CI drift check: scripts/checks/check-api-error-body-drift.sh
13
+ *
14
+ * Note: OAuth endpoints (`/api/mcp/token`, `/api/mcp/register`) use the RFC 6749
15
+ * error envelope (`error`, `error_description`), NOT this shape. Reads from
16
+ * those endpoints belong in `cli/src/lib/oauth.ts` and are intentionally not
17
+ * migrated here.
18
+ */
19
+ export type MainApiErrorBody = {
20
+ readonly error: string;
21
+ readonly details?: unknown;
22
+ readonly lockedUntil?: string | null;
23
+ readonly currentKeyVersion?: number;
24
+ };
25
+ export declare function readApiErrorBody(res: Response): Promise<MainApiErrorBody | null>;
26
+ /**
27
+ * Read `details.message` from an already-parsed error body.
28
+ *
29
+ * The `apiRequest` wrapper in `api-client.ts` returns `res.data` as the raw
30
+ * JSON body (success OR error shape). On error paths, callers can pass that
31
+ * body here after narrowing it via `readMainApiErrorBody`.
32
+ */
33
+ export declare function getApiErrorMessage(body: MainApiErrorBody | null): string | null;
34
+ /**
35
+ * Read a single named field from `body.details` with a runtime type guard.
36
+ *
37
+ * Mirrors `getApiErrorDetail` in the main copy. See that file for full
38
+ * rationale.
39
+ */
40
+ export declare function getApiErrorDetail<T>(body: MainApiErrorBody | null, field: string, guard: (value: unknown) => value is T): T | null;
41
+ /**
42
+ * Read `details.properties[<field>].errors` from a Zod `treeifyError()` shape.
43
+ *
44
+ * Centralizes the per-field Zod-tree access pattern used by validation-error
45
+ * consumers (slug, url, etc.). Returns the errors array if present, `null`
46
+ * otherwise.
47
+ */
48
+ export declare function getApiErrorFieldErrors(body: MainApiErrorBody | null, field: string): readonly unknown[] | null;
49
+ /**
50
+ * Narrow an unknown value (e.g. `apiRequest`'s `res.data`) to `MainApiErrorBody`.
51
+ *
52
+ * Useful at error-path call sites that consume the wrapper's `res.data` rather
53
+ * than reading the `Response` directly.
54
+ */
55
+ export declare function readMainApiErrorBody(value: unknown): MainApiErrorBody | null;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Typed error-body access for main-API responses.
3
+ *
4
+ * Mirrors `src/lib/http/api-response.ts` `MainApiErrorBody` — keep in sync.
5
+ * The CLI cannot import from the app's `src/` (separate tsconfig / `rootDir`),
6
+ * so this is a deliberate type duplicate with the same shape and invariants.
7
+ * See `docs/api/error-handling.md` for the canonical envelope.
8
+ *
9
+ * Keep in sync with the other 2 copies:
10
+ * - src/lib/http/read-api-error-body.ts (main, canonical)
11
+ * - extension/src/lib/api-error-body.ts (browser extension)
12
+ * CI drift check: scripts/checks/check-api-error-body-drift.sh
13
+ *
14
+ * Note: OAuth endpoints (`/api/mcp/token`, `/api/mcp/register`) use the RFC 6749
15
+ * error envelope (`error`, `error_description`), NOT this shape. Reads from
16
+ * those endpoints belong in `cli/src/lib/oauth.ts` and are intentionally not
17
+ * migrated here.
18
+ */
19
+ export async function readApiErrorBody(res) {
20
+ if (res.ok)
21
+ return null;
22
+ const json = await res.json().catch(() => null);
23
+ if (!json ||
24
+ typeof json !== "object" ||
25
+ typeof json.error !== "string") {
26
+ return null;
27
+ }
28
+ return json;
29
+ }
30
+ /**
31
+ * Read `details.message` from an already-parsed error body.
32
+ *
33
+ * The `apiRequest` wrapper in `api-client.ts` returns `res.data` as the raw
34
+ * JSON body (success OR error shape). On error paths, callers can pass that
35
+ * body here after narrowing it via `readMainApiErrorBody`.
36
+ */
37
+ export function getApiErrorMessage(body) {
38
+ if (!body || typeof body.details !== "object" || body.details === null) {
39
+ return null;
40
+ }
41
+ const message = body.details.message;
42
+ return typeof message === "string" ? message : null;
43
+ }
44
+ /**
45
+ * Read a single named field from `body.details` with a runtime type guard.
46
+ *
47
+ * Mirrors `getApiErrorDetail` in the main copy. See that file for full
48
+ * rationale.
49
+ */
50
+ export function getApiErrorDetail(body, field, guard) {
51
+ if (!body || typeof body.details !== "object" || body.details === null) {
52
+ return null;
53
+ }
54
+ const value = body.details[field];
55
+ return guard(value) ? value : null;
56
+ }
57
+ /**
58
+ * Read `details.properties[<field>].errors` from a Zod `treeifyError()` shape.
59
+ *
60
+ * Centralizes the per-field Zod-tree access pattern used by validation-error
61
+ * consumers (slug, url, etc.). Returns the errors array if present, `null`
62
+ * otherwise.
63
+ */
64
+ export function getApiErrorFieldErrors(body, field) {
65
+ if (!body || typeof body.details !== "object" || body.details === null) {
66
+ return null;
67
+ }
68
+ const properties = body.details.properties;
69
+ if (!properties || typeof properties !== "object")
70
+ return null;
71
+ const fieldObj = properties[field];
72
+ if (!fieldObj || typeof fieldObj !== "object")
73
+ return null;
74
+ const errors = fieldObj.errors;
75
+ return Array.isArray(errors) ? errors : null;
76
+ }
77
+ /**
78
+ * Narrow an unknown value (e.g. `apiRequest`'s `res.data`) to `MainApiErrorBody`.
79
+ *
80
+ * Useful at error-path call sites that consume the wrapper's `res.data` rather
81
+ * than reading the `Response` directly.
82
+ */
83
+ export function readMainApiErrorBody(value) {
84
+ if (!value || typeof value !== "object")
85
+ return null;
86
+ if (typeof value.error !== "string")
87
+ return null;
88
+ return value;
89
+ }
90
+ //# sourceMappingURL=api-error-body.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "passwd-sso-cli",
3
- "version": "0.4.47",
3
+ "version": "0.4.49",
4
4
  "description": "CLI for passwd-sso password manager",
5
5
  "type": "module",
6
6
  "license": "MIT",