@sigma-auth/cli 0.0.2 → 0.0.3

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/README.md CHANGED
@@ -33,7 +33,24 @@ bunx @sigma-auth/cli identity create \
33
33
  | `sigma backup push` | POST ciphertext only |
34
34
  | `sigma oauth register` | Session `/api/oauth-clients` or RFC 7591 DCR |
35
35
  | `sigma doctor` | Env, files, RFC 8414, session |
36
+ | `sigma diagnose bap` | Public BAP profile; optional `--pubkey` registered-list check |
37
+ | `sigma diagnose identities` | `GET /api/user/bap-ids` (`--pubkey` or session cookie) |
38
+ | `sigma diagnose last-oauth` | Last selected BAP for `--pubkey` `--client-id` |
39
+ | `sigma diagnose client` | Public OAuth client metadata |
36
40
 
37
41
  Password sources (exactly one): `--password-file`, `--password-stdin`, or `SIGMA_BACKUP_PASSWORD`. `--password` on argv is rejected.
38
42
 
43
+ ## Diagnose (no private keys)
44
+
45
+ Public HTTP lookups against `https://auth.sigmaidentity.com`. Use these when a Sigma login shows the wrong faucet, a profile 404s, or last-selected identity looks stuck.
46
+
47
+ ```bash
48
+ bunx @sigma-auth/cli diagnose bap --bap-id 3QpdyNb9HScYmWEyfqtRQbKzwyf --json
49
+ bunx @sigma-auth/cli diagnose bap --bap-id 33mGVYzkGE9XMbu346XkUaMHyzwV --pubkey 03a42932… --json
50
+ bunx @sigma-auth/cli diagnose identities --pubkey 03a42932… --json
51
+ bunx @sigma-auth/cli diagnose last-oauth --pubkey 03a42932… --client-id droplit --json
52
+ ```
53
+
54
+ `diagnose bap` treats a profile 404 as a successful diagnosis (`found: false`), not a command failure.
55
+
39
56
  Contract: `docs/specs/sigma-cli-v1.md` in [sigma-auth](https://github.com/b-open-io/sigma-auth).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sigma-auth/cli",
3
- "version": "0.0.2",
4
- "description": "Headless Sigma Auth CLI: create a BAP identity locally, sign in with Bitcoin-Auth, push encrypted backups, register OAuth clients",
3
+ "version": "0.0.3",
4
+ "description": "Headless Sigma Auth CLI: create a BAP identity locally, sign in with Bitcoin-Auth, diagnose public identities, push encrypted backups, register OAuth clients",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "sigma": "./src/index.ts"
package/src/commands.ts CHANGED
@@ -593,6 +593,10 @@ Commands:
593
593
  backup push POST ciphertext with session; never decrypt
594
594
  oauth register Register an OAuth client (session path or DCR)
595
595
  doctor Non-interactive health check
596
+ diagnose bap Public BAP profile (+ --pubkey registered-list check)
597
+ diagnose identities GET /api/user/bap-ids [--pubkey]
598
+ diagnose last-oauth Last selected BAP for --pubkey --client-id
599
+ diagnose client Public OAuth client metadata --client-id
596
600
 
597
601
  Global flags:
598
602
  --base-url <url> SIGMA_AUTH_URL (default https://auth.sigmaidentity.com)
@@ -0,0 +1,191 @@
1
+ import type { ParsedArgs } from "./args.ts";
2
+ import { flag } from "./args.ts";
3
+ import type { RuntimeConfig } from "./config.ts";
4
+ import { usage } from "./error.ts";
5
+ import { createHttp, requestJson, throwHttp } from "./http.ts";
6
+ import { printHuman, printJson, type OutputMode } from "./output.ts";
7
+
8
+ function mode(cfg: RuntimeConfig): OutputMode {
9
+ return { json: cfg.json, quiet: cfg.quiet };
10
+ }
11
+
12
+ function succeed(cfg: RuntimeConfig, data: Record<string, unknown>, human: string): number {
13
+ if (cfg.json) {
14
+ printJson(true, data);
15
+ } else {
16
+ printHuman(mode(cfg), human);
17
+ }
18
+ return 0;
19
+ }
20
+
21
+ function record(json: unknown): Record<string, unknown> {
22
+ return json && typeof json === "object" ? (json as Record<string, unknown>) : {};
23
+ }
24
+
25
+ export async function diagnoseBap(args: ParsedArgs, cfg: RuntimeConfig): Promise<number> {
26
+ const bapId = flag(args, "bap-id");
27
+ if (!bapId) {
28
+ usage("--bap-id is required");
29
+ }
30
+ const pubkey = flag(args, "pubkey");
31
+ const client = createHttp(cfg);
32
+ const profileRes = await requestJson(
33
+ client,
34
+ "GET",
35
+ `/api/bap/profile?bapId=${encodeURIComponent(bapId)}`
36
+ );
37
+ if (profileRes.status >= 500) {
38
+ throwHttp(
39
+ "/api/bap/profile",
40
+ profileRes.status,
41
+ profileRes.json,
42
+ profileRes.text,
43
+ profileRes.headers
44
+ );
45
+ }
46
+ const found = profileRes.status === 200 && record(profileRes.json).status === "OK";
47
+ const profile = found ? record(profileRes.json).result ?? record(profileRes.json) : null;
48
+
49
+ let registeredToPubkey: boolean | null = null;
50
+ let identities: unknown[] | undefined;
51
+ if (pubkey) {
52
+ const listRes = await requestJson(
53
+ client,
54
+ "GET",
55
+ `/api/user/bap-ids?pubkey=${encodeURIComponent(pubkey)}`
56
+ );
57
+ if (listRes.status >= 400) {
58
+ throwHttp(
59
+ "/api/user/bap-ids",
60
+ listRes.status,
61
+ listRes.json,
62
+ listRes.text,
63
+ listRes.headers
64
+ );
65
+ }
66
+ const bapIds = record(listRes.json).bapIds;
67
+ identities = Array.isArray(bapIds) ? bapIds : [];
68
+ registeredToPubkey = identities.some((row) => {
69
+ const item = record(row);
70
+ return item.identity_key === bapId || item.id === bapId || item.bapId === bapId;
71
+ });
72
+ }
73
+
74
+ let hint: string;
75
+ if (registeredToPubkey === true && found) {
76
+ hint = "registered to this pubkey and has a published profile";
77
+ } else if (registeredToPubkey === true && !found) {
78
+ hint = "registered to this pubkey but no published BAP profile";
79
+ } else if (registeredToPubkey === false && found) {
80
+ hint = "published profile exists but this BAP is not in this pubkey's registered list";
81
+ } else if (registeredToPubkey === false && !found) {
82
+ hint = "not in this pubkey's registered list and no published BAP profile (typical leftover HD identity)";
83
+ } else if (found) {
84
+ hint = "published BAP profile found";
85
+ } else {
86
+ hint = "no published BAP profile (404). Pass --pubkey to also check GET /api/user/bap-ids";
87
+ }
88
+
89
+ const data: Record<string, unknown> = {
90
+ bapId,
91
+ found,
92
+ profile,
93
+ hint,
94
+ };
95
+ if (pubkey) {
96
+ data.pubkey = pubkey;
97
+ data.registeredToPubkey = registeredToPubkey;
98
+ data.registeredCount = identities?.length ?? 0;
99
+ }
100
+
101
+ const lines = [
102
+ `bapId ${bapId}`,
103
+ `found ${found}`,
104
+ pubkey ? `registeredToPubkey ${registeredToPubkey}` : null,
105
+ hint,
106
+ ].filter((line): line is string => line !== null);
107
+ return succeed(cfg, data, lines.join("\n"));
108
+ }
109
+
110
+ export async function diagnoseIdentities(
111
+ args: ParsedArgs,
112
+ cfg: RuntimeConfig
113
+ ): Promise<number> {
114
+ const pubkey = flag(args, "pubkey");
115
+ const client = createHttp(cfg);
116
+ const path = pubkey
117
+ ? `/api/user/bap-ids?pubkey=${encodeURIComponent(pubkey)}`
118
+ : "/api/user/bap-ids";
119
+ const result = await requestJson(client, "GET", path, {
120
+ withCookies: !pubkey,
121
+ });
122
+ if (result.status >= 400) {
123
+ throwHttp(path, result.status, result.json, result.text, result.headers);
124
+ }
125
+ const bapIds = record(result.json).bapIds;
126
+ const list = Array.isArray(bapIds) ? bapIds : [];
127
+ const names = list
128
+ .map((row) => {
129
+ const item = record(row);
130
+ const id = String(item.identity_key ?? item.id ?? "");
131
+ const name = typeof item.name === "string" ? item.name : "";
132
+ const primary = item.is_primary === true ? " (primary)" : "";
133
+ return `${id}${name ? ` ${name}` : ""}${primary}`;
134
+ })
135
+ .join("\n");
136
+ return succeed(
137
+ cfg,
138
+ { pubkey: pubkey ?? null, count: list.length, bapIds: list },
139
+ names || "(none)"
140
+ );
141
+ }
142
+
143
+ export async function diagnoseLastOauth(
144
+ args: ParsedArgs,
145
+ cfg: RuntimeConfig
146
+ ): Promise<number> {
147
+ const pubkey = flag(args, "pubkey");
148
+ const clientId = flag(args, "client-id");
149
+ if (!pubkey || !clientId) {
150
+ usage("--pubkey and --client-id are required");
151
+ }
152
+ const client = createHttp(cfg);
153
+ const path = `/api/user/last-oauth-identity?pubkey=${encodeURIComponent(pubkey)}&clientId=${encodeURIComponent(clientId)}`;
154
+ const result = await requestJson(client, "GET", path);
155
+ if (result.status >= 400) {
156
+ throwHttp(path, result.status, result.json, result.text, result.headers);
157
+ }
158
+ const lastSelectedBapId = record(result.json).lastSelectedBapId ?? null;
159
+ return succeed(
160
+ cfg,
161
+ { pubkey, clientId, lastSelectedBapId },
162
+ typeof lastSelectedBapId === "string" ? lastSelectedBapId : "(none)"
163
+ );
164
+ }
165
+
166
+ export async function diagnoseClient(
167
+ args: ParsedArgs,
168
+ cfg: RuntimeConfig
169
+ ): Promise<number> {
170
+ const clientId = flag(args, "client-id");
171
+ if (!clientId) {
172
+ usage("--client-id is required");
173
+ }
174
+ const client = createHttp(cfg);
175
+ const path = `/api/oauth-clients?clientId=${encodeURIComponent(clientId)}`;
176
+ const result = await requestJson(client, "GET", path);
177
+ if (result.status >= 400) {
178
+ throwHttp(path, result.status, result.json, result.text, result.headers);
179
+ }
180
+ const payload = record(result.json);
181
+ const clients = Array.isArray(payload.clients) ? payload.clients : [];
182
+ const first = clients[0] ? record(clients[0]) : payload;
183
+ if (!first.clientId && !first.name) {
184
+ return succeed(cfg, { clientId, client: null }, "(not found)");
185
+ }
186
+ return succeed(
187
+ cfg,
188
+ { clientId, client: first },
189
+ `${String(first.clientId ?? clientId)} ${String(first.name ?? "")}`
190
+ );
191
+ }
package/src/index.ts CHANGED
@@ -11,6 +11,12 @@ import {
11
11
  identityInfo,
12
12
  oauthRegister,
13
13
  } from "./commands.ts";
14
+ import {
15
+ diagnoseBap,
16
+ diagnoseClient,
17
+ diagnoseIdentities,
18
+ diagnoseLastOauth,
19
+ } from "./diagnose.ts";
14
20
  import { loadConfig } from "./config.ts";
15
21
  import { usage } from "./error.ts";
16
22
  import { reportError } from "./output.ts";
@@ -47,6 +53,18 @@ export async function run(argv: string[]): Promise<number> {
47
53
  if (group === "doctor") {
48
54
  return await doctor(args, cfg);
49
55
  }
56
+ if (group === "diagnose" && command === "bap") {
57
+ return await diagnoseBap(args, cfg);
58
+ }
59
+ if (group === "diagnose" && command === "identities") {
60
+ return await diagnoseIdentities(args, cfg);
61
+ }
62
+ if (group === "diagnose" && command === "last-oauth") {
63
+ return await diagnoseLastOauth(args, cfg);
64
+ }
65
+ if (group === "diagnose" && command === "client") {
66
+ return await diagnoseClient(args, cfg);
67
+ }
50
68
  if (!group) {
51
69
  process.stdout.write(HELP);
52
70
  return 0;