@bitsocial/bitsocial-cli 0.19.39
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/LICENSE +674 -0
- package/README.md +706 -0
- package/bin/dev +20 -0
- package/bin/dev.cmd +3 -0
- package/bin/postinstall.js +125 -0
- package/bin/run +13 -0
- package/bin/run.cmd +3 -0
- package/dist/challenge-packages/challenge-utils.d.ts +24 -0
- package/dist/challenge-packages/challenge-utils.js +304 -0
- package/dist/cli/base-command.d.ts +11 -0
- package/dist/cli/base-command.js +45 -0
- package/dist/cli/commands/challenge/install.d.ts +12 -0
- package/dist/cli/commands/challenge/install.js +131 -0
- package/dist/cli/commands/challenge/list.d.ts +10 -0
- package/dist/cli/commands/challenge/list.js +37 -0
- package/dist/cli/commands/challenge/remove.d.ts +12 -0
- package/dist/cli/commands/challenge/remove.js +60 -0
- package/dist/cli/commands/community/create.d.ts +12 -0
- package/dist/cli/commands/community/create.js +54 -0
- package/dist/cli/commands/community/delete.d.ts +10 -0
- package/dist/cli/commands/community/delete.js +44 -0
- package/dist/cli/commands/community/edit.d.ts +12 -0
- package/dist/cli/commands/community/edit.js +74 -0
- package/dist/cli/commands/community/get.d.ts +9 -0
- package/dist/cli/commands/community/get.js +32 -0
- package/dist/cli/commands/community/list.d.ts +9 -0
- package/dist/cli/commands/community/list.js +30 -0
- package/dist/cli/commands/community/start.d.ts +13 -0
- package/dist/cli/commands/community/start.js +46 -0
- package/dist/cli/commands/community/stop.d.ts +10 -0
- package/dist/cli/commands/community/stop.js +44 -0
- package/dist/cli/commands/daemon.d.ts +14 -0
- package/dist/cli/commands/daemon.js +484 -0
- package/dist/cli/commands/logs.d.ts +24 -0
- package/dist/cli/commands/logs.js +199 -0
- package/dist/cli/commands/subplebbit/create.d.ts +12 -0
- package/dist/cli/commands/subplebbit/create.js +54 -0
- package/dist/cli/commands/subplebbit/edit.d.ts +12 -0
- package/dist/cli/commands/subplebbit/edit.js +73 -0
- package/dist/cli/commands/subplebbit/get.d.ts +9 -0
- package/dist/cli/commands/subplebbit/get.js +32 -0
- package/dist/cli/commands/subplebbit/list.d.ts +9 -0
- package/dist/cli/commands/subplebbit/list.js +30 -0
- package/dist/cli/commands/subplebbit/start.d.ts +10 -0
- package/dist/cli/commands/subplebbit/start.js +41 -0
- package/dist/cli/commands/subplebbit/stop.d.ts +10 -0
- package/dist/cli/commands/subplebbit/stop.js +43 -0
- package/dist/cli/commands/update/check.d.ts +6 -0
- package/dist/cli/commands/update/check.js +28 -0
- package/dist/cli/commands/update/install.d.ts +12 -0
- package/dist/cli/commands/update/install.js +63 -0
- package/dist/cli/commands/update/versions.d.ts +9 -0
- package/dist/cli/commands/update/versions.js +29 -0
- package/dist/cli/hooks/init/version-hook.d.ts +3 -0
- package/dist/cli/hooks/init/version-hook.js +43 -0
- package/dist/cli/hooks/prerun/parse-dynamic-flags-hook.d.ts +3 -0
- package/dist/cli/hooks/prerun/parse-dynamic-flags-hook.js +94 -0
- package/dist/cli/types.d.ts +4 -0
- package/dist/cli/types.js +1 -0
- package/dist/common-utils/data-migration.d.ts +1 -0
- package/dist/common-utils/data-migration.js +27 -0
- package/dist/common-utils/defaults.d.ts +9 -0
- package/dist/common-utils/defaults.js +10 -0
- package/dist/common-utils/resolvers.d.ts +2 -0
- package/dist/common-utils/resolvers.js +6 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/ipfs/startIpfs.d.ts +3 -0
- package/dist/ipfs/startIpfs.js +304 -0
- package/dist/seeder.d.ts +1 -0
- package/dist/seeder.js +83 -0
- package/dist/update/npm-registry.d.ts +6 -0
- package/dist/update/npm-registry.js +66 -0
- package/dist/update/semver.d.ts +5 -0
- package/dist/update/semver.js +29 -0
- package/dist/util.d.ts +31 -0
- package/dist/util.js +157 -0
- package/dist/webui/daemon-server.d.ts +10 -0
- package/dist/webui/daemon-server.js +140 -0
- package/package.json +143 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { Flags, Command } from "@oclif/core";
|
|
2
|
+
import defaults from "../../common-utils/defaults.js";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import fsPromise from "fs/promises";
|
|
5
|
+
import path from "path";
|
|
6
|
+
export default class Logs extends Command {
|
|
7
|
+
static description = "View the latest BitSocial daemon log file. By default dumps the full log and exits. Use --follow to stream new output in real-time (like tail -f).";
|
|
8
|
+
static flags = {
|
|
9
|
+
follow: Flags.boolean({
|
|
10
|
+
char: "f",
|
|
11
|
+
description: "Follow log output in real-time (like tail -f)",
|
|
12
|
+
default: false
|
|
13
|
+
}),
|
|
14
|
+
tail: Flags.string({
|
|
15
|
+
char: "n",
|
|
16
|
+
description: 'Number of log entries to show from the end. Use "all" to show everything.',
|
|
17
|
+
default: "all"
|
|
18
|
+
}),
|
|
19
|
+
since: Flags.string({
|
|
20
|
+
description: "Show logs since timestamp (ISO 8601, e.g. 2026-01-02T13:23:37Z) or relative time (e.g. 30s, 42m, 2h, 1d)",
|
|
21
|
+
required: false
|
|
22
|
+
}),
|
|
23
|
+
until: Flags.string({
|
|
24
|
+
description: "Show logs before timestamp (ISO 8601, e.g. 2026-01-02T13:23:37Z) or relative time (e.g. 30s, 42m, 2h, 1d)",
|
|
25
|
+
required: false
|
|
26
|
+
}),
|
|
27
|
+
logPath: Flags.directory({
|
|
28
|
+
description: "Specify the directory containing log files",
|
|
29
|
+
required: false
|
|
30
|
+
})
|
|
31
|
+
};
|
|
32
|
+
static examples = [
|
|
33
|
+
"bitsocial logs",
|
|
34
|
+
"bitsocial logs -f",
|
|
35
|
+
"bitsocial logs -n 50",
|
|
36
|
+
"bitsocial logs --since 5m",
|
|
37
|
+
"bitsocial logs --since 2026-01-02T13:23:37Z --until 2026-01-02T14:00:00Z",
|
|
38
|
+
"bitsocial logs --since 1h -f"
|
|
39
|
+
];
|
|
40
|
+
async _findLatestLogFile(logPath) {
|
|
41
|
+
let entries;
|
|
42
|
+
try {
|
|
43
|
+
entries = await fsPromise.readdir(logPath, { withFileTypes: true });
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
this.error(`Log directory does not exist: ${logPath}\nHave you started the daemon yet?`);
|
|
47
|
+
}
|
|
48
|
+
const logFiles = entries
|
|
49
|
+
.filter((entry) => entry.isFile() && entry.name.startsWith("bitsocial_cli_daemon_") && entry.name.endsWith(".log"))
|
|
50
|
+
.map((entry) => entry.name)
|
|
51
|
+
.sort();
|
|
52
|
+
if (logFiles.length === 0) {
|
|
53
|
+
this.error(`No log files found in ${logPath}\nHave you started the daemon yet?`);
|
|
54
|
+
}
|
|
55
|
+
return path.join(logPath, logFiles[logFiles.length - 1]);
|
|
56
|
+
}
|
|
57
|
+
_parseTimestamp(value) {
|
|
58
|
+
// Try relative duration first: 30s, 42m, 2h, 1d
|
|
59
|
+
const relativeMatch = value.match(/^(\d+)([smhd])$/);
|
|
60
|
+
if (relativeMatch) {
|
|
61
|
+
const amount = parseInt(relativeMatch[1], 10);
|
|
62
|
+
const unit = relativeMatch[2];
|
|
63
|
+
const multipliers = { s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
64
|
+
return new Date(Date.now() - amount * multipliers[unit]);
|
|
65
|
+
}
|
|
66
|
+
// Try ISO timestamp
|
|
67
|
+
const date = new Date(value);
|
|
68
|
+
if (isNaN(date.getTime())) {
|
|
69
|
+
this.error(`Invalid timestamp: "${value}". Use ISO 8601 format (e.g. 2026-01-02T13:23:37Z) or relative time (e.g. 30s, 42m, 2h, 1d)`);
|
|
70
|
+
}
|
|
71
|
+
return date;
|
|
72
|
+
}
|
|
73
|
+
_extractTimestamp(line) {
|
|
74
|
+
const match = line.match(/^\[(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)\] /);
|
|
75
|
+
if (!match)
|
|
76
|
+
return null;
|
|
77
|
+
return new Date(match[1]);
|
|
78
|
+
}
|
|
79
|
+
_parseLogEntries(content) {
|
|
80
|
+
const lines = content.split("\n");
|
|
81
|
+
const entries = [];
|
|
82
|
+
for (const line of lines) {
|
|
83
|
+
const timestamp = this._extractTimestamp(line);
|
|
84
|
+
if (timestamp !== null) {
|
|
85
|
+
// New timestamped entry
|
|
86
|
+
entries.push({ timestamp, lines: [line] });
|
|
87
|
+
}
|
|
88
|
+
else if (entries.length > 0) {
|
|
89
|
+
// Continuation line — belongs to the previous entry
|
|
90
|
+
entries[entries.length - 1].lines.push(line);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// Line before any timestamped entry (legacy/header)
|
|
94
|
+
entries.push({ timestamp: null, lines: [line] });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return entries;
|
|
98
|
+
}
|
|
99
|
+
_filterEntries(entries, since, until) {
|
|
100
|
+
return entries.filter((entry) => {
|
|
101
|
+
if (entry.timestamp === null) {
|
|
102
|
+
// Legacy entries with no timestamp: exclude if --since is set, include otherwise
|
|
103
|
+
return !since;
|
|
104
|
+
}
|
|
105
|
+
if (since && entry.timestamp < since)
|
|
106
|
+
return false;
|
|
107
|
+
if (until && entry.timestamp > until)
|
|
108
|
+
return false;
|
|
109
|
+
return true;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
_tailEntries(entries, tailValue) {
|
|
113
|
+
if (tailValue === "all")
|
|
114
|
+
return entries;
|
|
115
|
+
const n = parseInt(tailValue, 10);
|
|
116
|
+
if (isNaN(n) || n < 0) {
|
|
117
|
+
this.error(`Invalid --tail value: "${tailValue}". Must be a non-negative integer or "all".`);
|
|
118
|
+
}
|
|
119
|
+
if (n === 0)
|
|
120
|
+
return [];
|
|
121
|
+
return entries.slice(-n);
|
|
122
|
+
}
|
|
123
|
+
async run() {
|
|
124
|
+
const { flags } = await this.parse(Logs);
|
|
125
|
+
const logPath = flags.logPath ?? defaults.PKC_LOG_PATH;
|
|
126
|
+
const latestLogFile = await this._findLatestLogFile(logPath);
|
|
127
|
+
const since = flags.since ? this._parseTimestamp(flags.since) : undefined;
|
|
128
|
+
const until = flags.until ? this._parseTimestamp(flags.until) : undefined;
|
|
129
|
+
if (!flags.follow) {
|
|
130
|
+
const content = await fsPromise.readFile(latestLogFile, "utf-8");
|
|
131
|
+
const entries = this._parseLogEntries(content);
|
|
132
|
+
const filtered = this._filterEntries(entries, since, until);
|
|
133
|
+
const tailed = this._tailEntries(filtered, flags.tail);
|
|
134
|
+
const output = tailed.map((e) => e.lines.join("\n")).join("\n");
|
|
135
|
+
if (output)
|
|
136
|
+
process.stdout.write(output + "\n");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// Follow mode: dump existing content (filtered + tailed) then watch for new data
|
|
140
|
+
const existingContent = await fsPromise.readFile(latestLogFile, "utf-8");
|
|
141
|
+
const entries = this._parseLogEntries(existingContent);
|
|
142
|
+
const filtered = this._filterEntries(entries, since, until);
|
|
143
|
+
const tailed = this._tailEntries(filtered, flags.tail);
|
|
144
|
+
const initialOutput = tailed.map((e) => e.lines.join("\n")).join("\n");
|
|
145
|
+
if (initialOutput)
|
|
146
|
+
process.stdout.write(initialOutput + "\n");
|
|
147
|
+
const stat = await fsPromise.stat(latestLogFile);
|
|
148
|
+
let position = stat.size;
|
|
149
|
+
let pendingBuffer = "";
|
|
150
|
+
// Watch for new data using polling (works across filesystems including Docker volumes)
|
|
151
|
+
const readNewData = async () => {
|
|
152
|
+
try {
|
|
153
|
+
const currentStat = await fsPromise.stat(latestLogFile);
|
|
154
|
+
if (currentStat.size > position) {
|
|
155
|
+
const fd = await fsPromise.open(latestLogFile, "r");
|
|
156
|
+
const buf = new Uint8Array(currentStat.size - position);
|
|
157
|
+
const { bytesRead } = await fd.read(buf, 0, buf.length, position);
|
|
158
|
+
await fd.close();
|
|
159
|
+
position += bytesRead;
|
|
160
|
+
const chunk = pendingBuffer + new TextDecoder().decode(buf.subarray(0, bytesRead));
|
|
161
|
+
// Split into complete lines; keep any incomplete trailing line in the buffer
|
|
162
|
+
const lastNewline = chunk.lastIndexOf("\n");
|
|
163
|
+
if (lastNewline === -1) {
|
|
164
|
+
pendingBuffer = chunk;
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
pendingBuffer = chunk.slice(lastNewline + 1);
|
|
168
|
+
const completeText = chunk.slice(0, lastNewline + 1);
|
|
169
|
+
if (!since && !until) {
|
|
170
|
+
// No time filtering — pass through directly
|
|
171
|
+
process.stdout.write(completeText);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
const newEntries = this._parseLogEntries(completeText.replace(/\n$/, ""));
|
|
175
|
+
const filteredNew = this._filterEntries(newEntries, since, until);
|
|
176
|
+
const output = filteredNew.map((e) => e.lines.join("\n")).join("\n");
|
|
177
|
+
if (output)
|
|
178
|
+
process.stdout.write(output + "\n");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// File may have been rotated or deleted
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
fs.watchFile(latestLogFile, { interval: 300 }, readNewData);
|
|
187
|
+
// Keep the process alive and clean up on exit
|
|
188
|
+
process.on("SIGINT", () => {
|
|
189
|
+
fs.unwatchFile(latestLogFile, readNewData);
|
|
190
|
+
process.exit(0);
|
|
191
|
+
});
|
|
192
|
+
process.on("SIGTERM", () => {
|
|
193
|
+
fs.unwatchFile(latestLogFile, readNewData);
|
|
194
|
+
process.exit(0);
|
|
195
|
+
});
|
|
196
|
+
// Keep process alive
|
|
197
|
+
await new Promise(() => { });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseCommand } from "../../base-command.js";
|
|
2
|
+
export default class Create extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: {
|
|
5
|
+
description: string;
|
|
6
|
+
command: string;
|
|
7
|
+
}[];
|
|
8
|
+
static flags: {
|
|
9
|
+
privateKeyPath: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Flags } from "@oclif/core";
|
|
2
|
+
//@ts-ignore
|
|
3
|
+
import DataObjectParser from "dataobject-parser";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import { BaseCommand } from "../../base-command.js";
|
|
6
|
+
import { getPlebbitLogger } from "../../../util.js";
|
|
7
|
+
import * as remeda from "remeda";
|
|
8
|
+
export default class Create extends BaseCommand {
|
|
9
|
+
static description = "Create a subplebbit with specific properties. A newly created sub will be started after creation and be able to receive publications. For a list of properties, visit https://github.com/plebbit/plebbit-js#subplebbiteditsubplebbiteditoptions";
|
|
10
|
+
static examples = [
|
|
11
|
+
{
|
|
12
|
+
description: "Create a subplebbit with title 'Hello Plebs' and description 'Welcome'",
|
|
13
|
+
command: "<%= config.bin %> <%= command.id %> --title 'Hello Plebs' --description 'Welcome'"
|
|
14
|
+
}
|
|
15
|
+
];
|
|
16
|
+
static flags = {
|
|
17
|
+
privateKeyPath: Flags.file({
|
|
18
|
+
exists: true,
|
|
19
|
+
description: "Private key (PEM) of the subplebbit signer that will be used to determine address (if address is not a domain). If it's not provided then Plebbit will generate a private key"
|
|
20
|
+
})
|
|
21
|
+
};
|
|
22
|
+
async run() {
|
|
23
|
+
const { flags } = await this.parse(Create);
|
|
24
|
+
const log = (await getPlebbitLogger())("plebbit-cli:commands:subplebbit:create");
|
|
25
|
+
log(`flags: `, flags);
|
|
26
|
+
const plebbit = await this._connectToPlebbitRpc(flags.plebbitRpcUrl.toString());
|
|
27
|
+
const createOptions = DataObjectParser.transpose(remeda.omit(flags, ["plebbitRpcUrl", "privateKeyPath"]))["_data"];
|
|
28
|
+
if (flags.privateKeyPath)
|
|
29
|
+
try {
|
|
30
|
+
//@ts-expect-error
|
|
31
|
+
createOptions.signer = { privateKey: (await fs.promises.readFile(flags.privateKeyPath)).toString(), type: "ed25519" };
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
const error = e;
|
|
35
|
+
//@ts-expect-error
|
|
36
|
+
error.details = { ...error.details, privateKeyPath: flags.privateKeyPath };
|
|
37
|
+
await plebbit.destroy();
|
|
38
|
+
this.error(error);
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const createdSub = await plebbit.createSubplebbit(createOptions);
|
|
42
|
+
await createdSub.start();
|
|
43
|
+
this.log(createdSub.address);
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
const error = e;
|
|
47
|
+
//@ts-expect-error
|
|
48
|
+
error.details = { ...error.details, createOptions };
|
|
49
|
+
await plebbit.destroy();
|
|
50
|
+
this.error(error);
|
|
51
|
+
}
|
|
52
|
+
await plebbit.destroy();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseCommand } from "../../base-command.js";
|
|
2
|
+
export default class Edit extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static args: {
|
|
5
|
+
address: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static examples: {
|
|
8
|
+
description: string;
|
|
9
|
+
command: string;
|
|
10
|
+
}[];
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//@ts-expect-error
|
|
2
|
+
import DataObjectParser from "dataobject-parser";
|
|
3
|
+
import { Args } from "@oclif/core";
|
|
4
|
+
import { BaseCommand } from "../../base-command.js";
|
|
5
|
+
import { getPlebbitLogger, mergeDeep } from "../../../util.js";
|
|
6
|
+
import * as remeda from "remeda";
|
|
7
|
+
export default class Edit extends BaseCommand {
|
|
8
|
+
static description = "Edit a subplebbit properties. For a list of properties, visit https://github.com/plebbit/plebbit-js#subplebbiteditsubplebbiteditoptions";
|
|
9
|
+
static args = {
|
|
10
|
+
address: Args.string({
|
|
11
|
+
name: "address",
|
|
12
|
+
required: true,
|
|
13
|
+
description: "Address of the subplebbit address to edit"
|
|
14
|
+
})
|
|
15
|
+
};
|
|
16
|
+
static examples = [
|
|
17
|
+
{
|
|
18
|
+
description: "Change the address of the sub to a new ENS address",
|
|
19
|
+
command: "plebbit subplebbit edit 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu --address newAddress.eth"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
description: "Add the author address 'esteban.eth' as an admin on the sub",
|
|
23
|
+
command: `plebbit subplebbit edit mysub.eth '--roles["esteban.eth"].role' admin`
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
description: "Add two challenges to the sub. The first challenge will be a question and answer, and the second will be an image captcha",
|
|
27
|
+
command: `plebbit subplebbit edit mysub.eth --settings.challenges[0].name question --settings.challenges[0].options.question "what is the password?" --settings.challenges[0].options.answer thepassword --settings.challenges[1].name captcha-canvas-v3`
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
description: "Change the title and description",
|
|
31
|
+
command: `plebbit subplebbit edit mysub.eth --title "This is the new title" --description "This is the new description" `
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
description: "Remove a role from a moderator/admin/owner",
|
|
35
|
+
command: "plebbit subplebbit edit plebbit.eth --roles['rinse12.eth'] null"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
description: "Enable settings.fetchThumbnailUrls to fetch the thumbnail of url submitted by authors",
|
|
39
|
+
command: "subplebbit edit plebbit.eth --settings.fetchThumbnailUrls"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
description: "disable settings.fetchThumbnailUrls",
|
|
43
|
+
command: "subplebbit edit plebbit.eth --settings.fetchThumbnailUrls=false"
|
|
44
|
+
}
|
|
45
|
+
];
|
|
46
|
+
async run() {
|
|
47
|
+
const { flags, args } = await this.parse(Edit);
|
|
48
|
+
const log = (await getPlebbitLogger())("plebbit-cli:commands:subplebbit:edit");
|
|
49
|
+
log(`flags: `, flags);
|
|
50
|
+
const plebbit = await this._connectToPlebbitRpc(flags.plebbitRpcUrl.toString());
|
|
51
|
+
const editOptions = DataObjectParser.transpose(remeda.omit(flags, ["plebbitRpcUrl"]))["_data"];
|
|
52
|
+
log("Edit options parsed:", editOptions);
|
|
53
|
+
const localSubs = plebbit.subplebbits;
|
|
54
|
+
if (!localSubs.includes(args.address))
|
|
55
|
+
this.error("Can't edit a remote subplebbit, make sure you're editing a local sub");
|
|
56
|
+
try {
|
|
57
|
+
const sub = await plebbit.createSubplebbit({ address: args.address });
|
|
58
|
+
const mergedSubState = remeda.pick(sub, remeda.keys.strict(editOptions));
|
|
59
|
+
const finalMergedState = mergeDeep(mergedSubState, editOptions);
|
|
60
|
+
log("Internal sub state after merge:", finalMergedState);
|
|
61
|
+
await sub.edit(finalMergedState);
|
|
62
|
+
this.log(sub.address);
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
//@ts-expect-error
|
|
66
|
+
e.details = { ...e.details, editOptions, address: args.address };
|
|
67
|
+
console.error(e);
|
|
68
|
+
await plebbit.destroy();
|
|
69
|
+
this.exit(1);
|
|
70
|
+
}
|
|
71
|
+
await plebbit.destroy();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseCommand } from "../../base-command.js";
|
|
2
|
+
export default class Get extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
address: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Args } from "@oclif/core";
|
|
2
|
+
import { BaseCommand } from "../../base-command.js";
|
|
3
|
+
import * as remeda from "remeda";
|
|
4
|
+
export default class Get extends BaseCommand {
|
|
5
|
+
static description = "Fetch a local or remote subplebbit, and print its json in the terminal";
|
|
6
|
+
static examples = [
|
|
7
|
+
"plebbit subplebbit get plebmusic.eth",
|
|
8
|
+
"plebbit subplebbit get 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu"
|
|
9
|
+
];
|
|
10
|
+
static args = {
|
|
11
|
+
address: Args.string({
|
|
12
|
+
name: "address",
|
|
13
|
+
required: true,
|
|
14
|
+
description: "Address of the subplebbit address to fetch"
|
|
15
|
+
})
|
|
16
|
+
};
|
|
17
|
+
async run() {
|
|
18
|
+
const { args, flags } = await this.parse(Get);
|
|
19
|
+
const plebbit = await this._connectToPlebbitRpc(flags.plebbitRpcUrl.toString());
|
|
20
|
+
try {
|
|
21
|
+
const sub = await plebbit.getSubplebbit({ address: args.address });
|
|
22
|
+
const subJson = JSON.parse(JSON.stringify(sub));
|
|
23
|
+
this.logJson({ posts: subJson.posts, ...remeda.omit(subJson, ["posts"]) }); // make sure posts is printed first, because most users won't look at it
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
console.error(e);
|
|
27
|
+
await plebbit.destroy();
|
|
28
|
+
this.exit(1);
|
|
29
|
+
}
|
|
30
|
+
await plebbit.destroy();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseCommand } from "../../base-command.js";
|
|
2
|
+
export default class List extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Flags } from "@oclif/core";
|
|
2
|
+
import { BaseCommand } from "../../base-command.js";
|
|
3
|
+
import { EOL } from "os";
|
|
4
|
+
import { getPlebbitLogger } from "../../../util.js";
|
|
5
|
+
import { printTable } from "@oclif/table";
|
|
6
|
+
export default class List extends BaseCommand {
|
|
7
|
+
static description = "List your subplebbits";
|
|
8
|
+
static examples = ["plebbit subplebbit list -q", "plebbit subplebbit list"];
|
|
9
|
+
static flags = {
|
|
10
|
+
quiet: Flags.boolean({ char: "q", summary: "Only display subplebbit addresses" })
|
|
11
|
+
};
|
|
12
|
+
async run() {
|
|
13
|
+
const { flags } = await this.parse(List);
|
|
14
|
+
const log = (await getPlebbitLogger())("plebbit-cli:commands:subplebbit:list");
|
|
15
|
+
log(`flags: `, flags);
|
|
16
|
+
const plebbit = await this._connectToPlebbitRpc(flags.plebbitRpcUrl.toString());
|
|
17
|
+
const subs = plebbit.subplebbits;
|
|
18
|
+
if (flags.quiet) {
|
|
19
|
+
this.log(subs.join(EOL));
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const subsWithStarted = await Promise.all(subs.map(async (subAddress) => {
|
|
23
|
+
const subInstance = await plebbit.createSubplebbit({ address: subAddress });
|
|
24
|
+
return { address: subInstance.address, started: subInstance.started };
|
|
25
|
+
}));
|
|
26
|
+
printTable({ data: subsWithStarted, sort: { started: "desc" } });
|
|
27
|
+
}
|
|
28
|
+
await plebbit.destroy();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseCommand } from "../../base-command.js";
|
|
2
|
+
export default class Start extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static strict: boolean;
|
|
5
|
+
static args: {
|
|
6
|
+
addresses: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static examples: string[];
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getPlebbitLogger } from "../../../util.js";
|
|
2
|
+
import { BaseCommand } from "../../base-command.js";
|
|
3
|
+
import { Args } from "@oclif/core";
|
|
4
|
+
export default class Start extends BaseCommand {
|
|
5
|
+
static description = "Start a subplebbit";
|
|
6
|
+
static strict = false; // To allow for variable length arguments
|
|
7
|
+
static args = {
|
|
8
|
+
addresses: Args.string({
|
|
9
|
+
name: "addresses", // name of arg to show in help and reference with args[name]
|
|
10
|
+
required: true,
|
|
11
|
+
description: "Addresses of subplebbits to start. Separated by space"
|
|
12
|
+
})
|
|
13
|
+
};
|
|
14
|
+
static examples = [
|
|
15
|
+
"plebbit subplebbit start plebbit.eth",
|
|
16
|
+
"plebbit subplebbit start 12D3KooWG3XbzoVyAE6Y9vHZKF64Yuuu4TjdgQKedk14iYmTEPWu"
|
|
17
|
+
];
|
|
18
|
+
async run() {
|
|
19
|
+
const { argv, flags } = await this.parse(Start);
|
|
20
|
+
const addresses = argv;
|
|
21
|
+
const log = (await getPlebbitLogger())("plebbit-cli:commands:subplebbit:start");
|
|
22
|
+
log(`addresses: `, addresses);
|
|
23
|
+
log(`flags: `, flags);
|
|
24
|
+
const plebbit = await this._connectToPlebbitRpc(flags.plebbitRpcUrl.toString());
|
|
25
|
+
for (const address of addresses) {
|
|
26
|
+
try {
|
|
27
|
+
const sub = await plebbit.createSubplebbit({ address });
|
|
28
|
+
await sub.start();
|
|
29
|
+
this.log(address);
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
//@ts-expect-error
|
|
33
|
+
e.details = { ...e.details, address };
|
|
34
|
+
console.error(e);
|
|
35
|
+
await plebbit.destroy();
|
|
36
|
+
this.exit(1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
await plebbit.destroy();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseCommand } from "../../base-command.js";
|
|
2
|
+
export default class Stop extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static strict: boolean;
|
|
5
|
+
static args: {
|
|
6
|
+
addresses: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static examples: string[];
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { getPlebbitLogger } from "../../../util.js";
|
|
2
|
+
import { BaseCommand } from "../../base-command.js";
|
|
3
|
+
import { Args } from "@oclif/core";
|
|
4
|
+
export default class Stop extends BaseCommand {
|
|
5
|
+
static description = "Stop a subplebbit. The subplebbit will not publish or receive any publications until it is started again.";
|
|
6
|
+
static strict = false; // To allow for variable length arguments
|
|
7
|
+
static args = {
|
|
8
|
+
addresses: Args.string({
|
|
9
|
+
name: "addresses",
|
|
10
|
+
required: true,
|
|
11
|
+
description: "Addresses of subplebbits to stop. Separated by space"
|
|
12
|
+
})
|
|
13
|
+
};
|
|
14
|
+
static examples = [
|
|
15
|
+
"plebbit subplebbit stop plebbit.eth",
|
|
16
|
+
"plebbit subplebbit stop Qmb99crTbSUfKXamXwZBe829Vf6w5w5TktPkb6WstC9RFW"
|
|
17
|
+
];
|
|
18
|
+
async run() {
|
|
19
|
+
const { argv, flags } = await this.parse(Stop);
|
|
20
|
+
const log = (await getPlebbitLogger())("plebbit-cli:commands:subplebbit:stop");
|
|
21
|
+
log(`addresses: `, argv);
|
|
22
|
+
log(`flags: `, flags);
|
|
23
|
+
const addresses = argv;
|
|
24
|
+
if (!Array.isArray(addresses))
|
|
25
|
+
this.error(`Failed to parse addresses correctly (${addresses})`);
|
|
26
|
+
const plebbit = await this._connectToPlebbitRpc(flags.plebbitRpcUrl.toString());
|
|
27
|
+
for (const address of addresses) {
|
|
28
|
+
try {
|
|
29
|
+
const sub = await plebbit.createSubplebbit({ address });
|
|
30
|
+
await sub.stop(); // should stop the original subplebbit instance from running
|
|
31
|
+
this.log(address);
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
//@ts-expect-error
|
|
35
|
+
e.details = { ...e.details, address };
|
|
36
|
+
console.error(e);
|
|
37
|
+
await plebbit.destroy();
|
|
38
|
+
this.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
await plebbit.destroy();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
import { fetchLatestVersion } from "../../../update/npm-registry.js";
|
|
3
|
+
import { compareVersions } from "../../../update/semver.js";
|
|
4
|
+
export default class Check extends Command {
|
|
5
|
+
static description = "Check if a newer version of bitsocial is available on npm";
|
|
6
|
+
static examples = ["bitsocial update check"];
|
|
7
|
+
async run() {
|
|
8
|
+
let latest;
|
|
9
|
+
try {
|
|
10
|
+
latest = await fetchLatestVersion();
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
this.error(`Failed to check for updates: ${err.message}`, { exit: 1 });
|
|
14
|
+
}
|
|
15
|
+
const current = this.config.version;
|
|
16
|
+
const cmp = compareVersions(current, latest);
|
|
17
|
+
if (cmp === 0) {
|
|
18
|
+
this.log(`bitsocial is up-to-date: v${current}`);
|
|
19
|
+
}
|
|
20
|
+
else if (cmp < 0) {
|
|
21
|
+
this.log(`Update available: v${latest} (current: v${current})`);
|
|
22
|
+
this.log(`Run: bitsocial update install`);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
this.log(`bitsocial v${current} is newer than latest published release v${latest}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
export default class Install extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static args: {
|
|
5
|
+
version: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static flags: {
|
|
8
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
};
|
|
10
|
+
static examples: string[];
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Args, Flags, Command } from "@oclif/core";
|
|
2
|
+
import tcpPortUsed from "tcp-port-used";
|
|
3
|
+
import defaults from "../../../common-utils/defaults.js";
|
|
4
|
+
import { fetchLatestVersion, installGlobal } from "../../../update/npm-registry.js";
|
|
5
|
+
import { compareVersions } from "../../../update/semver.js";
|
|
6
|
+
export default class Install extends Command {
|
|
7
|
+
static description = "Install a specific version of bitsocial from npm";
|
|
8
|
+
static args = {
|
|
9
|
+
version: Args.string({
|
|
10
|
+
description: 'Version to install (e.g. "0.19.40" or "latest")',
|
|
11
|
+
required: false,
|
|
12
|
+
default: "latest"
|
|
13
|
+
})
|
|
14
|
+
};
|
|
15
|
+
static flags = {
|
|
16
|
+
force: Flags.boolean({
|
|
17
|
+
description: "Reinstall even if already on the requested version",
|
|
18
|
+
default: false
|
|
19
|
+
})
|
|
20
|
+
};
|
|
21
|
+
static examples = [
|
|
22
|
+
"bitsocial update install",
|
|
23
|
+
"bitsocial update install latest",
|
|
24
|
+
"bitsocial update install 0.19.40",
|
|
25
|
+
"bitsocial update install --force"
|
|
26
|
+
];
|
|
27
|
+
async run() {
|
|
28
|
+
const { args, flags } = await this.parse(Install);
|
|
29
|
+
// Check if daemon is running — refuse to update while it's active
|
|
30
|
+
const rpcPort = Number(defaults.PKC_RPC_URL.port);
|
|
31
|
+
const daemonRunning = await tcpPortUsed.check(rpcPort, "127.0.0.1").catch(() => false);
|
|
32
|
+
if (daemonRunning) {
|
|
33
|
+
this.error(`Daemon is running on port ${rpcPort}. Stop it first with Ctrl-C, then run 'bitsocial update install'.`, { exit: 1 });
|
|
34
|
+
}
|
|
35
|
+
// Resolve the target version
|
|
36
|
+
let targetVersion;
|
|
37
|
+
if (!args.version || args.version === "latest") {
|
|
38
|
+
try {
|
|
39
|
+
targetVersion = await fetchLatestVersion();
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
this.error(`Failed to fetch latest version: ${err.message}`, { exit: 1 });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
targetVersion = args.version.replace(/^v/i, "");
|
|
47
|
+
}
|
|
48
|
+
const current = this.config.version;
|
|
49
|
+
// Skip if already on this version (unless --force)
|
|
50
|
+
if (compareVersions(current, targetVersion) === 0 && !flags.force) {
|
|
51
|
+
this.log(`Already on v${current}. Use --force to reinstall.`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
this.log(`Installing bitsocial-cli@${targetVersion}...`);
|
|
55
|
+
try {
|
|
56
|
+
await installGlobal(targetVersion);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
this.error(`Update failed: ${err.message}`, { exit: 1 });
|
|
60
|
+
}
|
|
61
|
+
this.log(`Installed bitsocial v${targetVersion} (was v${current}).`);
|
|
62
|
+
}
|
|
63
|
+
}
|