@peerbit/server 1.1.2 → 3.0.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 (54) hide show
  1. package/lib/esm/cli.js +518 -144
  2. package/lib/esm/cli.js.map +1 -1
  3. package/lib/esm/client.d.ts +11 -3
  4. package/lib/esm/client.js +61 -27
  5. package/lib/esm/client.js.map +1 -1
  6. package/lib/esm/config.d.ts +8 -5
  7. package/lib/esm/config.js +44 -19
  8. package/lib/esm/config.js.map +1 -1
  9. package/lib/esm/peerbit.d.ts +4 -0
  10. package/lib/esm/peerbit.js +15 -2
  11. package/lib/esm/peerbit.js.map +1 -1
  12. package/lib/esm/remotes.browser.d.ts +0 -0
  13. package/lib/esm/remotes.browser.js +3 -0
  14. package/lib/esm/remotes.browser.js.map +1 -0
  15. package/lib/esm/remotes.d.ts +16 -0
  16. package/lib/esm/remotes.js +51 -0
  17. package/lib/esm/remotes.js.map +1 -0
  18. package/lib/esm/routes.d.ts +3 -0
  19. package/lib/esm/routes.js +3 -0
  20. package/lib/esm/routes.js.map +1 -1
  21. package/lib/esm/server.d.ts +14 -4
  22. package/lib/esm/server.js +297 -144
  23. package/lib/esm/server.js.map +1 -1
  24. package/lib/esm/session.d.ts +19 -0
  25. package/lib/esm/session.js +49 -0
  26. package/lib/esm/session.js.map +1 -0
  27. package/lib/esm/signes-request.d.ts +5 -0
  28. package/lib/esm/signes-request.js +54 -0
  29. package/lib/esm/signes-request.js.map +1 -0
  30. package/lib/esm/trust.browser.d.ts +0 -0
  31. package/lib/esm/trust.browser.js +3 -0
  32. package/lib/esm/trust.browser.js.map +1 -0
  33. package/lib/esm/trust.d.ts +9 -0
  34. package/lib/esm/trust.js +36 -0
  35. package/lib/esm/trust.js.map +1 -0
  36. package/lib/esm/types.d.ts +10 -0
  37. package/lib/ui/assets/index-cac7195d.js +77 -0
  38. package/lib/ui/index.html +1 -1
  39. package/package.json +9 -5
  40. package/src/cli.ts +705 -271
  41. package/src/client.ts +105 -30
  42. package/src/config.ts +52 -25
  43. package/src/peerbit.ts +27 -3
  44. package/src/remotes.browser.ts +1 -0
  45. package/src/remotes.ts +63 -0
  46. package/src/routes.ts +3 -1
  47. package/src/server.ts +381 -190
  48. package/src/session.ts +69 -0
  49. package/src/signes-request.ts +84 -0
  50. package/src/trust.browser.ts +1 -0
  51. package/src/trust.ts +39 -0
  52. package/src/types.ts +13 -0
  53. package/lib/ui/assets/config.browser-4ed993c7.js +0 -1
  54. package/lib/ui/assets/index-a8188422.js +0 -53
package/src/cli.ts CHANGED
@@ -6,10 +6,35 @@ import {
6
6
  } from "./domain.js";
7
7
  import { startServerWithNode } from "./server.js";
8
8
  import { createRecord } from "./aws.js";
9
- import { getHomeConfigDir } from "./config.js";
9
+ import {
10
+ getHomeConfigDir,
11
+ getKeypair,
12
+ getPackageName,
13
+ getRemotesPath,
14
+ } from "./config.js";
10
15
  import chalk from "chalk";
11
16
  import { client } from "./client.js";
12
- import { StartProgram } from "./types.js";
17
+ import { InstallDependency, StartProgram } from "./types.js";
18
+ import { exit } from "process";
19
+ import yargs from "yargs";
20
+ import readline from "readline";
21
+ import fs from "fs";
22
+ import path from "path";
23
+ import { toBase64 } from "@peerbit/crypto";
24
+ import { Remotes } from "./remotes.js";
25
+ import { peerIdFromString } from "@libp2p/peer-id";
26
+
27
+ const padString = function (string: string, padding: number, padChar = " ") {
28
+ const val = string.valueOf();
29
+ if (Math.abs(padding) <= val.length) {
30
+ return val;
31
+ }
32
+ const m = Math.max(Math.abs(padding) - string.length || 0, 0);
33
+ const pad = Array(m + 1).join(String(padChar).charAt(0));
34
+ // var pad = String(c || ' ').charAt(0).repeat(Math.abs(n) - this.length);
35
+ return padding < 0 ? pad + val : val + pad;
36
+ // return (n < 0) ? val + pad : pad + val;
37
+ };
13
38
 
14
39
  export const cli = async (args?: string[]) => {
15
40
  const yargs = await import("yargs");
@@ -24,19 +49,40 @@ export const cli = async (args?: string[]) => {
24
49
  .command({
25
50
  command: "start",
26
51
  describe: "Start node",
27
- builder: {
28
- directory: {
29
- describe: "Directory for all data created by the node",
30
- defaultDescription: "~.peerbit",
31
- type: "string",
32
- default: await getHomeConfigDir(),
33
- },
34
-
35
- bootstrap: {
36
- describe: "Whether to connect to bootstap nodes on startup",
37
- type: "boolean",
38
- default: false,
39
- },
52
+ builder: (yargs: yargs.Argv) => {
53
+ yargs
54
+ .option("directory", {
55
+ describe: "Peerbit directory",
56
+ defaultDescription: "~.peerbit",
57
+ type: "string",
58
+ alias: "d",
59
+ default: getHomeConfigDir(),
60
+ })
61
+ .option("bootstrap", {
62
+ describe: "Whether to connect to bootstap nodes on startup",
63
+ type: "boolean",
64
+ default: false,
65
+ })
66
+ .option("reset", {
67
+ describe:
68
+ "If true, then programs opened during last session will not be opened",
69
+ type: "boolean",
70
+ default: false,
71
+ alias: "r",
72
+ })
73
+ .option("port-api", {
74
+ describe:
75
+ "Set API server port. Only modify this when testing locally, since NGINX config depends on the default value",
76
+ type: "number",
77
+ default: undefined,
78
+ })
79
+ .option("port-node", {
80
+ describe:
81
+ "Set Libp2p listen port. Only modify this when testing locally, since NGINX config depends on the default value",
82
+ type: "number",
83
+ default: undefined,
84
+ });
85
+ return yargs;
40
86
  },
41
87
  handler: async (args) => {
42
88
  await startServerWithNode({
@@ -44,306 +90,694 @@ export const cli = async (args?: string[]) => {
44
90
  domain: await loadConfig().then((config) =>
45
91
  config ? getDomainFromConfig(config) : undefined
46
92
  ),
93
+ ports: { api: args["port-api"], node: args["port-node"] },
47
94
  bootstrap: args.bootstrap,
95
+ newSession: args.reset,
48
96
  });
49
97
  },
50
98
  })
51
- .command("domain", "Setup a domain and certificate", (yargs) => {
52
- yargs
53
- .command({
54
- command: "test",
55
- describe:
56
- "Setup a testing domain with SSL (no guarantess on how long the domain will be available)",
57
- builder: {
58
- email: {
59
- describe: "Email for Lets encrypt autorenewal messages",
60
- type: "string",
61
- demandOption: true,
62
- },
63
- outdir: {
64
- describe: "Output path for Nginx config",
65
- type: "string",
66
- alias: "o",
67
- },
68
- wait: {
69
- alias: "w",
70
- describe: "Wait for setup to succeed (or fail)",
71
- type: "boolean",
72
- default: false,
73
- },
74
- },
75
- handler: async (args) => {
76
- const domain = await createTestDomain();
77
- await startCertbot(domain, args.email, args.outdir, args.wait);
78
- const { exit } = await import("process");
79
- exit();
80
- },
81
- })
82
- .command({
83
- command: "aws",
84
- describe:
85
- "Setup a domain with an AWS account. You either have to setup you AWS credentials in the .aws folder, or pass the credentials in the cli",
86
- builder: {
87
- domain: {
88
- describe: "domain, e.g. abc.example.com, example.com",
89
- alias: "d",
90
- type: "string",
91
- demandOption: true,
92
- },
93
- hostedZoneId: {
94
- describe: 'The id of the hosted zone "HostedZoneId"',
95
- alias: "hz",
96
- type: "string",
97
- require: true,
98
- },
99
- accessKeyId: {
100
- describe: "Access key id of the AWS user",
101
- alias: "ak",
102
- type: "string",
103
- },
104
- region: {
105
- describe: "AWS region",
106
- alias: "r",
107
- type: "string",
108
- },
109
- secretAccessKey: {
110
- describe: "Secret key id of the AWS user",
111
- alias: "sk",
112
- type: "string",
113
- },
114
- email: {
115
- describe: "Email for Lets encrypt auto-renewal messages",
116
- type: "string",
117
- demandOption: true,
99
+ .command({
100
+ command: "id",
101
+ describe: "Get peer id",
102
+ builder: (yargs: yargs.Argv) => {
103
+ yargs.option("directory", {
104
+ describe: "Peerbit directory",
105
+ defaultDescription: "~.peerbit",
106
+ type: "string",
107
+ alias: "d",
108
+ default: getHomeConfigDir(),
109
+ });
110
+ return yargs;
111
+ },
112
+ handler: async (args) => {
113
+ const kp = await getKeypair(args.directory);
114
+ console.log((await kp.toPeerId()).toString());
115
+ },
116
+ })
117
+ .command(
118
+ "domain",
119
+ "Setup a domain and certificate for this node",
120
+ (yargs) => {
121
+ yargs
122
+ .command({
123
+ command: "test",
124
+ describe:
125
+ "Setup a testing domain with SSL (no guarantess on how long the domain will be available)",
126
+ builder: {
127
+ email: {
128
+ describe: "Email for Lets encrypt autorenewal messages",
129
+ type: "string",
130
+ demandOption: true,
131
+ },
132
+ outdir: {
133
+ describe: "Output path for Nginx config",
134
+ type: "string",
135
+ alias: "o",
136
+ },
137
+ wait: {
138
+ alias: "w",
139
+ describe: "Wait for setup to succeed (or fail)",
140
+ type: "boolean",
141
+ default: false,
142
+ },
118
143
  },
119
- outdir: {
120
- describe: "Output path for Nginx config",
121
- type: "string",
122
- alias: "o",
144
+ handler: async (args) => {
145
+ const domain = await createTestDomain();
146
+ await startCertbot(domain, args.email, args.outdir, args.wait);
147
+ exit();
123
148
  },
124
- wait: {
125
- alias: "w",
126
- describe: "Wait for setup to succeed (or fail)",
127
- type: "boolean",
128
- default: false,
149
+ })
150
+ .command({
151
+ command: "aws",
152
+ describe:
153
+ "Setup a domain with an AWS account. You either have to setup you AWS credentials in the .aws folder, or pass the credentials in the cli",
154
+ builder: {
155
+ domain: {
156
+ describe: "domain, e.g. abc.example.com, example.com",
157
+ alias: "d",
158
+ type: "string",
159
+ demandOption: true,
160
+ },
161
+ hostedZoneId: {
162
+ describe: 'The id of the hosted zone "HostedZoneId"',
163
+ alias: "hz",
164
+ type: "string",
165
+ require: true,
166
+ },
167
+ accessKeyId: {
168
+ describe: "Access key id of the AWS user",
169
+ alias: "ak",
170
+ type: "string",
171
+ },
172
+ region: {
173
+ describe: "AWS region",
174
+ alias: "r",
175
+ type: "string",
176
+ },
177
+ secretAccessKey: {
178
+ describe: "Secret key id of the AWS user",
179
+ alias: "sk",
180
+ type: "string",
181
+ },
182
+ email: {
183
+ describe: "Email for Lets encrypt auto-renewal messages",
184
+ type: "string",
185
+ demandOption: true,
186
+ },
187
+ outdir: {
188
+ describe: "Output path for Nginx config",
189
+ type: "string",
190
+ alias: "o",
191
+ },
192
+ wait: {
193
+ alias: "w",
194
+ describe: "Wait for setup to succeed (or fail)",
195
+ type: "boolean",
196
+ default: false,
197
+ },
129
198
  },
130
- },
131
- handler: async (args) => {
132
- if (
133
- !!args.accessKeyId !== !!args.secretAccessKey ||
134
- !!args.region !== !!args.secretAccessKey
135
- ) {
136
- throw new Error(
137
- "Expecting either all 'accessKeyId', 'region' and 'secretAccessKey' to be provided or none"
199
+ handler: async (args) => {
200
+ if (
201
+ !!args.accessKeyId !== !!args.secretAccessKey ||
202
+ !!args.region !== !!args.secretAccessKey
203
+ ) {
204
+ throw new Error(
205
+ "Expecting either all 'accessKeyId', 'region' and 'secretAccessKey' to be provided or none"
206
+ );
207
+ }
208
+ await createRecord({
209
+ domain: args.domain,
210
+ hostedZoneId: args.hostedZoneId,
211
+ region: args.region,
212
+ credentials: args.accessKeyId
213
+ ? {
214
+ accessKeyId: args.accessKeyId,
215
+ secretAccessKey: args.secretAccessKey,
216
+ }
217
+ : undefined,
218
+ });
219
+ await startCertbot(
220
+ args.domain,
221
+ args.email,
222
+ args.outdir,
223
+ args.wait
138
224
  );
139
- }
140
- await createRecord({
141
- domain: args.domain,
142
- hostedZoneId: args.hostedZoneId,
143
- region: args.region,
144
- credentials: args.accessKeyId
145
- ? {
146
- accessKeyId: args.accessKeyId,
147
- secretAccessKey: args.secretAccessKey,
148
- }
149
- : undefined,
150
- });
151
- await startCertbot(args.domain, args.email, args.outdir, args.wait);
152
- const { exit } = await import("process");
153
- exit();
154
- },
155
- })
156
- .strict()
157
- .demandCommand();
158
- })
159
- .command("network", "Manage network", (yargs) => {
160
- yargs
161
- .command({
162
- command: "bootstrap",
163
- describe: "Connect to bootstrap nodes",
164
- handler: async () => {
165
- const c = await client();
166
- await c.network.bootstrap();
167
- },
168
- })
169
- .strict()
170
- .demandCommand();
171
- })
172
-
173
- .command("topic", "Manage topics the node is listening to", (yargs) => {
174
- yargs
225
+ exit();
226
+ },
227
+ })
228
+ .strict()
229
+ .demandCommand();
230
+ }
231
+ )
232
+ .command("remote", "Handle remote nodes", (innerYargs) => {
233
+ innerYargs
175
234
  .command({
176
235
  command: "list",
177
236
  aliases: "ls",
178
- describe: "List all topics",
179
- builder: (yargs: any) => {
180
- yargs.option("replicate", {
181
- type: "boolean",
182
- describe: "Replicate data on this topic",
183
- alias: "r",
184
- default: false,
237
+ describe: "List remotes",
238
+ builder: (yargs: yargs.Argv) => {
239
+ yargs.option("directory", {
240
+ describe: "Peerbit directory",
241
+ defaultDescription: "~.peerbit",
242
+ type: "string",
243
+ alias: "d",
244
+ default: getHomeConfigDir(),
185
245
  });
246
+
186
247
  return yargs;
187
248
  },
188
249
  handler: async (args) => {
189
- /* const c = await client();
190
- const topics = await c.topics.get(args.replicate);
191
- if (topics?.length > 0) {
192
- console.log("Topic (" + topics.length + "):");
193
- for (const t of topics) {
194
- console.log(t);
250
+ const remotes = new Remotes(getRemotesPath(args.directory));
251
+ const allRemotes = await remotes.all();
252
+ const maxNameLength = allRemotes
253
+ .map((x) => x.name.length)
254
+ .reduce((prev, c, i) => {
255
+ return Math.max(prev, c);
256
+ }, 0);
257
+ const all = await remotes.all();
258
+ if (all.length > 0) {
259
+ for (const remote of all) {
260
+ console.log(
261
+ padString(remote.name, maxNameLength + 10),
262
+ remote.address
263
+ );
195
264
  }
196
265
  } else {
197
- console.log("Not subscribed to any topics");
198
- } */
199
- console.error("Not implemented");
266
+ console.log("No remotes found!");
267
+ }
200
268
  },
201
269
  })
202
- .strict()
203
- .demandCommand();
204
- return yargs;
205
- })
206
- .command("program", "Manage programs", (yargs) => {
207
- yargs
208
270
  .command({
209
- command: "status <address>",
210
- describe: "Is a program open",
211
- builder: (yargs: any) => {
212
- yargs.positional("address", {
213
- type: "string",
214
- describe: "Program address",
215
- demandOption: true,
216
- });
271
+ command: "add <name> <address>",
272
+ describe: "Add remote",
273
+ builder: (yargs: yargs.Argv) => {
274
+ yargs
275
+ .positional("address", {
276
+ type: "string",
277
+ describe: "Remote name",
278
+ demandOption: true,
279
+ })
280
+ .positional("name", {
281
+ type: "string",
282
+ describe: "Remote address",
283
+ demandOption: true,
284
+ })
285
+ .option("directory", {
286
+ describe: "Peerbit directory",
287
+ defaultDescription: "~.peerbit",
288
+ type: "string",
289
+ alias: "d",
290
+ default: getHomeConfigDir(),
291
+ });
292
+
217
293
  return yargs;
218
294
  },
219
-
220
295
  handler: async (args) => {
221
- const c = await client();
222
- const program = await c.program.has(args.address);
223
- if (!program) {
224
- console.log(chalk.red("Closed"));
225
- } else {
226
- console.log(chalk.green("Open"));
296
+ if (args.name === "localhost") {
297
+ throw new Error("Remote can not be named 'localhost'");
298
+ }
299
+ const api = await client(
300
+ await getKeypair(args.directory),
301
+ args.address
302
+ );
303
+ try {
304
+ await api.program.list();
305
+ } catch (error) {
306
+ throw new Error("Failed to add remote: " + error?.toString());
227
307
  }
308
+ if (!fs.existsSync(args.directory)) {
309
+ fs.mkdirSync(args.directory, { recursive: true });
310
+ }
311
+ const remotes = new Remotes(getRemotesPath(args.directory));
312
+ remotes.add(args.name, args.address);
228
313
  },
229
314
  })
230
315
  .command({
231
- command: "drop <address>",
232
- describe: "Drop a program",
233
- builder: (yargs: any) => {
234
- yargs.positional("address", {
235
- type: "string",
236
- describe: "Program address",
237
- demandOption: true,
238
- });
316
+ command: "remove <name>",
317
+ describe: "Remove a remote",
318
+ builder: (yargs: yargs.Argv) => {
319
+ yargs
320
+
321
+ .positional("name", {
322
+ type: "string",
323
+ describe: "Remote address",
324
+ demandOption: true,
325
+ })
326
+ .option("directory", {
327
+ describe: "Peerbit directory",
328
+ defaultDescription: "~.peerbit",
329
+ type: "string",
330
+ alias: "d",
331
+ default: getHomeConfigDir(),
332
+ });
333
+
239
334
  return yargs;
240
335
  },
241
-
242
336
  handler: async (args) => {
243
- const c = await client();
244
- await c.program.drop(args.address);
337
+ const remotes = new Remotes(getRemotesPath(args.directory));
338
+ if (remotes.remove(args.name)) {
339
+ console.log(
340
+ chalk.green("Removed remote with name: " + args.name)
341
+ );
342
+ } else {
343
+ console.log(
344
+ chalk.red("Did not find any remote with name: " + args.name)
345
+ );
346
+ }
245
347
  },
246
348
  })
247
349
  .command({
248
- command: "close <address>",
249
- describe: "Close a program",
250
- builder: (yargs: any) => {
251
- yargs.positional("address", {
252
- type: "string",
253
- describe: "Program address",
254
- demandOption: true,
255
- });
350
+ command: "connect [name...]",
351
+ describe: "Connect to remote(s)",
352
+ builder: (yargs: yargs.Argv) => {
353
+ yargs
354
+ .positional("name", {
355
+ type: "string",
356
+ describe: "Remote name",
357
+ default: "localhost",
358
+ demandOption: false,
359
+ array: true,
360
+ })
361
+ .option("directory", {
362
+ describe: "Peerbit directory",
363
+ defaultDescription: "~.peerbit",
364
+ type: "string",
365
+ alias: "d",
366
+ default: getHomeConfigDir(),
367
+ });
256
368
  return yargs;
257
369
  },
370
+ handler: async (connectArgs) => {
371
+ const names = connectArgs.name;
372
+ const apis: {
373
+ log: (string: string) => void;
374
+ name: string;
375
+ api: Awaited<ReturnType<typeof client>>;
376
+ }[] = [];
377
+ console.log(getRemotesPath(connectArgs.directory));
378
+ const config = await import("./config.js");
379
+ const keypair = await config.getKeypair(connectArgs.directory);
258
380
 
259
- handler: async (args) => {
260
- const c = await client();
261
- await c.program.close(args.address);
262
- },
263
- })
264
- .command({
265
- command: "list",
266
- describe: "List all running programs",
267
- aliases: "ls",
268
- handler: async (args) => {
269
- const c = await client();
270
- const list = await c.program.list();
381
+ if (names.length > 0) {
382
+ const remotes = new Remotes(
383
+ getRemotesPath(connectArgs.directory)
384
+ );
385
+ for (const name of names) {
386
+ if (name === "localhost") {
387
+ apis.push({
388
+ log: (string) => console.log("localhost: " + string),
389
+ name: "localhost",
390
+ api: await client(keypair),
391
+ });
392
+ } else {
393
+ const remote = remotes.getByName(name);
394
+ if (!remote) {
395
+ throw new Error("Missing remote with name: " + name);
396
+ }
397
+ let logFn: (name: string) => void;
398
+ if (names.length > 0) {
399
+ logFn = (string) => console.log(name + ": " + string);
400
+ } else {
401
+ logFn = (string) => console.log(string);
402
+ }
271
403
 
272
- console.log(`Running programs (${list.length}):`);
273
- list.forEach((p) => {
274
- console.log(chalk.green(p));
275
- });
276
- },
277
- })
404
+ apis.push({
405
+ log: logFn,
406
+ name,
407
+ api: await client(keypair, remote.address),
408
+ });
409
+ }
410
+ }
411
+ }
278
412
 
279
- .command({
280
- command: "open [program]",
281
- describe: "Open program",
282
- builder: (yargs: any) => {
283
- yargs.positional("program", {
284
- type: "string",
285
- describe: "Identifier",
286
- demandOption: true,
287
- });
288
- yargs.option("base64", {
289
- type: "string",
290
- describe: "Base64 encoded serialized",
291
- alias: "b",
413
+ // try if authenticated
414
+ for (const api of apis) {
415
+ try {
416
+ await api.api.program.list();
417
+ } catch (error) {
418
+ throw new Error(
419
+ `Failed to connect to '${api.name}': ${error?.toString()}`
420
+ );
421
+ }
422
+ }
423
+ const capi = () =>
424
+ yargs
425
+ .default()
426
+ .command("peer", "Peer info", (yargs) => {
427
+ yargs
428
+ .command({
429
+ command: "id",
430
+ describe: "Get peer id",
431
+ handler: async (args) => {
432
+ for (const api of apis) {
433
+ api.log((await api.api.peer.id.get()).toString());
434
+ }
435
+ },
436
+ })
437
+ .command({
438
+ command: "address",
439
+ describe: "Get addresses",
440
+ handler: async (args) => {
441
+ for (const api of apis) {
442
+ (await api.api.peer.addresses.get()).forEach((x) =>
443
+ api.log(x.toString())
444
+ );
445
+ }
446
+ },
447
+ })
448
+ .strict()
449
+ .demandCommand();
450
+ return yargs;
451
+ })
452
+ .command(
453
+ "access",
454
+ "Modify access control for this node",
455
+ (yargs) => {
456
+ yargs
457
+ .command({
458
+ command: "grant <peer-id>",
459
+ describe: "Give a peer-id admin capabilities",
460
+ builder: (yargs: yargs.Argv) => {
461
+ yargs.positional("peer-id", {
462
+ describe: "Peer id",
463
+ type: "string",
464
+ demandOption: true,
465
+ });
466
+ return yargs;
467
+ },
468
+ handler: async (args) => {
469
+ const peerId = peerIdFromString(args["peer-id"]);
470
+ for (const api of apis) {
471
+ await api.api.trust.add(peerId);
472
+ }
473
+ },
474
+ })
475
+ .command({
476
+ command: "deny <peer-id>",
477
+ describe: "Remove admin capabilities from peer-id",
478
+ builder: (yargs: yargs.Argv) => {
479
+ yargs.positional("peer-id", {
480
+ describe: "Peer id",
481
+ demandOption: true,
482
+ });
483
+ return yargs;
484
+ },
485
+ handler: async (args) => {
486
+ const peerId = peerIdFromString(args["peer-id"]);
487
+ for (const api of apis) {
488
+ await api.api.trust.remove(peerId);
489
+ }
490
+ },
491
+ })
492
+ .strict()
493
+ .demandCommand();
494
+ }
495
+ )
496
+ .command("network", "Manage network", (yargs) => {
497
+ yargs
498
+ .command({
499
+ command: "bootstrap",
500
+ describe: "Connect to bootstrap nodes",
501
+ handler: async () => {
502
+ for (const api of apis) {
503
+ await api.api.network.bootstrap();
504
+ }
505
+ },
506
+ })
507
+ .strict()
508
+ .demandCommand();
509
+ })
510
+
511
+ .command(
512
+ "topic",
513
+ "Manage topics the node is listening to",
514
+ (yargs) => {
515
+ yargs
516
+ .command({
517
+ command: "list",
518
+ aliases: "ls",
519
+ describe: "List all topics",
520
+ builder: (yargs: any) => {
521
+ yargs.option("replicate", {
522
+ type: "boolean",
523
+ describe: "Replicate data on this topic",
524
+ aliases: "r",
525
+ default: false,
526
+ });
527
+ return yargs;
528
+ },
529
+ handler: async (args) => {
530
+ /* const c = await client();
531
+ const topics = await c.topics.get(args.replicate);
532
+ if (topics?.length > 0) {
533
+ console.log("Topic (" + topics.length + "):");
534
+ for (const t of topics) {
535
+ console.log(t);
536
+ }
537
+ } else {
538
+ console.log("Not subscribed to any topics");
539
+ } */
540
+ console.error("Not implemented");
541
+ },
542
+ })
543
+ .strict()
544
+ .demandCommand();
545
+ return yargs;
546
+ }
547
+ )
548
+ .command("program", "Manage programs", (yargs) => {
549
+ yargs
550
+ .command({
551
+ command: "status <address>",
552
+ describe: "Is a program open",
553
+ builder: (yargs: any) => {
554
+ yargs.positional("address", {
555
+ type: "string",
556
+ describe: "Program address",
557
+ demandOption: true,
558
+ });
559
+ return yargs;
560
+ },
561
+
562
+ handler: async (args) => {
563
+ for (const api of apis) {
564
+ const program = await api.api.program.has(
565
+ args.address
566
+ );
567
+ if (!program) {
568
+ api.log(chalk.red("Closed"));
569
+ } else {
570
+ api.log(chalk.green("Open"));
571
+ }
572
+ }
573
+ },
574
+ })
575
+ .command({
576
+ command: "drop <address>",
577
+ describe: "Drop a program",
578
+ builder: (yargs: any) => {
579
+ yargs.positional("address", {
580
+ type: "string",
581
+ describe: "Program address",
582
+ demandOption: true,
583
+ });
584
+ return yargs;
585
+ },
586
+
587
+ handler: async (args) => {
588
+ for (const api of apis) {
589
+ try {
590
+ await api.api.program.drop(args.address);
591
+ } catch (error: any) {
592
+ api.log(
593
+ chalk.red(
594
+ `Failed to drop ${
595
+ args.address
596
+ }: ${error.toString()}`
597
+ )
598
+ );
599
+ }
600
+ }
601
+ },
602
+ })
603
+ .command({
604
+ command: "close <address>",
605
+ describe: "Close a program",
606
+ builder: (yargs: any) => {
607
+ yargs.positional("address", {
608
+ type: "string",
609
+ describe: "Program address",
610
+ demandOption: true,
611
+ });
612
+ return yargs;
613
+ },
614
+
615
+ handler: async (args) => {
616
+ for (const api of apis) {
617
+ await api.api.program.close(args.address);
618
+ }
619
+ },
620
+ })
621
+ .command({
622
+ command: "list",
623
+ describe: "List all running programs",
624
+ aliases: "ls",
625
+ handler: async (args) => {
626
+ for (const api of apis) {
627
+ const list = await api.api.program.list();
628
+ api.log(`Running programs (${list.length}):`);
629
+ list.forEach((p) => {
630
+ api.log(chalk.green(p));
631
+ });
632
+ }
633
+ },
634
+ })
635
+
636
+ .command({
637
+ command: "open [program]",
638
+ describe: "Open program",
639
+ builder: (yargs: any) => {
640
+ yargs.positional("program", {
641
+ type: "string",
642
+ describe: "Identifier",
643
+ demandOption: true,
644
+ });
645
+ yargs.option("base64", {
646
+ type: "string",
647
+ describe: "Base64 encoded serialized",
648
+ aliases: "b",
649
+ });
650
+ yargs.option("variant", {
651
+ type: "string",
652
+ describe: "Variant name",
653
+ aliases: "v",
654
+ });
655
+ return yargs;
656
+ },
657
+ handler: async (args) => {
658
+ if (!args.base64 && !args.variant) {
659
+ throw new Error(
660
+ "Either base64 or variant argument needs to be provided"
661
+ );
662
+ }
663
+ let startArg: StartProgram;
664
+ if (args.base64) {
665
+ startArg = {
666
+ base64: args.base64,
667
+ };
668
+ } else {
669
+ startArg = {
670
+ variant: args.variant,
671
+ };
672
+ }
673
+ for (const api of apis) {
674
+ const address = await api.api.program.open(startArg);
675
+ api.log("Started program with address: ");
676
+ api.log(chalk.green(address.toString()));
677
+ }
678
+ },
679
+ })
680
+ .strict()
681
+ .demandCommand();
682
+ return yargs;
683
+ })
684
+ .command({
685
+ command: "install <package-spec>",
686
+ describe: "install and import a dependency",
687
+ builder: (yargs: any) => {
688
+ yargs.positional("package-spec", {
689
+ type: "string",
690
+ describe:
691
+ "Installed dependency will be loaded with js import(...)",
692
+ demandOption: true,
693
+ });
694
+
695
+ return yargs;
696
+ },
697
+ handler: async (args) => {
698
+ // if ends with .tgz assume it is a file
699
+
700
+ let installCommand: InstallDependency;
701
+ const packageName: string = args["package-spec"];
702
+ if (packageName.endsWith(".tgz")) {
703
+ const packagePath = path.isAbsolute(packageName)
704
+ ? packageName
705
+ : path.join(process.cwd(), packageName);
706
+
707
+ const buffer = fs.readFileSync(packagePath);
708
+ const base64 = toBase64(buffer);
709
+ installCommand = {
710
+ type: "tgz",
711
+ name: await getPackageName(packageName),
712
+ base64,
713
+ };
714
+ } else {
715
+ installCommand = { type: "npm", name: packageName };
716
+ }
717
+
718
+ for (const api of apis) {
719
+ const newPrograms = await api.api.dependency.install(
720
+ installCommand
721
+ );
722
+ api.log(
723
+ `New programs available (${newPrograms.length}):`
724
+ );
725
+ newPrograms.forEach((p) => {
726
+ api.log(chalk.green(p));
727
+ });
728
+ }
729
+ },
730
+ })
731
+ .command({
732
+ command: "restart",
733
+ describe: "Restart the server",
734
+ handler: async () => {
735
+ for (const api of apis) {
736
+ await api.api.restart();
737
+ }
738
+ },
739
+ })
740
+ .command({
741
+ command: "terminate",
742
+ describe: "Terminate the server",
743
+ handler: async () => {
744
+ for (const api of apis) {
745
+ await api.api.terminate();
746
+ }
747
+ },
748
+ })
749
+ .help()
750
+ .strict()
751
+ .scriptName("")
752
+ .demandCommand()
753
+ .showHelpOnFail(true)
754
+ .exitProcess(false);
755
+ const rl = readline.createInterface({
756
+ input: process.stdin,
757
+ output: process.stdout,
758
+ terminal: true,
759
+ historySize: 100,
292
760
  });
293
- yargs.option("variant", {
294
- type: "string",
295
- describe: "Variant name",
296
- alias: "v",
761
+ console.log(chalk.green("Connected"));
762
+ console.log("Write 'help' to show commands.\n");
763
+ const first = true;
764
+ rl.prompt(false);
765
+ rl.on("line", async (cargs) => {
766
+ const cmds = capi();
767
+ try {
768
+ await cmds.parse(cargs);
769
+ } catch (error: any) {
770
+ /* console.log(chalk.red("Error parsing command: " + cargs))*/
771
+ }
772
+ rl.prompt(true);
297
773
  });
298
- return yargs;
299
- },
300
- handler: async (args) => {
301
- const c = await client();
302
- if (!args.base64 && !args.variant) {
303
- throw new Error(
304
- "Either base64 or variant argument needs to be provided"
305
- );
306
- }
307
- let startArg: StartProgram;
308
- if (args.base64) {
309
- startArg = {
310
- base64: args.base64,
311
- };
312
- } else {
313
- startArg = {
314
- variant: args.variant,
315
- };
316
- }
317
- const address = await c.program.open(startArg);
318
- console.log("Started program with address: ");
319
- console.log(chalk.green(address.toString()));
320
774
  },
321
775
  })
776
+
777
+ .help()
322
778
  .strict()
323
779
  .demandCommand();
324
- return yargs;
325
- })
326
- .command({
327
- command: "install <package-spec>",
328
- describe: "install and import a dependency",
329
- builder: (yargs: any) => {
330
- yargs.positional("package-spec", {
331
- type: "string",
332
- describe: "Installed dependency will be loaded with js import(...)",
333
- demandOption: true,
334
- });
335
-
336
- return yargs;
337
- },
338
- handler: async (args) => {
339
- const c = await client();
340
- const newPrograms = await c.dependency.install(args["package-spec"]);
341
-
342
- console.log(`New programs available (${newPrograms.length}):`);
343
- newPrograms.forEach((p) => {
344
- console.log(chalk.green(p));
345
- });
346
- },
780
+ return innerYargs;
347
781
  })
348
782
  .help()
349
783
  .strict()