@ours.network/cli 0.1.0 → 0.1.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.
@@ -1,10 +1,21 @@
1
1
  import type { OursClient } from '@ours.network/sdk/client';
2
- type FieldKind = 'string' | 'boolean' | 'integer' | 'string[]' | 'integer[]' | 'cursor';
3
- interface FieldSpec {
2
+ export type FieldKind = 'string' | 'boolean' | 'integer' | 'string[]' | 'integer[]' | 'cursor';
3
+ export interface FieldSpec {
4
4
  kind: FieldKind;
5
+ help: string;
6
+ valueName: string;
5
7
  required?: boolean;
6
8
  values?: readonly string[];
7
9
  positive?: boolean;
10
+ allowEmpty?: boolean;
11
+ }
12
+ export interface OperationSpec {
13
+ method: keyof OursClient;
14
+ fields: Readonly<Record<string, FieldSpec>>;
15
+ summary: string;
16
+ effects: string;
17
+ exampleInput?: Readonly<Record<string, unknown>>;
18
+ refine?: (input: Record<string, unknown>) => void;
8
19
  }
9
20
  export declare const OPERATION_SPECS: {
10
21
  readonly 'create-identity': {
@@ -15,6 +26,14 @@ export declare const OPERATION_SPECS: {
15
26
  readonly exposeLocal: FieldSpec;
16
27
  readonly localAutoAccept: FieldSpec;
17
28
  };
29
+ readonly summary: "Create a persistent identity and bind this CLI session to it.";
30
+ readonly effects: "Writes identity state. With a Human identity, the new identity becomes its role; otherwise the first persistent identity becomes the Human identity.";
31
+ readonly exampleInput: {
32
+ readonly name: "BuildBot";
33
+ readonly bio: "Build coordinator";
34
+ readonly exposeLocal: true;
35
+ readonly localAutoAccept: true;
36
+ };
18
37
  };
19
38
  readonly 'create-temporary-identity': {
20
39
  readonly method: "createTemporaryIdentity";
@@ -24,12 +43,25 @@ export declare const OPERATION_SPECS: {
24
43
  readonly exposeLocal: FieldSpec;
25
44
  readonly localAutoAccept: FieldSpec;
26
45
  };
46
+ readonly summary: "Create and bind a session-owned temporary identity.";
47
+ readonly effects: "Writes temporary identity state. Session teardown or lease release closes it, sends best-effort remove-me notices, and permanently deletes its local state.";
48
+ readonly exampleInput: {
49
+ readonly name: "ScratchBot";
50
+ readonly bio: "Short-lived test operator";
51
+ readonly exposeLocal: false;
52
+ readonly localAutoAccept: true;
53
+ };
27
54
  };
28
55
  readonly 'close-temporary-identity': {
29
56
  readonly method: "closeTemporaryIdentityOp";
30
57
  readonly fields: {
31
58
  readonly name: FieldSpec;
32
59
  };
60
+ readonly summary: "Close a temporary identity owned by this session, or reclaim one whose owner is stale.";
61
+ readonly effects: "Destructive: queues best-effort remove-me notices and permanently deletes local identity state. A live owner cannot be evicted.";
62
+ readonly exampleInput: {
63
+ readonly name: "ScratchBot";
64
+ };
33
65
  };
34
66
  readonly 'create-root-identity': {
35
67
  readonly method: "createRootIdentity";
@@ -40,6 +72,15 @@ export declare const OPERATION_SPECS: {
40
72
  readonly localAutoAccept: FieldSpec;
41
73
  readonly skipIfRootExists: FieldSpec;
42
74
  };
75
+ readonly summary: "Create the host Human identity and bind this CLI session to it.";
76
+ readonly effects: "Writes identity state and may adopt existing persistent identities as roles. If a Human identity already exists, a non-skipped creation becomes a role under it.";
77
+ readonly exampleInput: {
78
+ readonly name: "Alice@laptop";
79
+ readonly bio: "Alice on laptop";
80
+ readonly exposeLocal: true;
81
+ readonly localAutoAccept: true;
82
+ readonly skipIfRootExists: true;
83
+ };
43
84
  };
44
85
  readonly 'define-local-identity-file': {
45
86
  readonly method: "defineLocalIdentityFile";
@@ -51,6 +92,16 @@ export declare const OPERATION_SPECS: {
51
92
  readonly localAutoAccept: FieldSpec;
52
93
  readonly overwrite: FieldSpec;
53
94
  };
95
+ readonly summary: "Write an advisory .ours-identity workspace pin.";
96
+ readonly effects: "Writes only the workspace pin file. It does not create or bind an identity.";
97
+ readonly exampleInput: {
98
+ readonly name: "BuildBot";
99
+ readonly path: "/srv/project";
100
+ readonly force: false;
101
+ readonly exposeLocal: true;
102
+ readonly localAutoAccept: true;
103
+ readonly overwrite: false;
104
+ };
54
105
  };
55
106
  readonly 'choose-identity': {
56
107
  readonly method: "chooseIdentity";
@@ -58,24 +109,41 @@ export declare const OPERATION_SPECS: {
58
109
  readonly name: FieldSpec;
59
110
  readonly force: FieldSpec;
60
111
  };
112
+ readonly summary: "Bind an existing identity to this CLI lease.";
113
+ readonly effects: "Changes lease ownership. Force can evict another live session; temporary identities still enforce their session owner.";
114
+ readonly exampleInput: {
115
+ readonly name: "BuildBot";
116
+ readonly force: false;
117
+ };
61
118
  };
62
119
  readonly 'list-identities': {
63
120
  readonly method: "listIdentities";
64
121
  readonly fields: {};
122
+ readonly summary: "List identities, hierarchy, temporary ownership, and lease state.";
123
+ readonly effects: "Read-only.";
65
124
  };
66
125
  readonly 'current-identity': {
67
126
  readonly method: "currentIdentity";
68
127
  readonly fields: {};
128
+ readonly summary: "Show the identity bound to this CLI lease, including its profile and hierarchy.";
129
+ readonly effects: "Read-only.";
69
130
  };
70
131
  readonly 'remove-identity': {
71
132
  readonly method: "removeIdentity";
72
133
  readonly fields: {
73
134
  readonly name: FieldSpec;
74
135
  };
136
+ readonly summary: "Permanently remove an identity.";
137
+ readonly effects: "Destructive: deletes local identity state. A Human identity cannot be removed while it still has roles; temporary identities follow their close lifecycle.";
138
+ readonly exampleInput: {
139
+ readonly name: "OldBot";
140
+ };
75
141
  };
76
142
  readonly 'release-lease': {
77
143
  readonly method: "releaseLease";
78
144
  readonly fields: {};
145
+ readonly summary: "Release every identity lease held by this CLI client token.";
146
+ readonly effects: "Changes lease state. Owned temporary identities begin asynchronous permanent closure and best-effort contact notification.";
79
147
  };
80
148
  readonly 'generate-invite': {
81
149
  readonly method: "generateInvite";
@@ -83,16 +151,29 @@ export declare const OPERATION_SPECS: {
83
151
  readonly name: FieldSpec;
84
152
  readonly mode: FieldSpec;
85
153
  };
154
+ readonly summary: "Generate encrypted contact-introduction material for out-of-band sharing.";
155
+ readonly effects: "Creates an outstanding invite. Omitted mode defaults to one_time; public invites cannot pre-assign a name.";
156
+ readonly exampleInput: {
157
+ readonly name: "Peer";
158
+ readonly mode: "one_time";
159
+ };
86
160
  };
87
161
  readonly 'list-invites': {
88
162
  readonly method: "listInvites";
89
163
  readonly fields: {};
164
+ readonly summary: "List outstanding one-time and public invites.";
165
+ readonly effects: "Read-only.";
90
166
  };
91
167
  readonly 'revoke-invite': {
92
168
  readonly method: "revokeInvite";
93
169
  readonly fields: {
94
170
  readonly invite_id: FieldSpec;
95
171
  };
172
+ readonly summary: "Revoke an outstanding invite.";
173
+ readonly effects: "Destructive and idempotent. It prevents future redemption but does not remove existing contacts.";
174
+ readonly exampleInput: {
175
+ readonly invite_id: "INVITE_ID";
176
+ };
96
177
  };
97
178
  readonly 'add-contact': {
98
179
  readonly method: "addContact";
@@ -100,14 +181,24 @@ export declare const OPERATION_SPECS: {
100
181
  readonly invite: FieldSpec;
101
182
  readonly name: FieldSpec;
102
183
  };
184
+ readonly summary: "Redeem an invite and begin an encrypted contact handshake.";
185
+ readonly effects: "Sends the first handshake leg. The contact remains pending until the inviter verifies and replies.";
186
+ readonly exampleInput: {
187
+ readonly invite: "<invite-from-peer>";
188
+ readonly name: "Peer";
189
+ };
103
190
  };
104
191
  readonly 'list-contacts': {
105
192
  readonly method: "listContacts";
106
193
  readonly fields: {};
194
+ readonly summary: "List established contacts and pending local introductions.";
195
+ readonly effects: "Read-only.";
107
196
  };
108
197
  readonly 'list-local-contact-book': {
109
198
  readonly method: "listLocalContactBook";
110
199
  readonly fields: {};
200
+ readonly summary: "List same-host identities published for invite-free introduction.";
201
+ readonly effects: "Read-only.";
111
202
  };
112
203
  readonly 'set-local-book-policy': {
113
204
  readonly method: "setLocalBookPolicy";
@@ -115,6 +206,12 @@ export declare const OPERATION_SPECS: {
115
206
  readonly expose: FieldSpec;
116
207
  readonly auto_accept: FieldSpec;
117
208
  };
209
+ readonly summary: "Change local contact-book exposure and/or introduction approval policy.";
210
+ readonly effects: "Writes identity-local policy. At least one of expose or auto_accept is required.";
211
+ readonly exampleInput: {
212
+ readonly expose: true;
213
+ readonly auto_accept: false;
214
+ };
118
215
  readonly refine: (a: Record<string, unknown>) => void;
119
216
  };
120
217
  readonly 'remove-contact': {
@@ -122,6 +219,11 @@ export declare const OPERATION_SPECS: {
122
219
  readonly fields: {
123
220
  readonly contact: FieldSpec;
124
221
  };
222
+ readonly summary: "Forget a contact locally.";
223
+ readonly effects: "Destructive: removes the local contact and queues one best-effort remove-me notice; remote deletion is not guaranteed.";
224
+ readonly exampleInput: {
225
+ readonly contact: "OldPeer";
226
+ };
125
227
  };
126
228
  readonly 'rename-contact': {
127
229
  readonly method: "renameContact";
@@ -129,6 +231,12 @@ export declare const OPERATION_SPECS: {
129
231
  readonly contact: FieldSpec;
130
232
  readonly name: FieldSpec;
131
233
  };
234
+ readonly summary: "Rename a contact locally without changing keys or the encrypted channel.";
235
+ readonly effects: "Changes only the local display name.";
236
+ readonly exampleInput: {
237
+ readonly contact: "Peer";
238
+ readonly name: "PeerLaptop";
239
+ };
132
240
  };
133
241
  readonly 'respond-to-introduction': {
134
242
  readonly method: "respondToIntroduction";
@@ -136,18 +244,34 @@ export declare const OPERATION_SPECS: {
136
244
  readonly contact: FieldSpec;
137
245
  readonly action: FieldSpec;
138
246
  };
247
+ readonly summary: "Approve or reject a queued local introduction.";
248
+ readonly effects: "Approval establishes the contact and moves queued messages into the inbox; rejection drops the queue.";
249
+ readonly exampleInput: {
250
+ readonly contact: "LocalBot";
251
+ readonly action: "approve";
252
+ };
139
253
  };
140
254
  readonly 'set-bio': {
141
255
  readonly method: "setBio";
142
256
  readonly fields: {
143
257
  readonly bio: FieldSpec;
144
258
  };
259
+ readonly summary: "Replace the bound identity’s public bio.";
260
+ readonly effects: "Writes public profile state. Updating a Human identity also refreshes its profile in role delegation chains.";
261
+ readonly exampleInput: {
262
+ readonly bio: "Build coordinator";
263
+ };
145
264
  };
146
265
  readonly 'set-persona': {
147
266
  readonly method: "setPersona";
148
267
  readonly fields: {
149
268
  readonly persona: FieldSpec;
150
269
  };
270
+ readonly summary: "Replace the bound identity’s local persona.";
271
+ readonly effects: "Writes local profile state. Personas are not shared in invites.";
272
+ readonly exampleInput: {
273
+ readonly persona: "Handle build coordination only.";
274
+ };
151
275
  };
152
276
  readonly 'send-message': {
153
277
  readonly method: "sendMessage";
@@ -157,6 +281,12 @@ export declare const OPERATION_SPECS: {
157
281
  readonly reply_to_wire_id: FieldSpec;
158
282
  readonly reply_to_sentence: FieldSpec;
159
283
  };
284
+ readonly summary: "Send an end-to-end-encrypted text message.";
285
+ readonly effects: "Sends data. A verified same-host identity may be introduced automatically when it is not already a contact.";
286
+ readonly exampleInput: {
287
+ readonly contact: "Peer";
288
+ readonly text: "Build finished.";
289
+ };
160
290
  };
161
291
  readonly 'send-file': {
162
292
  readonly method: "sendFile";
@@ -169,77 +299,132 @@ export declare const OPERATION_SPECS: {
169
299
  readonly reply_to_wire_id: FieldSpec;
170
300
  readonly reply_to_sentence: FieldSpec;
171
301
  };
302
+ readonly summary: "Send an end-to-end-encrypted file from a path or inline base64.";
303
+ readonly effects: "Reads the selected local file when path is used and sends bytes. Exactly one of path or data_base64 is required; data_base64 also requires filename.";
304
+ readonly exampleInput: {
305
+ readonly contact: "Peer";
306
+ readonly path: "/tmp/report.pdf";
307
+ };
172
308
  readonly refine: (a: Record<string, unknown>) => void;
173
309
  };
174
310
  readonly 'list-incoming-messages': {
175
311
  readonly method: "listIncomingMessages";
176
312
  readonly fields: {};
313
+ readonly summary: "List inbox metadata and status without revealing message bodies.";
314
+ readonly effects: "Read-only; message status is unchanged.";
177
315
  };
178
316
  readonly 'get-messages': {
179
317
  readonly method: "getMessages";
180
318
  readonly fields: {};
319
+ readonly summary: "Return every unread message body and mark those messages processed.";
320
+ readonly effects: "Mutates inbox status. This is the only message-body read and is exactly-once unless the returned message IDs are deferred.";
181
321
  };
182
322
  readonly 'defer-messages': {
183
323
  readonly method: "deferMessages";
184
324
  readonly fields: {
185
325
  readonly msg_ids: FieldSpec;
186
326
  };
327
+ readonly summary: "Return processed messages to the unread queue.";
328
+ readonly effects: "Mutates inbox status.";
329
+ readonly exampleInput: {
330
+ readonly msg_ids: readonly [12, 13];
331
+ };
187
332
  };
188
333
  readonly 'defer-files': {
189
334
  readonly method: "deferFiles";
190
335
  readonly fields: {
191
336
  readonly file_ids: FieldSpec;
192
337
  };
338
+ readonly summary: "Return processed files to the unread queue.";
339
+ readonly effects: "Mutates inbox status; already-unread files are left unchanged.";
340
+ readonly exampleInput: {
341
+ readonly file_ids: readonly [4, 5];
342
+ };
193
343
  };
194
344
  readonly 'get-conversation': {
195
345
  readonly method: "getConversation";
196
346
  readonly fields: {
197
347
  readonly contact: FieldSpec;
198
348
  };
349
+ readonly summary: "Show retained conversation history with one contact, oldest first.";
350
+ readonly effects: "Read-only. It is empty when history retention is disabled.";
351
+ readonly exampleInput: {
352
+ readonly contact: "Peer";
353
+ };
199
354
  };
200
355
  readonly 'get-receipts': {
201
356
  readonly method: "getReceipts";
202
357
  readonly fields: {
203
358
  readonly contact: FieldSpec;
204
359
  };
360
+ readonly summary: "Show delivered/read receipts received for sent messages.";
361
+ readonly effects: "Read-only. Receipts are retained only when conversation history is enabled.";
362
+ readonly exampleInput: {
363
+ readonly contact: "Peer";
364
+ };
205
365
  };
206
366
  readonly 'mark-read': {
207
367
  readonly method: "markRead";
208
368
  readonly fields: {
209
369
  readonly contact: FieldSpec;
210
370
  };
371
+ readonly summary: "Mark unread inbound entries in one conversation as read.";
372
+ readonly effects: "Mutates conversation state and sends read receipts for newly marked messages.";
373
+ readonly exampleInput: {
374
+ readonly contact: "Peer";
375
+ };
211
376
  };
212
377
  readonly 'set-conversation-policy': {
213
378
  readonly method: "setConversationPolicy";
214
379
  readonly fields: {
215
380
  readonly keep_history: FieldSpec;
216
381
  };
382
+ readonly summary: "Set conversation-history policy for the bound identity.";
383
+ readonly effects: "Writes identity-local policy. Existing contacts learn newly enabled receipt capability on later outbound traffic.";
384
+ readonly exampleInput: {
385
+ readonly keep_history: true;
386
+ };
217
387
  };
218
388
  readonly 'list-incoming-files': {
219
389
  readonly method: "listIncomingFiles";
220
390
  readonly fields: {};
391
+ readonly summary: "List incoming-file metadata for authorization.";
392
+ readonly effects: "Read-only: returns no bytes and does not change file status. Authorize using the authenticated sender ID, not its display name.";
221
393
  };
222
394
  readonly 'get-files': {
223
395
  readonly method: "getFiles";
224
396
  readonly fields: {
225
397
  readonly wire_ids: FieldSpec;
226
398
  };
399
+ readonly summary: "Write approved unread files into the bound identity’s files directory.";
400
+ readonly effects: "Writes received bytes and marks retrieved files processed. An explicit selection must contain 1–32 unique 64-hex wire IDs.";
401
+ readonly exampleInput: {
402
+ readonly wire_ids: readonly ["0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"];
403
+ };
227
404
  };
228
405
  readonly version: {
229
406
  readonly method: "version";
230
407
  readonly fields: {};
408
+ readonly summary: "Show the selected daemon’s version and protocol information.";
409
+ readonly effects: "Read-only.";
231
410
  };
232
411
  readonly 'state-dir': {
233
412
  readonly method: "stateDir";
234
413
  readonly fields: {};
414
+ readonly summary: "Show the selected daemon’s canonical state directory.";
415
+ readonly effects: "Read-only.";
235
416
  };
236
417
  readonly identities: {
237
418
  readonly method: "identities";
238
419
  readonly fields: {};
420
+ readonly summary: "Show the selected daemon’s identity summary.";
421
+ readonly effects: "Read-only.";
239
422
  };
240
423
  readonly unread: {
241
424
  readonly method: "unread";
242
425
  readonly fields: {};
426
+ readonly summary: "Show body-free unread counts and sender metadata.";
427
+ readonly effects: "Read-only; it never reveals message bodies or changes inbox status.";
243
428
  };
244
429
  readonly 'watch-notifications': {
245
430
  readonly method: "watchNotifications";
@@ -247,21 +432,36 @@ export declare const OPERATION_SPECS: {
247
432
  readonly identity: FieldSpec;
248
433
  readonly since: {
249
434
  readonly kind: "cursor";
435
+ readonly help: "Byte cursor to resume from, or tip to wait only for new events; omitted defaults to tip.";
436
+ readonly valueName: "N|tip";
250
437
  };
251
438
  };
439
+ readonly summary: "Stream body-free notification events until interrupted.";
440
+ readonly effects: "Long-running read. It does not reveal message bodies or change inbox status.";
441
+ readonly exampleInput: {
442
+ readonly identity: "BuildBot";
443
+ readonly since: "tip";
444
+ };
252
445
  };
253
446
  readonly 'fetch-file': {
254
447
  readonly method: "fetchFile";
255
448
  readonly fields: {
256
449
  readonly wire_id: FieldSpec;
257
450
  };
451
+ readonly summary: "Return one already-stored received file as base64.";
452
+ readonly effects: "Read-only; file bytes are emitted in the command result, so protect redirected output.";
453
+ readonly exampleInput: {
454
+ readonly wire_id: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
455
+ };
258
456
  };
259
457
  };
260
458
  export type OperationName = keyof typeof OPERATION_SPECS;
261
459
  export declare const operationNames: () => OperationName[];
262
460
  export declare const isOperationName: (value: string) => value is OperationName;
263
461
  export declare function parseOperationJson(raw: string | undefined, operation: OperationName): Record<string, unknown>;
462
+ export declare const fieldFlag: (name: string) => string;
463
+ export declare function operationSpec(operation: OperationName): OperationSpec;
264
464
  export declare function operationValueFlags(operation: OperationName): Set<string>;
465
+ export declare function operationEmptyValueFlags(operation: OperationName): Set<string>;
265
466
  export declare function operationInputFromFlags(operation: OperationName, values: Record<string, string>, defaults?: Record<string, unknown>): Record<string, unknown>;
266
467
  export declare function invokeOperation(client: OursClient, operation: OperationName, input: Record<string, unknown>): Promise<unknown>;
267
- export {};
@@ -1,19 +1,25 @@
1
1
  import {
2
2
  OPERATION_SPECS,
3
+ fieldFlag,
3
4
  invokeOperation,
4
5
  isOperationName,
6
+ operationEmptyValueFlags,
5
7
  operationInputFromFlags,
6
8
  operationNames,
9
+ operationSpec,
7
10
  operationValueFlags,
8
11
  parseOperationJson
9
- } from "./chunk-IUZOQT4D.js";
10
- import "./chunk-AXXFFER2.js";
12
+ } from "./chunk-QRNZFPNJ.js";
13
+ import "./chunk-6NFATG6P.js";
11
14
  export {
12
15
  OPERATION_SPECS,
16
+ fieldFlag,
13
17
  invokeOperation,
14
18
  isOperationName,
19
+ operationEmptyValueFlags,
15
20
  operationInputFromFlags,
16
21
  operationNames,
22
+ operationSpec,
17
23
  operationValueFlags,
18
24
  parseOperationJson
19
25
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Transport-neutral operator CLI for the ours shared daemon",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",
@@ -1,152 +0,0 @@
1
- import {
2
- CliUsageError,
3
- parseBoolean
4
- } from "./chunk-AXXFFER2.js";
5
-
6
- // src/operations.ts
7
- var str = (required = false, values) => ({ kind: "string", required, values });
8
- var bool = (required = false) => ({ kind: "boolean", required });
9
- var integer = (required = false, positive = false) => ({ kind: "integer", required, positive });
10
- var strings = (required = false) => ({ kind: "string[]", required });
11
- var integers = (required = false) => ({ kind: "integer[]", required });
12
- var none = {};
13
- var OPERATION_SPECS = {
14
- "create-identity": { method: "createIdentity", fields: { name: str(true), bio: str(true), exposeLocal: bool(true), localAutoAccept: bool(true) } },
15
- "create-temporary-identity": { method: "createTemporaryIdentity", fields: { name: str(), bio: str(true), exposeLocal: bool(true), localAutoAccept: bool(true) } },
16
- "close-temporary-identity": { method: "closeTemporaryIdentityOp", fields: { name: str() } },
17
- "create-root-identity": { method: "createRootIdentity", fields: { name: str(true), bio: str(true), exposeLocal: bool(true), localAutoAccept: bool(true), skipIfRootExists: bool(true) } },
18
- "define-local-identity-file": { method: "defineLocalIdentityFile", fields: { name: str(true), path: str(true), force: bool(true), exposeLocal: bool(true), localAutoAccept: bool(true), overwrite: bool(true) } },
19
- "choose-identity": { method: "chooseIdentity", fields: { name: str(true), force: bool(true) } },
20
- "list-identities": { method: "listIdentities", fields: none },
21
- "current-identity": { method: "currentIdentity", fields: none },
22
- "remove-identity": { method: "removeIdentity", fields: { name: str(true) } },
23
- "release-lease": { method: "releaseLease", fields: none },
24
- "generate-invite": { method: "generateInvite", fields: { name: str(), mode: str(false, ["one_time", "public"]) } },
25
- "list-invites": { method: "listInvites", fields: none },
26
- "revoke-invite": { method: "revokeInvite", fields: { invite_id: str(true) } },
27
- "add-contact": { method: "addContact", fields: { invite: str(true), name: str() } },
28
- "list-contacts": { method: "listContacts", fields: none },
29
- "list-local-contact-book": { method: "listLocalContactBook", fields: none },
30
- "set-local-book-policy": {
31
- method: "setLocalBookPolicy",
32
- fields: { expose: bool(), auto_accept: bool() },
33
- refine: (a) => {
34
- if (a.expose === void 0 && a.auto_accept === void 0) throw new CliUsageError("set-local-book-policy requires expose and/or auto_accept");
35
- }
36
- },
37
- "remove-contact": { method: "removeContact", fields: { contact: str(true) } },
38
- "rename-contact": { method: "renameContact", fields: { contact: str(true), name: str(true) } },
39
- "respond-to-introduction": { method: "respondToIntroduction", fields: { contact: str(true), action: str(true, ["approve", "reject"]) } },
40
- "set-bio": { method: "setBio", fields: { bio: str(true) } },
41
- "set-persona": { method: "setPersona", fields: { persona: str(true) } },
42
- "send-message": { method: "sendMessage", fields: { contact: str(true), text: str(true), reply_to_wire_id: str(), reply_to_sentence: integer(false, true) } },
43
- "send-file": {
44
- method: "sendFile",
45
- fields: { contact: str(true), path: str(), data_base64: str(), filename: str(), mime: str(), reply_to_wire_id: str(), reply_to_sentence: integer(false, true) },
46
- refine: (a) => {
47
- if (a.path === void 0 === (a.data_base64 === void 0)) throw new CliUsageError("send-file requires exactly one of path or data_base64");
48
- if (a.data_base64 !== void 0 && a.filename === void 0) throw new CliUsageError("send-file requires filename with data_base64");
49
- }
50
- },
51
- "list-incoming-messages": { method: "listIncomingMessages", fields: none },
52
- "get-messages": { method: "getMessages", fields: none },
53
- "defer-messages": { method: "deferMessages", fields: { msg_ids: integers(true) } },
54
- "defer-files": { method: "deferFiles", fields: { file_ids: integers(true) } },
55
- "get-conversation": { method: "getConversation", fields: { contact: str(true) } },
56
- "get-receipts": { method: "getReceipts", fields: { contact: str(true) } },
57
- "mark-read": { method: "markRead", fields: { contact: str(true) } },
58
- "set-conversation-policy": { method: "setConversationPolicy", fields: { keep_history: bool(true) } },
59
- "list-incoming-files": { method: "listIncomingFiles", fields: none },
60
- "get-files": { method: "getFiles", fields: { wire_ids: strings() } },
61
- version: { method: "version", fields: none },
62
- "state-dir": { method: "stateDir", fields: none },
63
- identities: { method: "identities", fields: none },
64
- unread: { method: "unread", fields: none },
65
- "watch-notifications": { method: "watchNotifications", fields: { identity: str(true), since: { kind: "cursor" } } },
66
- "fetch-file": { method: "fetchFile", fields: { wire_id: str(true) } }
67
- };
68
- var operationNames = () => Object.keys(OPERATION_SPECS).sort();
69
- var isOperationName = (value) => Object.prototype.hasOwnProperty.call(OPERATION_SPECS, value);
70
- function validateInput(input, operation) {
71
- const spec = OPERATION_SPECS[operation];
72
- for (const key of Object.keys(input)) {
73
- if (!Object.prototype.hasOwnProperty.call(spec.fields, key)) throw new CliUsageError(`${operation}: unknown input field ${JSON.stringify(key)}`);
74
- }
75
- for (const [name, field] of Object.entries(spec.fields)) {
76
- const item = input[name];
77
- if (item === void 0) {
78
- if (field.required) throw new CliUsageError(`${operation}: missing required input field ${JSON.stringify(name)}`);
79
- continue;
80
- }
81
- let valid = false;
82
- if (field.kind === "string") valid = typeof item === "string" && (field.values === void 0 || field.values.includes(item));
83
- else if (field.kind === "boolean") valid = typeof item === "boolean";
84
- else if (field.kind === "integer") valid = Number.isSafeInteger(item) && (!field.positive || Number(item) >= 1);
85
- else if (field.kind === "string[]") valid = Array.isArray(item) && item.every((v) => typeof v === "string");
86
- else if (field.kind === "integer[]") valid = Array.isArray(item) && item.every((v) => Number.isSafeInteger(v));
87
- else valid = item === "tip" || Number.isSafeInteger(item) && Number(item) >= 0;
88
- if (!valid) {
89
- const expected = field.values ? field.values.map((value) => JSON.stringify(value)).join(" or ") : field.kind;
90
- throw new CliUsageError(`${operation}: input field ${JSON.stringify(name)} must be ${expected}`);
91
- }
92
- }
93
- spec.refine?.(input);
94
- return input;
95
- }
96
- function parseOperationJson(raw, operation) {
97
- let value = {};
98
- if (raw !== void 0) {
99
- try {
100
- value = JSON.parse(raw);
101
- } catch (error) {
102
- throw new CliUsageError(`--input is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
103
- }
104
- }
105
- if (value === null || typeof value !== "object" || Array.isArray(value)) throw new CliUsageError("--input must be a JSON object");
106
- return validateInput(value, operation);
107
- }
108
- var fieldFlag = (name) => `--${name.replaceAll("_", "-").replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}`;
109
- function operationValueFlags(operation) {
110
- return new Set(Object.keys(OPERATION_SPECS[operation].fields).map(fieldFlag));
111
- }
112
- function operationInputFromFlags(operation, values, defaults = {}) {
113
- const input = { ...defaults };
114
- for (const [name, field] of Object.entries(OPERATION_SPECS[operation].fields)) {
115
- const flag = fieldFlag(name);
116
- const raw = values[flag];
117
- if (raw === void 0) continue;
118
- if (field.kind === "boolean") input[name] = parseBoolean(raw, flag);
119
- else if (field.kind === "integer") input[name] = parseCliInteger(raw, flag, field.positive === true);
120
- else if (field.kind === "integer[]") input[name] = raw === "" ? [] : raw.split(",").map((v) => parseCliInteger(v, flag));
121
- else if (field.kind === "string[]") input[name] = raw === "" ? [] : raw.split(",");
122
- else if (field.kind === "cursor") input[name] = raw === "tip" ? "tip" : parseCliInteger(raw, flag);
123
- else input[name] = raw;
124
- }
125
- return validateInput(input, operation);
126
- }
127
- function parseCliInteger(raw, flag, positive = false) {
128
- if (!/^[0-9]+$/.test(raw)) throw new CliUsageError(`${flag} must be an integer`);
129
- const value = Number(raw);
130
- if (!Number.isSafeInteger(value) || positive && value < 1) throw new CliUsageError(`${flag} is outside the supported integer range`);
131
- return value;
132
- }
133
- async function invokeOperation(client, operation, input) {
134
- if (operation === "watch-notifications") throw new Error("watch-notifications is a streaming operation");
135
- if (operation === "fetch-file") {
136
- const bytes = await client.fetchFile(input.wire_id);
137
- return { wire_id: input.wire_id, bytes: bytes.byteLength, data_base64: Buffer.from(bytes).toString("base64") };
138
- }
139
- const spec = OPERATION_SPECS[operation];
140
- const fn = client[spec.method];
141
- return Object.keys(spec.fields).length === 0 ? fn.call(client) : fn.call(client, input);
142
- }
143
-
144
- export {
145
- OPERATION_SPECS,
146
- operationNames,
147
- isOperationName,
148
- parseOperationJson,
149
- operationValueFlags,
150
- operationInputFromFlags,
151
- invokeOperation
152
- };