nostr-bray 1.41.0 → 1.42.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/dist/admin/handlers.d.ts +14 -1
- package/dist/admin/handlers.js +47 -3
- package/dist/bunker-context.d.ts +29 -2
- package/dist/bunker-context.js +68 -19
- package/dist/cli/commands/admin.d.ts +4 -0
- package/dist/cli/commands/admin.js +24 -13
- package/dist/cli/commands/event.js +11 -1
- package/dist/cli/commands/relay.js +18 -9
- package/dist/cli/commands/util.js +14 -1
- package/dist/cli/dispatch.d.ts +2 -0
- package/dist/cli/dispatch.js +21 -4
- package/dist/cli/index.js +68 -14
- package/dist/config.d.ts +12 -0
- package/dist/config.js +23 -0
- package/dist/dispatch/tools.js +2 -2
- package/dist/event/handlers.d.ts +7 -0
- package/dist/event/handlers.js +20 -2
- package/dist/event/index.d.ts +1 -0
- package/dist/event/index.js +1 -0
- package/dist/event/pow.d.ts +50 -0
- package/dist/event/pow.js +85 -0
- package/dist/event-validation/index.d.ts +1 -0
- package/dist/event-validation/index.js +1 -0
- package/dist/event-validation/kind-lookup.d.ts +48 -0
- package/dist/event-validation/kind-lookup.js +86 -0
- package/dist/format.d.ts +18 -0
- package/dist/format.js +40 -0
- package/dist/index.js +53 -18
- package/dist/relay/handlers.d.ts +28 -0
- package/dist/relay/handlers.js +63 -2
- package/dist/relay/tools.js +14 -1
- package/dist/relay-pool.d.ts +55 -11
- package/dist/relay-pool.js +93 -8
- package/dist/serve.d.ts +2 -0
- package/dist/serve.js +20 -3
- package/dist/social/handlers.d.ts +10 -2
- package/dist/social/handlers.js +18 -3
- package/dist/social/tools.js +10 -2
- package/dist/types.d.ts +2 -0
- package/dist/util/tools.js +22 -0
- package/dist/widgets/dm-thread.html +11 -11
- package/dist/widgets/identity-picker.html +11 -11
- package/dist/widgets/social-feed.html +11 -11
- package/package.json +1 -1
package/dist/admin/handlers.d.ts
CHANGED
|
@@ -7,7 +7,20 @@
|
|
|
7
7
|
* Reference: https://github.com/nostr-protocol/nips/blob/master/86.md
|
|
8
8
|
*/
|
|
9
9
|
import type { SigningContext } from '../signing-context.js';
|
|
10
|
-
|
|
10
|
+
/** Method names defined by NIP-86, plus widely-implemented extensions. */
|
|
11
|
+
export type AdminMethod = 'supportedmethods' | 'allowpubkey' | 'unallowpubkey' | 'banpubkey' | 'unbanpubkey' | 'listallowedpubkeys' | 'listbannedpubkeys' | 'allowkind' | 'disallowkind' | 'listallowedkinds' | 'allowevent' | 'banevent' | 'listbannedevents' | 'listeventsneedingmoderation' | 'changerelayname' | 'changerelaydescription' | 'changerelayicon' | 'createrole' | 'editrole' | 'deleterole' | 'assignrole' | 'unassignrole' | 'grantadmin' | 'revokeadmin' | 'blockip' | 'unblockip' | 'listblockedips' | 'listdisallowedkinds';
|
|
12
|
+
/**
|
|
13
|
+
* Legacy bray subcommand names retained as aliases.
|
|
14
|
+
*
|
|
15
|
+
* `bankind` and `listbannedkinds` were never NIP-86 method names, so requests
|
|
16
|
+
* using them were rejected by spec-compliant relays. They are still accepted as
|
|
17
|
+
* input and rewritten to the correct wire method.
|
|
18
|
+
*/
|
|
19
|
+
export declare const ADMIN_METHOD_ALIASES: Record<string, AdminMethod>;
|
|
20
|
+
/** Resolve a user-supplied method name to its NIP-86 wire name. */
|
|
21
|
+
export declare function resolveAdminMethod(method: string): AdminMethod;
|
|
22
|
+
/** Coerce the parameters a given method expects as numbers, leaving the rest alone. */
|
|
23
|
+
export declare function coerceAdminParams(method: AdminMethod, params: Array<string | number>): Array<string | number>;
|
|
11
24
|
export interface AdminCallOptions {
|
|
12
25
|
/** Relay HTTP URL (e.g. https://relay.example.com) */
|
|
13
26
|
relay: string;
|
package/dist/admin/handlers.js
CHANGED
|
@@ -6,6 +6,48 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Reference: https://github.com/nostr-protocol/nips/blob/master/86.md
|
|
8
8
|
*/
|
|
9
|
+
/**
|
|
10
|
+
* Legacy bray subcommand names retained as aliases.
|
|
11
|
+
*
|
|
12
|
+
* `bankind` and `listbannedkinds` were never NIP-86 method names, so requests
|
|
13
|
+
* using them were rejected by spec-compliant relays. They are still accepted as
|
|
14
|
+
* input and rewritten to the correct wire method.
|
|
15
|
+
*/
|
|
16
|
+
export const ADMIN_METHOD_ALIASES = {
|
|
17
|
+
bankind: 'disallowkind',
|
|
18
|
+
listbannedkinds: 'listdisallowedkinds',
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Positions (zero-indexed) whose parameter is a number rather than a string.
|
|
22
|
+
*
|
|
23
|
+
* Everything else is passed through verbatim. Blanket numeric coercion is
|
|
24
|
+
* unsafe here because an all-digit 64-character pubkey would be mangled by
|
|
25
|
+
* `Number()`.
|
|
26
|
+
*/
|
|
27
|
+
const NUMERIC_PARAM_POSITIONS = {
|
|
28
|
+
allowkind: [0],
|
|
29
|
+
disallowkind: [0],
|
|
30
|
+
createrole: [4],
|
|
31
|
+
editrole: [4],
|
|
32
|
+
};
|
|
33
|
+
/** Resolve a user-supplied method name to its NIP-86 wire name. */
|
|
34
|
+
export function resolveAdminMethod(method) {
|
|
35
|
+
return ADMIN_METHOD_ALIASES[method] ?? method;
|
|
36
|
+
}
|
|
37
|
+
/** Coerce the parameters a given method expects as numbers, leaving the rest alone. */
|
|
38
|
+
export function coerceAdminParams(method, params) {
|
|
39
|
+
const numeric = NUMERIC_PARAM_POSITIONS[method];
|
|
40
|
+
if (!numeric)
|
|
41
|
+
return params;
|
|
42
|
+
return params.map((p, i) => {
|
|
43
|
+
if (!numeric.includes(i) || typeof p === 'number')
|
|
44
|
+
return p;
|
|
45
|
+
const n = Number(p);
|
|
46
|
+
if (!Number.isFinite(n))
|
|
47
|
+
throw new Error(`${method}: parameter ${i + 1} must be a number, got "${p}"`);
|
|
48
|
+
return n;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
9
51
|
// ─── Internal helpers ─────────────────────────────────────────────────────────
|
|
10
52
|
/**
|
|
11
53
|
* Build a NIP-98 HTTP Auth header value for the given URL and HTTP method.
|
|
@@ -40,9 +82,11 @@ async function buildNip98Header(ctx, url, httpMethod, payloadHash) {
|
|
|
40
82
|
export async function handleAdminCall(ctx, opts) {
|
|
41
83
|
// NIP-86 always POSTs to the relay root URL
|
|
42
84
|
const url = opts.relay.replace(/\/$/, '');
|
|
85
|
+
// Rewrite legacy aliases so SDK callers get the spec method name on the wire too
|
|
86
|
+
const method = resolveAdminMethod(opts.method);
|
|
43
87
|
const body = JSON.stringify({
|
|
44
|
-
method
|
|
45
|
-
params: opts.params ?? [],
|
|
88
|
+
method,
|
|
89
|
+
params: coerceAdminParams(method, opts.params ?? []),
|
|
46
90
|
});
|
|
47
91
|
// Hash the request body for the NIP-98 payload tag (SHA-256 hex)
|
|
48
92
|
const { createHash } = await import('node:crypto');
|
|
@@ -65,7 +109,7 @@ export async function handleAdminCall(ctx, opts) {
|
|
|
65
109
|
throw new Error(`Relay error: ${json.error}`);
|
|
66
110
|
return {
|
|
67
111
|
relay: opts.relay,
|
|
68
|
-
method
|
|
112
|
+
method,
|
|
69
113
|
result: json.result,
|
|
70
114
|
};
|
|
71
115
|
}
|
package/dist/bunker-context.d.ts
CHANGED
|
@@ -16,6 +16,13 @@ import { BunkerSigner } from 'nostr-tools/nip46';
|
|
|
16
16
|
import { SimplePool } from 'nostr-tools/pool';
|
|
17
17
|
import type { PublicIdentity, SignFn } from './types.js';
|
|
18
18
|
import type { SigningContext } from './signing-context.js';
|
|
19
|
+
/**
|
|
20
|
+
* Reject if `promise` does not settle within `ms`. Converts a silent NIP-46
|
|
21
|
+
* hang into a catchable error so callers can fail fast or retry with backoff
|
|
22
|
+
* instead of wedging -- which, at startup under an MCP health check, becomes a
|
|
23
|
+
* respawn loop that floods the signer.
|
|
24
|
+
*/
|
|
25
|
+
export declare function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T>;
|
|
19
26
|
export interface BunkerConfig {
|
|
20
27
|
pubkey: string;
|
|
21
28
|
/**
|
|
@@ -73,9 +80,29 @@ export declare class BunkerContext implements SigningContext {
|
|
|
73
80
|
protected pool: SimplePool;
|
|
74
81
|
protected pubkeyHex: string | undefined;
|
|
75
82
|
private clientSk;
|
|
76
|
-
|
|
83
|
+
private config;
|
|
84
|
+
protected constructor(signer: BunkerSigner, pool: SimplePool, clientSk: Uint8Array, config: ReturnType<typeof parseBunkerUri>);
|
|
85
|
+
/**
|
|
86
|
+
* Construct a BunkerContext WITHOUT performing the network handshake.
|
|
87
|
+
* Lets a caller bring its process up immediately and connect in the
|
|
88
|
+
* background (see {@link establish}), so a slow or unreachable signer
|
|
89
|
+
* cannot wedge startup. Under an MCP stdio health check that respawns
|
|
90
|
+
* "failed to connect" servers, a wedged startup turns a flaky signer into
|
|
91
|
+
* a respawn loop that floods the bunker with `connect` requests.
|
|
92
|
+
*/
|
|
93
|
+
static create(uri: string, stateDir?: string): BunkerContext;
|
|
94
|
+
/**
|
|
95
|
+
* Perform the NIP-46 `connect` handshake, bounded by `timeoutMs`.
|
|
96
|
+
*
|
|
97
|
+
* nostr-tools' BunkerSigner has no per-request timeout, so a signer that is
|
|
98
|
+
* offline or whose relays are unreachable makes `signer.connect()` hang
|
|
99
|
+
* forever. We race it against a timer and reject on expiry so the caller can
|
|
100
|
+
* retry with backoff. Ping is redundant (a successful connect proves the
|
|
101
|
+
* signer is alive) and getPublicKey is deferred to first access.
|
|
102
|
+
*/
|
|
103
|
+
establish(timeoutMs?: number): Promise<void>;
|
|
77
104
|
/** Connect to a remote bunker. Blocks until the connection is established. */
|
|
78
|
-
static connect(uri: string,
|
|
105
|
+
static connect(uri: string, timeoutMs?: number, stateDir?: string): Promise<BunkerContext>;
|
|
79
106
|
/**
|
|
80
107
|
* Resolve the signing identity pubkey from the remote bunker.
|
|
81
108
|
* Called lazily on first access so startup stays fast (no extra
|
package/dist/bunker-context.js
CHANGED
|
@@ -19,6 +19,25 @@ import { npubEncode } from 'nostr-tools/nip19';
|
|
|
19
19
|
import WebSocket from 'ws';
|
|
20
20
|
import { readStateFile, writeStateFile } from './state.js';
|
|
21
21
|
useWebSocketImplementation(WebSocket);
|
|
22
|
+
/** Bound the NIP-46 connect handshake. nostr-tools' BunkerSigner has no
|
|
23
|
+
* per-request timeout, so an offline signer or unreachable relays make the
|
|
24
|
+
* connect hang forever. */
|
|
25
|
+
const CONNECT_TIMEOUT_MS = 15_000;
|
|
26
|
+
/** Bound individual signer round-trips (sign_event, get_public_key, nip44). */
|
|
27
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
28
|
+
/**
|
|
29
|
+
* Reject if `promise` does not settle within `ms`. Converts a silent NIP-46
|
|
30
|
+
* hang into a catchable error so callers can fail fast or retry with backoff
|
|
31
|
+
* instead of wedging -- which, at startup under an MCP health check, becomes a
|
|
32
|
+
* respawn loop that floods the signer.
|
|
33
|
+
*/
|
|
34
|
+
export function withTimeout(promise, ms, label) {
|
|
35
|
+
let timer;
|
|
36
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
37
|
+
timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
38
|
+
});
|
|
39
|
+
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
40
|
+
}
|
|
22
41
|
/** Parse a bunker:// URI */
|
|
23
42
|
export function parseBunkerUri(uri) {
|
|
24
43
|
// bunker://<pubkey>?relay=<url>&relay=<url>...&secret=<hex>
|
|
@@ -74,13 +93,22 @@ export class BunkerContext {
|
|
|
74
93
|
pool;
|
|
75
94
|
pubkeyHex;
|
|
76
95
|
clientSk;
|
|
77
|
-
|
|
96
|
+
config;
|
|
97
|
+
constructor(signer, pool, clientSk, config) {
|
|
78
98
|
this.signer = signer;
|
|
79
99
|
this.pool = pool;
|
|
80
100
|
this.clientSk = clientSk;
|
|
101
|
+
this.config = config;
|
|
81
102
|
}
|
|
82
|
-
/**
|
|
83
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Construct a BunkerContext WITHOUT performing the network handshake.
|
|
105
|
+
* Lets a caller bring its process up immediately and connect in the
|
|
106
|
+
* background (see {@link establish}), so a slow or unreachable signer
|
|
107
|
+
* cannot wedge startup. Under an MCP stdio health check that respawns
|
|
108
|
+
* "failed to connect" servers, a wedged startup turns a flaky signer into
|
|
109
|
+
* a respawn loop that floods the bunker with `connect` requests.
|
|
110
|
+
*/
|
|
111
|
+
static create(uri, stateDir) {
|
|
84
112
|
const config = parseBunkerUri(uri);
|
|
85
113
|
const clientSk = resolveClientKey(config, stateDir);
|
|
86
114
|
const pool = new SimplePool();
|
|
@@ -93,18 +121,39 @@ export class BunkerContext {
|
|
|
93
121
|
// request will require a manual approval on the signer side.
|
|
94
122
|
secret: config.connectSecret ?? null,
|
|
95
123
|
}, { pool });
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
124
|
+
// Do NOT use config.pubkey as the identity -- it is the bunker's
|
|
125
|
+
// transport key from the URI, not the signing identity. The actual
|
|
126
|
+
// identity pubkey is resolved lazily via resolvePublicKey().
|
|
127
|
+
// The parsed config is retained so establish() can send the connect
|
|
128
|
+
// handshake with its client-name metadata.
|
|
129
|
+
return new BunkerContext(signer, pool, clientSk, config);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Perform the NIP-46 `connect` handshake, bounded by `timeoutMs`.
|
|
133
|
+
*
|
|
134
|
+
* nostr-tools' BunkerSigner has no per-request timeout, so a signer that is
|
|
135
|
+
* offline or whose relays are unreachable makes `signer.connect()` hang
|
|
136
|
+
* forever. We race it against a timer and reject on expiry so the caller can
|
|
137
|
+
* retry with backoff. Ping is redundant (a successful connect proves the
|
|
138
|
+
* signer is alive) and getPublicKey is deferred to first access.
|
|
139
|
+
*/
|
|
140
|
+
async establish(timeoutMs = CONNECT_TIMEOUT_MS) {
|
|
141
|
+
// Sent via sendRequest (not signer.connect()) so params[3] can carry the
|
|
142
|
+
// client-name metadata that signer approval screens display. nostr-tools'
|
|
143
|
+
// connect() is literally sendRequest('connect', [pubkey, secret]);
|
|
144
|
+
// semantics are otherwise identical.
|
|
145
|
+
await withTimeout(this.signer.sendRequest('connect', buildConnectParams(this.config)), timeoutMs, 'bunker connect');
|
|
146
|
+
}
|
|
147
|
+
/** Connect to a remote bunker. Blocks until the connection is established. */
|
|
148
|
+
static async connect(uri, timeoutMs = CONNECT_TIMEOUT_MS, stateDir) {
|
|
149
|
+
const ctx = BunkerContext.create(uri, stateDir);
|
|
150
|
+
try {
|
|
151
|
+
await ctx.establish(timeoutMs);
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
ctx.destroy();
|
|
155
|
+
throw e;
|
|
156
|
+
}
|
|
108
157
|
return ctx;
|
|
109
158
|
}
|
|
110
159
|
/**
|
|
@@ -114,7 +163,7 @@ export class BunkerContext {
|
|
|
114
163
|
*/
|
|
115
164
|
async resolvePublicKey() {
|
|
116
165
|
if (!this.pubkeyHex) {
|
|
117
|
-
this.pubkeyHex = await this.signer.getPublicKey();
|
|
166
|
+
this.pubkeyHex = await withTimeout(this.signer.getPublicKey(), REQUEST_TIMEOUT_MS, 'bunker get_public_key');
|
|
118
167
|
}
|
|
119
168
|
return this.pubkeyHex;
|
|
120
169
|
}
|
|
@@ -135,7 +184,7 @@ export class BunkerContext {
|
|
|
135
184
|
/** Sign an event via the remote bunker */
|
|
136
185
|
getSigningFunction() {
|
|
137
186
|
return async (template) => {
|
|
138
|
-
return this.signer.signEvent(template);
|
|
187
|
+
return withTimeout(this.signer.signEvent(template), REQUEST_TIMEOUT_MS, 'bunker sign_event');
|
|
139
188
|
};
|
|
140
189
|
}
|
|
141
190
|
/** List identities — bunker mode only has one (the remote key) */
|
|
@@ -144,11 +193,11 @@ export class BunkerContext {
|
|
|
144
193
|
}
|
|
145
194
|
/** NIP-44 encrypt via the remote bunker */
|
|
146
195
|
async nip44Encrypt(recipientPubkey, plaintext) {
|
|
147
|
-
return this.signer.nip44Encrypt(recipientPubkey, plaintext);
|
|
196
|
+
return withTimeout(this.signer.nip44Encrypt(recipientPubkey, plaintext), REQUEST_TIMEOUT_MS, 'bunker nip44_encrypt');
|
|
148
197
|
}
|
|
149
198
|
/** NIP-44 decrypt via the remote bunker */
|
|
150
199
|
async nip44Decrypt(senderPubkey, ciphertext) {
|
|
151
|
-
return this.signer.nip44Decrypt(senderPubkey, ciphertext);
|
|
200
|
+
return withTimeout(this.signer.nip44Decrypt(senderPubkey, ciphertext), REQUEST_TIMEOUT_MS, 'bunker nip44_decrypt');
|
|
152
201
|
}
|
|
153
202
|
/** Clean up */
|
|
154
203
|
destroy() {
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
+
import type { AdminMethod } from '../../exports.js';
|
|
1
2
|
import type { Helpers } from '../dispatch.js';
|
|
3
|
+
export declare const ADMIN_METHODS: Set<AdminMethod>;
|
|
4
|
+
/** Legacy subcommand spellings still accepted on the CLI. */
|
|
5
|
+
export declare const ADMIN_ALIASES: string[];
|
|
2
6
|
export declare function dispatch(cmd: string, cmdArgs: string[], h: Helpers, ctx: any): Promise<void>;
|
|
@@ -1,26 +1,37 @@
|
|
|
1
|
-
import { handleAdminCall } from '../../exports.js';
|
|
2
|
-
const ADMIN_METHODS = new Set([
|
|
3
|
-
'
|
|
4
|
-
'
|
|
1
|
+
import { handleAdminCall, coerceAdminParams, resolveAdminMethod, ADMIN_METHOD_ALIASES } from '../../exports.js';
|
|
2
|
+
export const ADMIN_METHODS = new Set([
|
|
3
|
+
'supportedmethods',
|
|
4
|
+
'allowpubkey', 'unallowpubkey', 'banpubkey', 'unbanpubkey',
|
|
5
|
+
'listallowedpubkeys', 'listbannedpubkeys',
|
|
6
|
+
'allowkind', 'disallowkind', 'listallowedkinds', 'listdisallowedkinds',
|
|
7
|
+
'allowevent', 'banevent', 'listbannedevents', 'listeventsneedingmoderation',
|
|
8
|
+
'changerelayname', 'changerelaydescription', 'changerelayicon',
|
|
9
|
+
'createrole', 'editrole', 'deleterole', 'assignrole', 'unassignrole',
|
|
10
|
+
'grantadmin', 'revokeadmin',
|
|
5
11
|
'blockip', 'unblockip', 'listblockedips',
|
|
6
12
|
]);
|
|
13
|
+
/** Legacy subcommand spellings still accepted on the CLI. */
|
|
14
|
+
export const ADMIN_ALIASES = Object.keys(ADMIN_METHOD_ALIASES);
|
|
7
15
|
export async function dispatch(cmd, cmdArgs, h, ctx) {
|
|
8
16
|
// cmd is always 'admin'; subcommand is cmdArgs[1] (already sliced by normaliser)
|
|
9
17
|
// After COMPOUND_COMMANDS normalisation: cmd = 'admin-<subcommand>'
|
|
10
18
|
const { req, out } = h;
|
|
11
|
-
// Extract the method from the compound command name
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
// Extract the method from the compound command name, mapping legacy spellings.
|
|
20
|
+
// A bare `admin` means the subcommand was not recognised during normalisation.
|
|
21
|
+
const requested = cmd === 'admin' ? (cmdArgs[1] ?? '') : cmd.replace(/^admin-/, '');
|
|
22
|
+
const method = resolveAdminMethod(requested);
|
|
23
|
+
if (!requested || !ADMIN_METHODS.has(method)) {
|
|
24
|
+
throw new Error(`${requested ? `Unknown admin subcommand: ${requested}.` : 'admin needs a subcommand.'}\n` +
|
|
25
|
+
`Usage: admin <subcommand> <relay-url> [param...]\n` +
|
|
26
|
+
`Valid: ${[...ADMIN_METHODS].join(', ')}`);
|
|
27
|
+
}
|
|
28
|
+
if (requested !== method) {
|
|
29
|
+
console.error(`note: "admin ${requested}" is not a NIP-86 method name; sending "${method}" instead`);
|
|
15
30
|
}
|
|
16
31
|
const relay = req(1, `admin ${method} <relay-url> [param...]`);
|
|
17
32
|
// Remaining positional args after relay URL are method params
|
|
18
33
|
const params = cmdArgs.slice(2).filter(a => !a.startsWith('--'));
|
|
19
|
-
|
|
20
|
-
const coercedParams = params.map(p => {
|
|
21
|
-
const n = Number(p);
|
|
22
|
-
return isNaN(n) ? p : n;
|
|
23
|
-
});
|
|
34
|
+
const coercedParams = coerceAdminParams(method, params);
|
|
24
35
|
out(await handleAdminCall(ctx, {
|
|
25
36
|
relay,
|
|
26
37
|
method,
|
|
@@ -15,13 +15,19 @@ export async function dispatch(cmd, _cmdArgs, h, ctx, pool) {
|
|
|
15
15
|
const content = flag('content') ?? '';
|
|
16
16
|
const validationMode = parseValidationMode(flag('validation'));
|
|
17
17
|
const relayOverrides = flags('relay');
|
|
18
|
+
const pow = flag('pow') ? parseInt(flag('pow'), 10) : undefined;
|
|
19
|
+
const powTimeoutMs = flag('pow-timeout') ? parseInt(flag('pow-timeout'), 10) : undefined;
|
|
18
20
|
if (hasFlag('no-publish')) {
|
|
19
|
-
const
|
|
21
|
+
const { minePow } = await import('../../event/pow.js');
|
|
22
|
+
let template = {
|
|
20
23
|
kind,
|
|
21
24
|
created_at: Math.floor(Date.now() / 1000),
|
|
22
25
|
tags: tagValues,
|
|
23
26
|
content,
|
|
24
27
|
};
|
|
28
|
+
if (pow !== undefined) {
|
|
29
|
+
template = minePow(template, { difficulty: pow, pubkey: ctx.activePublicKeyHex, timeoutMs: powTimeoutMs }).template;
|
|
30
|
+
}
|
|
25
31
|
const validation = assertEventSemanticallyValid(template, validationMode);
|
|
26
32
|
reportWarnings(validation.issues);
|
|
27
33
|
const sign = ctx.getSigningFunction();
|
|
@@ -35,6 +41,8 @@ export async function dispatch(cmd, _cmdArgs, h, ctx, pool) {
|
|
|
35
41
|
tags: tagValues,
|
|
36
42
|
relays: relayOverrides.length ? relayOverrides : undefined,
|
|
37
43
|
validationMode,
|
|
44
|
+
pow,
|
|
45
|
+
powTimeoutMs,
|
|
38
46
|
}));
|
|
39
47
|
break;
|
|
40
48
|
}
|
|
@@ -56,6 +64,8 @@ export async function dispatch(cmd, _cmdArgs, h, ctx, pool) {
|
|
|
56
64
|
timeoutMs,
|
|
57
65
|
quorum,
|
|
58
66
|
validationMode,
|
|
67
|
+
pow: flag('pow') ? parseInt(flag('pow'), 10) : undefined,
|
|
68
|
+
powTimeoutMs: flag('pow-timeout') ? parseInt(flag('pow-timeout'), 10) : undefined,
|
|
59
69
|
});
|
|
60
70
|
if (report) {
|
|
61
71
|
const { accepted, rejected, errors } = result.publish;
|
|
@@ -57,15 +57,24 @@ export async function dispatch(cmd, cmdArgs, h, ctx, pool, _activeNpub) {
|
|
|
57
57
|
tags[k].push(v);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
...(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
const paginate = hasFlag('paginate');
|
|
61
|
+
const paginateIntervalMs = flag('paginate-interval') ? parseInt(flag('paginate-interval'), 10) : undefined;
|
|
62
|
+
const maxPages = flag('max-pages') ? parseInt(flag('max-pages'), 10) : undefined;
|
|
63
|
+
const queryArgs = {
|
|
64
|
+
...(stdinFilter ?? {
|
|
65
|
+
...(kindsRaw ? { kinds: kindsRaw.split(',').map(Number) } : {}),
|
|
66
|
+
...(authorsRaw ? { authors: authorsRaw.split(',') } : {}),
|
|
67
|
+
...(since !== undefined ? { since } : {}),
|
|
68
|
+
...(until !== undefined ? { until } : {}),
|
|
69
|
+
...(limit !== undefined ? { limit } : {}),
|
|
70
|
+
...(search ? { search } : {}),
|
|
71
|
+
...(Object.keys(tags).length ? { tags } : {}),
|
|
72
|
+
...(relayOverrides.length ? { relays: relayOverrides } : {}),
|
|
73
|
+
}),
|
|
74
|
+
// Pagination is a transport concern, so it applies to a stdin filter too
|
|
75
|
+
...(paginate ? { paginate } : {}),
|
|
76
|
+
...(paginateIntervalMs !== undefined ? { paginateIntervalMs } : {}),
|
|
77
|
+
...(maxPages !== undefined ? { maxPages } : {}),
|
|
69
78
|
};
|
|
70
79
|
let events = await handleRelayQuery(pool, ctx.activeNpub, queryArgs);
|
|
71
80
|
const minTrust = flag('min-trust');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { handleDecode, handleEncodeNpub, handleEncodeNote, handleEncodeNprofile, handleEncodeNevent, handleVerify, handleEncrypt, handleDecrypt, handleCount, handleFetch, handleKeyPublic, handleEncodeNsec, handleFilter, handleNipList, handleNipShow, handleKeyEncrypt, handleKeyDecrypt, handleValidateEvent, } from '../../exports.js';
|
|
1
|
+
import { handleDecode, handleEncodeNpub, handleEncodeNote, handleEncodeNprofile, handleEncodeNevent, handleVerify, handleEncrypt, handleDecrypt, handleCount, handleFetch, handleKeyPublic, handleEncodeNsec, handleFilter, handleNipList, handleNipShow, handleKeyEncrypt, handleKeyDecrypt, handleValidateEvent, lookupKind, searchKinds, } from '../../exports.js';
|
|
2
2
|
import { validateInputPath } from '../../validation.js';
|
|
3
3
|
import * as fmt from '../../format.js';
|
|
4
4
|
export async function dispatch(cmd, cmdArgs, h, ctx, pool) {
|
|
@@ -63,6 +63,19 @@ export async function dispatch(cmd, cmdArgs, h, ctx, pool) {
|
|
|
63
63
|
out(handleValidateEvent(JSON.parse(raw), mode));
|
|
64
64
|
break;
|
|
65
65
|
}
|
|
66
|
+
case 'kind': {
|
|
67
|
+
const arg = req(1, 'kind <number|search text>');
|
|
68
|
+
if (/^\d+$/.test(arg)) {
|
|
69
|
+
out(lookupKind(Number(arg)), fmt.formatKindInfo);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const results = searchKinds(cmdArgs.slice(1).filter(a => !a.startsWith('--')).join(' '));
|
|
73
|
+
if (results.length === 0)
|
|
74
|
+
throw new Error(`No kind matches "${arg}"`);
|
|
75
|
+
out(results, rs => rs.map(fmt.formatKindInfo).join('\n\n'));
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
66
79
|
case 'encrypt': {
|
|
67
80
|
const skHex = Buffer.from(ctx.activePrivateKey).toString('hex');
|
|
68
81
|
console.log(handleEncrypt(skHex, req(1, 'encrypt <pubkey-hex> "plaintext"'), req(2, 'encrypt <pubkey-hex> "plaintext"')));
|
package/dist/cli/dispatch.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/** Shared dispatch helpers shared across all CLI command modules. */
|
|
2
|
+
export declare const ADMIN_SUBCOMMANDS: readonly ["supportedmethods", "allowpubkey", "unallowpubkey", "banpubkey", "unbanpubkey", "listallowedpubkeys", "listbannedpubkeys", "allowkind", "disallowkind", "listallowedkinds", "listdisallowedkinds", "allowevent", "banevent", "listbannedevents", "listeventsneedingmoderation", "changerelayname", "changerelaydescription", "changerelayicon", "createrole", "editrole", "deleterole", "assignrole", "unassignrole", "grantadmin", "revokeadmin", "blockip", "unblockip", "listblockedips", "bankind", "listbannedkinds"];
|
|
3
|
+
export declare const ADMIN_COMMANDS: string[];
|
|
2
4
|
export declare const COMPOUND_COMMANDS: Set<string>;
|
|
3
5
|
export declare const OFFLINE_COMMANDS: Set<string>;
|
|
4
6
|
export interface Helpers {
|
package/dist/cli/dispatch.js
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
/** Shared dispatch helpers shared across all CLI command modules. */
|
|
2
|
+
// NIP-86 relay management subcommands, plus the two legacy bray spellings
|
|
3
|
+
// (`bankind`, `listbannedkinds`) kept as aliases. Declared once and reused for
|
|
4
|
+
// compound-command normalisation, routing, and shell completion.
|
|
5
|
+
export const ADMIN_SUBCOMMANDS = [
|
|
6
|
+
'supportedmethods',
|
|
7
|
+
'allowpubkey', 'unallowpubkey', 'banpubkey', 'unbanpubkey',
|
|
8
|
+
'listallowedpubkeys', 'listbannedpubkeys',
|
|
9
|
+
'allowkind', 'disallowkind', 'listallowedkinds', 'listdisallowedkinds',
|
|
10
|
+
'allowevent', 'banevent', 'listbannedevents', 'listeventsneedingmoderation',
|
|
11
|
+
'changerelayname', 'changerelaydescription', 'changerelayicon',
|
|
12
|
+
'createrole', 'editrole', 'deleterole', 'assignrole', 'unassignrole',
|
|
13
|
+
'grantadmin', 'revokeadmin',
|
|
14
|
+
'blockip', 'unblockip', 'listblockedips',
|
|
15
|
+
// legacy aliases
|
|
16
|
+
'bankind', 'listbannedkinds',
|
|
17
|
+
];
|
|
18
|
+
export const ADMIN_COMMANDS = ADMIN_SUBCOMMANDS.map(s => `admin-${s}`);
|
|
2
19
|
// Known two-word commands: `noun subverb` → normalised to `noun-subverb` internally.
|
|
3
20
|
// Enumerated explicitly to avoid swallowing positional args (e.g. `profile <pubkey>`).
|
|
4
21
|
export const COMPOUND_COMMANDS = new Set([
|
|
@@ -16,9 +33,7 @@ export const COMPOUND_COMMANDS = new Set([
|
|
|
16
33
|
// sync
|
|
17
34
|
'sync-plan', 'sync-pull', 'sync-push',
|
|
18
35
|
// admin (NIP-86 relay management)
|
|
19
|
-
|
|
20
|
-
'admin-allowkind', 'admin-bankind', 'admin-listallowedkinds', 'admin-listbannedkinds',
|
|
21
|
-
'admin-blockip', 'admin-unblockip', 'admin-listblockedips',
|
|
36
|
+
...ADMIN_COMMANDS,
|
|
22
37
|
// wallet (NIP-47 Nostr Wallet Connect)
|
|
23
38
|
'wallet-connect', 'wallet-disconnect', 'wallet-status', 'wallet-pay', 'wallet-balance', 'wallet-history',
|
|
24
39
|
]);
|
|
@@ -29,10 +44,12 @@ export const OFFLINE_COMMANDS = new Set([
|
|
|
29
44
|
'ring-verify', 'zap-decode', 'safety-configure', 'safety-activate',
|
|
30
45
|
'decode', 'encode-npub', 'encode-note', 'encode-nprofile', 'encode-nevent', 'encode-nsec',
|
|
31
46
|
'key-public', 'key-encrypt', 'key-decrypt', 'filter', 'verify', 'encrypt', 'decrypt',
|
|
32
|
-
'validate-event',
|
|
47
|
+
'validate-event', 'kind',
|
|
33
48
|
'musig2-key', 'musig2-nonce', 'musig2-partial-sign', 'musig2-aggregate',
|
|
34
49
|
// wallet config subcommands don't touch the relay
|
|
35
50
|
'wallet-connect', 'wallet-disconnect', 'wallet-status',
|
|
51
|
+
// NIP-86 admin calls are plain HTTP to the target relay; the pool is not used
|
|
52
|
+
...ADMIN_COMMANDS,
|
|
36
53
|
]);
|
|
37
54
|
export function resolveOutputMode(cmdArgs, envDefault) {
|
|
38
55
|
if (cmdArgs.includes('--jsonl'))
|