@certen.io/cli 0.5.0 → 0.7.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.
Files changed (86) hide show
  1. package/CHANGELOG.md +407 -0
  2. package/README.md +232 -160
  3. package/dist/chains.d.ts +67 -0
  4. package/dist/chains.js +242 -0
  5. package/dist/chains.js.map +1 -0
  6. package/dist/commands/admin.js +49 -4
  7. package/dist/commands/admin.js.map +1 -1
  8. package/dist/commands/auth.js +145 -12
  9. package/dist/commands/auth.js.map +1 -1
  10. package/dist/commands/billing.js +648 -40
  11. package/dist/commands/billing.js.map +1 -1
  12. package/dist/commands/call.d.ts +2 -0
  13. package/dist/commands/call.js +168 -0
  14. package/dist/commands/call.js.map +1 -0
  15. package/dist/commands/chains.d.ts +2 -0
  16. package/dist/commands/chains.js +123 -0
  17. package/dist/commands/chains.js.map +1 -0
  18. package/dist/commands/doctor.d.ts +2 -0
  19. package/dist/commands/doctor.js +171 -0
  20. package/dist/commands/doctor.js.map +1 -0
  21. package/dist/commands/errors-catalogue.d.ts +2 -0
  22. package/dist/commands/errors-catalogue.js +83 -0
  23. package/dist/commands/errors-catalogue.js.map +1 -0
  24. package/dist/commands/identity.js +269 -19
  25. package/dist/commands/identity.js.map +1 -1
  26. package/dist/commands/init.d.ts +2 -0
  27. package/dist/commands/init.js +307 -0
  28. package/dist/commands/init.js.map +1 -0
  29. package/dist/commands/keys.js +2 -0
  30. package/dist/commands/keys.js.map +1 -1
  31. package/dist/commands/oauth-clients.d.ts +2 -0
  32. package/dist/commands/oauth-clients.js +131 -0
  33. package/dist/commands/oauth-clients.js.map +1 -0
  34. package/dist/commands/orgs.d.ts +2 -0
  35. package/dist/commands/orgs.js +117 -0
  36. package/dist/commands/orgs.js.map +1 -0
  37. package/dist/commands/pending.js +13 -1
  38. package/dist/commands/pending.js.map +1 -1
  39. package/dist/commands/portfolio.js +34 -1
  40. package/dist/commands/portfolio.js.map +1 -1
  41. package/dist/commands/proof.d.ts +2 -0
  42. package/dist/commands/proof.js +400 -0
  43. package/dist/commands/proof.js.map +1 -0
  44. package/dist/commands/signup.d.ts +14 -0
  45. package/dist/commands/signup.js +271 -0
  46. package/dist/commands/signup.js.map +1 -0
  47. package/dist/commands/transaction.js +139 -21
  48. package/dist/commands/transaction.js.map +1 -1
  49. package/dist/commands/webhooks.d.ts +2 -0
  50. package/dist/commands/webhooks.js +200 -0
  51. package/dist/commands/webhooks.js.map +1 -0
  52. package/dist/commands/whoami.d.ts +16 -0
  53. package/dist/commands/whoami.js +98 -0
  54. package/dist/commands/whoami.js.map +1 -0
  55. package/dist/config.d.ts +56 -0
  56. package/dist/config.js +51 -0
  57. package/dist/config.js.map +1 -1
  58. package/dist/errors.d.ts +14 -1
  59. package/dist/errors.js +15 -1
  60. package/dist/errors.js.map +1 -1
  61. package/dist/funding-guard.d.ts +40 -0
  62. package/dist/funding-guard.js +120 -0
  63. package/dist/funding-guard.js.map +1 -0
  64. package/dist/help-root.d.ts +2 -0
  65. package/dist/help-root.js +94 -0
  66. package/dist/help-root.js.map +1 -0
  67. package/dist/index.js +27 -0
  68. package/dist/index.js.map +1 -1
  69. package/dist/output.d.ts +23 -0
  70. package/dist/output.js +46 -0
  71. package/dist/output.js.map +1 -1
  72. package/dist/passphrase.d.ts +18 -0
  73. package/dist/passphrase.js +35 -3
  74. package/dist/passphrase.js.map +1 -1
  75. package/dist/payment-uri.d.ts +76 -0
  76. package/dist/payment-uri.js +135 -0
  77. package/dist/payment-uri.js.map +1 -0
  78. package/dist/signer.js +6 -3
  79. package/dist/signer.js.map +1 -1
  80. package/dist/solidity-args.d.ts +31 -0
  81. package/dist/solidity-args.js +111 -0
  82. package/dist/solidity-args.js.map +1 -0
  83. package/dist/wait.d.ts +79 -0
  84. package/dist/wait.js +168 -0
  85. package/dist/wait.js.map +1 -0
  86. package/package.json +5 -4
@@ -0,0 +1,200 @@
1
+ import { CertenClient } from '@certen.io/sdk';
2
+ import { getApiKey, getApiUrl, getOutputFormat } from '../config.js';
3
+ import { printOutput, human, hint, isJsonMode } from '../output.js';
4
+ import { CliError, EXIT } from '../errors.js';
5
+ /**
6
+ * Webhooks — where events go, and what happened to each one.
7
+ *
8
+ * CERTEN has pushed events since early on, with a delivery queue, a retry policy and a published
9
+ * signature-verification guide. None of it was reachable from here, so an endpoint could not be
10
+ * registered and a failed delivery could not be seen. That is the worst shape for a push mechanism:
11
+ * it fails quietly, and from the outside a dropped delivery is indistinguishable from an event that
12
+ * never happened.
13
+ *
14
+ * `deliveries` is the command that earns this group. Everything else configures; that one answers
15
+ * the question someone actually arrives with.
16
+ */
17
+ async function getClient() {
18
+ return new CertenClient({ apiKey: await getApiKey(), baseUrl: getApiUrl() });
19
+ }
20
+ const machineOutput = () => isJsonMode() || getOutputFormat() === 'json';
21
+ export function registerWebhookCommands(program) {
22
+ const webhooks = program
23
+ .command('webhooks')
24
+ .description('Where events are delivered, and what happened to each one');
25
+ webhooks
26
+ .command('list', { isDefault: true })
27
+ .description('Endpoints registered for this organization')
28
+ .action(async () => {
29
+ const { endpoints } = await (await getClient()).webhooks.list();
30
+ if (machineOutput()) {
31
+ printOutput({ endpoints });
32
+ return;
33
+ }
34
+ human('');
35
+ if (endpoints.length === 0) {
36
+ human(' No endpoints registered — CERTEN is not pushing events anywhere.');
37
+ hint('certen webhooks add https://example.com/hooks');
38
+ return;
39
+ }
40
+ for (const e of endpoints) {
41
+ // `verified` and `is_active` are the two states that decide whether anything arrives, so
42
+ // they belong on the same line as the URL rather than behind a second command.
43
+ const state = [
44
+ e.is_active ? 'active' : 'INACTIVE',
45
+ e.verified ? 'verified' : 'UNVERIFIED',
46
+ ].join(', ');
47
+ human(` ${e.url}`);
48
+ human(` ${e.id} (${state})`);
49
+ if (e.event_types?.length)
50
+ human(` events: ${e.event_types.join(', ')}`);
51
+ else
52
+ human(' events: all');
53
+ if (e.verification_error)
54
+ human(` last verification error: ${e.verification_error}`);
55
+ }
56
+ human('');
57
+ });
58
+ webhooks
59
+ .command('add <url>')
60
+ .description('Register an endpoint to receive events')
61
+ .option('--events <list>', 'Comma-separated event types. Omit to receive everything.')
62
+ .option('--secret <secret>', 'Your own signing secret, so it is never in a response body')
63
+ .option('--description <text>', 'A note for your own reference')
64
+ .option('--skip-verification', 'Register without sending the verification ping')
65
+ .action(async (url, opts) => {
66
+ // Checked here so a typo does not become a registered endpoint that silently never fires.
67
+ if (!/^https?:\/\//i.test(url)) {
68
+ throw new CliError(`"${url}" is not a URL. Expected something starting http:// or https://`, 'INVALID_URL', EXIT.USAGE);
69
+ }
70
+ const created = await (await getClient()).webhooks.register({
71
+ url,
72
+ secret: opts.secret,
73
+ eventTypes: opts.events?.split(',').map((e) => e.trim()).filter(Boolean),
74
+ description: opts.description,
75
+ skipVerification: opts.skipVerification,
76
+ });
77
+ printOutput({ ...created });
78
+ if (machineOutput())
79
+ return;
80
+ human('');
81
+ human(` Registered ${created.url}`);
82
+ human(` ${created.id}`);
83
+ if (created.secret) {
84
+ human('');
85
+ human(` Signing secret: ${created.secret}`);
86
+ // Said plainly because there is no second chance: rotating is the only recovery, and it
87
+ // invalidates whatever the previous secret was already signing.
88
+ human(' SAVE THIS NOW — it is shown once and cannot be retrieved. Verify every delivery');
89
+ human(' against it, or you cannot tell a real event from anything else that finds the URL.');
90
+ }
91
+ if (created.verified === false) {
92
+ human('');
93
+ human(` Not verified${created.verification_error ? `: ${created.verification_error}` : '.'}`);
94
+ hint(`certen webhooks verify ${created.id}`);
95
+ }
96
+ });
97
+ webhooks
98
+ .command('remove <id>')
99
+ .description('Stop delivering to an endpoint')
100
+ .action(async (id) => {
101
+ const result = await (await getClient()).webhooks.remove(id);
102
+ printOutput({ ...result });
103
+ if (!machineOutput()) {
104
+ human('');
105
+ human(` ${id} will no longer receive events.`);
106
+ }
107
+ });
108
+ webhooks
109
+ .command('verify <id>')
110
+ .description('Re-run the verification ping against an endpoint')
111
+ .action(async (id) => {
112
+ const result = await (await getClient()).webhooks.verify(id);
113
+ printOutput({ ...result });
114
+ if (machineOutput())
115
+ return;
116
+ human('');
117
+ human(result.verified
118
+ ? ` ${result.url} responded correctly.`
119
+ : ` ${result.url} did not verify${result.verification_error ? `: ${result.verification_error}` : '.'}`);
120
+ });
121
+ webhooks
122
+ .command('rotate-secret <id>')
123
+ .description('Issue a new signing secret (invalidates the current one)')
124
+ .action(async (id) => {
125
+ const result = await (await getClient()).webhooks.rotateSecret(id);
126
+ printOutput({ ...result });
127
+ if (machineOutput())
128
+ return;
129
+ human('');
130
+ human(` New signing secret: ${result.secret}`);
131
+ human(' SAVE THIS NOW. The previous secret stopped working the moment this was issued, so');
132
+ human(' deliveries will fail verification until your receiver is using the new one.');
133
+ });
134
+ webhooks
135
+ .command('deliveries')
136
+ .description('Delivery attempts, newest first — including why one failed')
137
+ .option('--limit <n>', 'How many to fetch (page size with --all)', '50')
138
+ .option('--offset <n>', 'Skip this many')
139
+ .option('--all', 'Fetch every page')
140
+ .option('--failed', 'Only deliveries that did not succeed')
141
+ .action(async (opts) => {
142
+ const limit = Number(opts.limit);
143
+ if (!Number.isInteger(limit) || limit < 1) {
144
+ throw new CliError(`"${opts.limit}" is not a whole number for --limit.`, 'INVALID_NUMBER', EXIT.USAGE);
145
+ }
146
+ const offset = opts.offset === undefined ? undefined : Number(opts.offset);
147
+ if (opts.all && offset !== undefined) {
148
+ throw new CliError('--all starts from the beginning, so --offset has no meaning with it. Use one or the other.', 'CONFLICTING_PAGING_FLAGS', EXIT.USAGE);
149
+ }
150
+ const client = await getClient();
151
+ const all = [];
152
+ if (opts.all) {
153
+ for await (const d of client.webhooks.deliveriesAll(limit))
154
+ all.push(d);
155
+ }
156
+ else {
157
+ all.push(...(await client.webhooks.deliveries({ limit, offset })).deliveries);
158
+ }
159
+ // Filtered here rather than server-side because the endpoint takes no status filter; doing
160
+ // it locally is honest about that and still answers the question people arrive with.
161
+ const deliveries = opts.failed
162
+ ? all.filter((d) => d.status !== 'delivered' && d.status !== 'succeeded')
163
+ : all;
164
+ if (machineOutput()) {
165
+ printOutput({ deliveries });
166
+ return;
167
+ }
168
+ human('');
169
+ if (deliveries.length === 0) {
170
+ human(opts.failed ? ' No failed deliveries.' : ' No deliveries yet.');
171
+ return;
172
+ }
173
+ for (const d of deliveries) {
174
+ const when = String(d.created_at ?? '').slice(0, 19).replace('T', ' ');
175
+ human(` ${when} ${String(d.event_type ?? '?').padEnd(28)} ${d.status ?? '?'}`);
176
+ if (d.response_status)
177
+ human(` HTTP ${d.response_status}, attempt ${d.attempts ?? 1}`);
178
+ if (d.error)
179
+ human(` ${d.error}`);
180
+ if (d.status && d.status !== 'delivered' && d.status !== 'succeeded') {
181
+ human(` retry it: certen webhooks redeliver ${d.id}`);
182
+ }
183
+ }
184
+ human('');
185
+ });
186
+ webhooks
187
+ .command('redeliver <id>')
188
+ .description('Send a delivery again')
189
+ .action(async (id) => {
190
+ const result = await (await getClient()).webhooks.redeliver(id);
191
+ printOutput({ ...result });
192
+ if (!machineOutput()) {
193
+ human('');
194
+ human(' Queued for delivery again.');
195
+ // Worth saying: this is one of the few genuinely non-idempotent operations on the API.
196
+ human(' Each call delivers again — make sure your receiver can handle a repeat.');
197
+ }
198
+ });
199
+ }
200
+ //# sourceMappingURL=webhooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../../src/commands/webhooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * `certen whoami` — which credential is this, against which gateway, and what standing does it
4
+ * have.
5
+ *
6
+ * Distinct from `certen auth status`, which reads local config and never contacts anything. The
7
+ * two questions are different and conflating them is how someone concludes their setup is fine
8
+ * while every request 401s.
9
+ *
10
+ * **What this deliberately does NOT show: the organization name and the caller's role.** With a
11
+ * machine API key there is no endpoint that returns either. `GET /v1/me` exists but requires a
12
+ * Firebase ID token from the self-service portal, and nothing on the API-key surface echoes an
13
+ * org name. Inventing a plausible-looking value, or silently omitting the fields as though they
14
+ * were merely absent, would both be worse than saying where they live.
15
+ */
16
+ export declare function registerWhoamiCommands(program: Command): void;
@@ -0,0 +1,98 @@
1
+ import { CertenClient, CertenError } from '@certen.io/sdk';
2
+ import { getApiKey, getApiUrl, getPortalUrl, readConfig } from '../config.js';
3
+ import { printOutput, hint, human, isJsonMode, usd } from '../output.js';
4
+ /**
5
+ * `certen whoami` — which credential is this, against which gateway, and what standing does it
6
+ * have.
7
+ *
8
+ * Distinct from `certen auth status`, which reads local config and never contacts anything. The
9
+ * two questions are different and conflating them is how someone concludes their setup is fine
10
+ * while every request 401s.
11
+ *
12
+ * **What this deliberately does NOT show: the organization name and the caller's role.** With a
13
+ * machine API key there is no endpoint that returns either. `GET /v1/me` exists but requires a
14
+ * Firebase ID token from the self-service portal, and nothing on the API-key surface echoes an
15
+ * org name. Inventing a plausible-looking value, or silently omitting the fields as though they
16
+ * were merely absent, would both be worse than saying where they live.
17
+ */
18
+ export function registerWhoamiCommands(program) {
19
+ program
20
+ .command('whoami')
21
+ .description('Which key, which gateway, and what standing it has (contacts the gateway)')
22
+ .action(async () => {
23
+ const config = readConfig();
24
+ const apiUrl = getApiUrl();
25
+ // Resolved rather than read from config, so the env var takes the precedence it actually has.
26
+ const apiKey = await getApiKey();
27
+ const source = process.env.CERTEN_API_KEY
28
+ ? 'CERTEN_API_KEY env'
29
+ : config.storage === 'keyring' ? 'OS keyring' : '~/.certen/config.json';
30
+ const client = new CertenClient({ apiKey, baseUrl: apiUrl });
31
+ // `me` is the credential asking about itself; `balance` is its standing. Each degrades
32
+ // independently, so a key that can read one but not the other still reports what it can.
33
+ //
34
+ // The old third call to `admin.getUsage` is gone. It existed only to INFER whether the key
35
+ // held `admin:read`, by seeing whether the call succeeded — and `me.scopes` now says so
36
+ // outright. Inferring permissions by making calls and reading the refusals is a strange way
37
+ // to answer a question the gateway can answer directly.
38
+ const [me, balance] = await Promise.all([
39
+ client.me().catch(() => null),
40
+ client.billing.balance().catch((err) => (err instanceof CertenError ? err : null)),
41
+ ]);
42
+ const balanceOk = balance !== null && !(balance instanceof CertenError);
43
+ const b = balanceOk ? balance : null;
44
+ // The full structure goes to machines; humans get the rendered summary below. The table
45
+ // renderer flattens `credit` and `usage` to raw JSON on one line, which is strictly worse
46
+ // than the three lines that follow.
47
+ if (isJsonMode()) {
48
+ printOutput({
49
+ api_url: apiUrl,
50
+ portal_url: getPortalUrl(),
51
+ key_prefix: `${apiKey.substring(0, 12)}...`,
52
+ key_source: source,
53
+ organization: me ? { id: me.org.id, name: me.org.name } : null,
54
+ key_id: me?.key?.id ?? null,
55
+ rate_limit_rpm: me?.key?.rate_limit_rpm ?? null,
56
+ account_status: b?.status ?? 'unknown',
57
+ spendable_usd: b?.spendable_usd ?? null,
58
+ available_usd: b?.available_usd ?? null,
59
+ held_usd: b?.held_usd ?? null,
60
+ credit: b?.credit ?? null,
61
+ // Granted, not observed. This used to be a guess assembled from which probe calls
62
+ // happened to succeed, which could only ever describe the scopes it had thought to test.
63
+ scopes: me?.scopes ?? null,
64
+ });
65
+ return;
66
+ }
67
+ human('');
68
+ human(` Key ${apiKey.substring(0, 12)}... (${source})`);
69
+ human(` Gateway ${apiUrl}`);
70
+ if (b) {
71
+ human(` Account ${b.status}`);
72
+ human(` Spendable ${usd(b.spendable_usd)}`);
73
+ if (b.credit && b.credit.kind !== 'none') {
74
+ if (b.credit.expired) {
75
+ human(` Credit ${b.credit.kind} — EXPIRED`);
76
+ }
77
+ else if (b.credit.expires_at) {
78
+ const days = Math.max(0, Math.round((new Date(b.credit.expires_at).getTime() - Date.now()) / 86_400_000));
79
+ human(` Credit ${b.credit.label ?? b.credit.kind} — ends in ${days} day(s)`);
80
+ }
81
+ }
82
+ }
83
+ else if (balance instanceof CertenError && balance.status === 403) {
84
+ human(' Account (this key has no billing:read scope)');
85
+ }
86
+ else {
87
+ human(' Account (could not read)');
88
+ }
89
+ human('');
90
+ human(' Organization name, your role, and team management are not available to an API key.');
91
+ human(' Inviting a teammate needs a signed-in portal session — deliberately, since a leaked');
92
+ human(' machine key must not be able to add people to your organization.');
93
+ human(` ${getPortalUrl()}`);
94
+ hint('');
95
+ hint('Check the whole setup end to end: certen doctor');
96
+ });
97
+ }
98
+ //# sourceMappingURL=whoami.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"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"}
package/dist/config.d.ts CHANGED
@@ -10,7 +10,49 @@ export interface CliConfig {
10
10
  * which key is selected without round-tripping through the keyring.
11
11
  */
12
12
  key_prefix?: string;
13
+ /**
14
+ * Identities this machine created, most recent last.
15
+ *
16
+ * The gateway has no identity collection route, and `/v1/portfolio` — the only cross-identity
17
+ * view — keys on ADI URL and does not return the UUID that every other command takes. So an id
18
+ * that is not written down when it is minted is unrecoverable: `certen identity create` printed
19
+ * it once, and after that the only copy was the user's scrollback.
20
+ *
21
+ * That is what made `init` unable to be idempotent in a useful way. It could see that
22
+ * identities existed and still had nothing to put after `--identity`.
23
+ */
24
+ identities?: Array<{
25
+ id: string;
26
+ adi_url: string;
27
+ chains: string[];
28
+ created_at: string;
29
+ }>;
13
30
  }
31
+ /**
32
+ * Record an identity this machine created.
33
+ *
34
+ * Deduplicated on id, and appended rather than replaced, so creating a second identity does not
35
+ * make the first unfindable.
36
+ */
37
+ export declare function rememberIdentity(entry: {
38
+ id: string;
39
+ adi_url: string;
40
+ chains: string[];
41
+ }): void;
42
+ /** The most recently created identity on this machine, if any. */
43
+ export declare function lastIdentity(): {
44
+ id: string;
45
+ adi_url: string;
46
+ chains: string[];
47
+ } | undefined;
48
+ /**
49
+ * Drop a retired identity from the local record.
50
+ *
51
+ * Without this, `certen init` would keep offering to reuse an identity the gateway no longer
52
+ * tracks. It recovers on its own — the lookup 404s and it creates a fresh one — but silently
53
+ * doing the right thing after an avoidable round trip is worse than not recording a dead id.
54
+ */
55
+ export declare function forgetIdentity(id: string): void;
14
56
  export declare function readConfig(): CliConfig;
15
57
  /**
16
58
  * Write the config file with 0600 perms (owner read/write only). On Windows,
@@ -36,4 +78,18 @@ export declare function clearApiKey(): Promise<void>;
36
78
  */
37
79
  export declare const DEFAULT_API_URL = "https://gateway.kompendium.co";
38
80
  export declare function getApiUrl(): string;
81
+ /**
82
+ * Where a human goes to do the things a machine key cannot — register a payer wallet, invite a
83
+ * teammate, revoke a key.
84
+ *
85
+ * Derived from the API URL rather than hardcoded, so a self-hosted gateway points at its own
86
+ * portal instead of at ours. `CERTEN_PORTAL_URL` overrides for a deployment that serves the two
87
+ * from different hosts.
88
+ *
89
+ * This exists because the funding hint printed the literal string "/portal" as an instruction — a
90
+ * bare path with no host, which is not somewhere anyone can go. The 402 handler had it right all
91
+ * along by reading `portal_url` off the error; there was simply nothing equivalent for the paths
92
+ * that have no error to read it from.
93
+ */
94
+ export declare function getPortalUrl(): string;
39
95
  export declare function getOutputFormat(): 'table' | 'json';
package/dist/config.js CHANGED
@@ -4,6 +4,38 @@ import { homedir } from 'os';
4
4
  import { UsageError } from './errors.js';
5
5
  const CONFIG_DIR = join(homedir(), '.certen');
6
6
  const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
7
+ /**
8
+ * Record an identity this machine created.
9
+ *
10
+ * Deduplicated on id, and appended rather than replaced, so creating a second identity does not
11
+ * make the first unfindable.
12
+ */
13
+ export function rememberIdentity(entry) {
14
+ const cfg = readConfig();
15
+ const identities = (cfg.identities ?? []).filter((i) => i.id !== entry.id);
16
+ identities.push({ ...entry, created_at: new Date().toISOString() });
17
+ cfg.identities = identities;
18
+ writeConfig(cfg);
19
+ }
20
+ /** The most recently created identity on this machine, if any. */
21
+ export function lastIdentity() {
22
+ const all = readConfig().identities ?? [];
23
+ return all.length > 0 ? all[all.length - 1] : undefined;
24
+ }
25
+ /**
26
+ * Drop a retired identity from the local record.
27
+ *
28
+ * Without this, `certen init` would keep offering to reuse an identity the gateway no longer
29
+ * tracks. It recovers on its own — the lookup 404s and it creates a fresh one — but silently
30
+ * doing the right thing after an avoidable round trip is worse than not recording a dead id.
31
+ */
32
+ export function forgetIdentity(id) {
33
+ const cfg = readConfig();
34
+ if (!cfg.identities?.some((i) => i.id === id))
35
+ return;
36
+ cfg.identities = cfg.identities.filter((i) => i.id !== id);
37
+ writeConfig(cfg);
38
+ }
7
39
  export function readConfig() {
8
40
  if (!existsSync(CONFIG_FILE)) {
9
41
  return {};
@@ -147,6 +179,25 @@ export function getApiUrl() {
147
179
  const cfg = readConfig();
148
180
  return cfg.api_url ?? DEFAULT_API_URL;
149
181
  }
182
+ /**
183
+ * Where a human goes to do the things a machine key cannot — register a payer wallet, invite a
184
+ * teammate, revoke a key.
185
+ *
186
+ * Derived from the API URL rather than hardcoded, so a self-hosted gateway points at its own
187
+ * portal instead of at ours. `CERTEN_PORTAL_URL` overrides for a deployment that serves the two
188
+ * from different hosts.
189
+ *
190
+ * This exists because the funding hint printed the literal string "/portal" as an instruction — a
191
+ * bare path with no host, which is not somewhere anyone can go. The 402 handler had it right all
192
+ * along by reading `portal_url` off the error; there was simply nothing equivalent for the paths
193
+ * that have no error to read it from.
194
+ */
195
+ export function getPortalUrl() {
196
+ const envUrl = process.env.CERTEN_PORTAL_URL;
197
+ if (envUrl)
198
+ return envUrl;
199
+ return `${getApiUrl().replace(/\/+$/, '')}/portal`;
200
+ }
150
201
  export function getOutputFormat() {
151
202
  const cfg = readConfig();
152
203
  return cfg.output ?? 'table';
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/errors.d.ts CHANGED
@@ -29,7 +29,20 @@ export declare class CliError extends Error {
29
29
  readonly code: string;
30
30
  readonly exitCode: ExitCode;
31
31
  readonly retryable: boolean;
32
- constructor(message: string, code: string, exitCode?: ExitCode, retryable?: boolean);
32
+ /**
33
+ * Structured payload carried alongside the failure.
34
+ *
35
+ * Some commands fail and still have a result worth handing over — `doctor` is the case this
36
+ * exists for: the diagnosis ran successfully and every check it performed is exactly what an
37
+ * automated caller wants, but "a check failed" must still be a non-zero exit or CI treats a
38
+ * broken setup as a working one. Discarding the checks to signal the failure would make the
39
+ * machine interface strictly less useful than the human one.
40
+ *
41
+ * Additive, and rendered under `error.details`. A consumer that ignores unknown keys is
42
+ * unaffected, which is the same property the 402 payment fields rely on.
43
+ */
44
+ readonly details?: Record<string, unknown>;
45
+ constructor(message: string, code: string, exitCode?: ExitCode, retryable?: boolean, details?: Record<string, unknown>);
33
46
  }
34
47
  /** A wrong invocation: exits 2, never retryable. */
35
48
  export declare class UsageError extends CliError {
package/dist/errors.js CHANGED
@@ -28,12 +28,26 @@ export class CliError extends Error {
28
28
  code;
29
29
  exitCode;
30
30
  retryable;
31
- constructor(message, code, exitCode = EXIT.FAILED, retryable = false) {
31
+ /**
32
+ * Structured payload carried alongside the failure.
33
+ *
34
+ * Some commands fail and still have a result worth handing over — `doctor` is the case this
35
+ * exists for: the diagnosis ran successfully and every check it performed is exactly what an
36
+ * automated caller wants, but "a check failed" must still be a non-zero exit or CI treats a
37
+ * broken setup as a working one. Discarding the checks to signal the failure would make the
38
+ * machine interface strictly less useful than the human one.
39
+ *
40
+ * Additive, and rendered under `error.details`. A consumer that ignores unknown keys is
41
+ * unaffected, which is the same property the 402 payment fields rely on.
42
+ */
43
+ details;
44
+ constructor(message, code, exitCode = EXIT.FAILED, retryable = false, details) {
32
45
  super(message);
33
46
  this.name = 'CliError';
34
47
  this.code = code;
35
48
  this.exitCode = exitCode;
36
49
  this.retryable = retryable;
50
+ this.details = details;
37
51
  }
38
52
  }
39
53
  /** A wrong invocation: exits 2, never retryable. */
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB,eAAe;IACf,EAAE,EAAE,CAAC;IACL,+FAA+F;IAC/F,MAAM,EAAE,CAAC;IACT,sFAAsF;IACtF,KAAK,EAAE,CAAC;IACR,sEAAsE;IACtE,WAAW,EAAE,CAAC;CACN,CAAC;AAIX;;;;;GAKG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IACxB,IAAI,CAAS;IACb,QAAQ,CAAW;IACnB,SAAS,CAAU;IAE5B,YAAY,OAAe,EAAE,IAAY,EAAE,WAAqB,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,KAAK;QAC5F,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF;AAED,oDAAoD;AACpD,MAAM,OAAO,UAAW,SAAQ,QAAQ;IACtC,YAAY,OAAe,EAAE,IAAI,GAAG,aAAa;QAC/C,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB,eAAe;IACf,EAAE,EAAE,CAAC;IACL,+FAA+F;IAC/F,MAAM,EAAE,CAAC;IACT,sFAAsF;IACtF,KAAK,EAAE,CAAC;IACR,sEAAsE;IACtE,WAAW,EAAE,CAAC;CACN,CAAC;AAIX;;;;;GAKG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IACxB,IAAI,CAAS;IACb,QAAQ,CAAW;IACnB,SAAS,CAAU;IAC5B;;;;;;;;;;;OAWG;IACM,OAAO,CAA2B;IAE3C,YACE,OAAe,EACf,IAAY,EACZ,WAAqB,IAAI,CAAC,MAAM,EAChC,SAAS,GAAG,KAAK,EACjB,OAAiC;QAEjC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED,oDAAoD;AACpD,MAAM,OAAO,UAAW,SAAQ,QAAQ;IACtC,YAAY,OAAe,EAAE,IAAI,GAAG,aAAa;QAC/C,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF"}
@@ -0,0 +1,40 @@
1
+ import type { CertenClient } from '@certen.io/sdk';
2
+ export declare function faucetFor(chain: string): string | undefined;
3
+ /** Does this intent move value? A zero or absent amount needs no funded account. */
4
+ export declare function movesValue(intent: Record<string, unknown>): boolean;
5
+ /**
6
+ * Read one identity's funding position on one chain.
7
+ *
8
+ * Returns null whenever the answer is not knowable, which the caller must treat as "do not
9
+ * block" rather than as zero.
10
+ */
11
+ /**
12
+ * Per-chain balances as `GET /v1/identity/{id}` already returns them.
13
+ *
14
+ * Accepted so a caller that has just fetched the identity does not pay for a second read of the
15
+ * same numbers — see the note on `assertFundedForValue`.
16
+ */
17
+ export type KnownBalances = Array<{
18
+ chain_id: string;
19
+ address: string;
20
+ token?: string;
21
+ balance: string;
22
+ }>;
23
+ /**
24
+ * Refuse a value-moving intent from an abstract account that is positively known to be empty.
25
+ *
26
+ * `force` exists because someone deliberately exercising the anchoring path should be able to,
27
+ * and because a guard with no override becomes something people work around rather than with.
28
+ */
29
+ export declare function assertFundedForValue(client: CertenClient, identityId: string, chain: string | undefined, intent: Record<string, unknown>, force: boolean,
30
+ /**
31
+ * Balances the caller has ALREADY fetched, from `identity.get()`.
32
+ *
33
+ * `GET /v1/identity/{id}` returns a `balances` array carrying the same chain, address and native
34
+ * balance this guard reads out of `/v1/portfolio`. `certen call` fetches the identity first (it
35
+ * needs `can_sign` before prompting for a passphrase) and then paid for a second read of numbers
36
+ * it was already holding — a round trip on the critical path of the main flow, for nothing.
37
+ *
38
+ * Omit it and the portfolio is fetched as before; `certen tx create` has no identity in hand.
39
+ */
40
+ known?: KnownBalances): Promise<void>;