chainlesschain 0.37.12 → 0.40.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.
- package/package.json +3 -2
- package/src/commands/agent.js +7 -1
- package/src/commands/ask.js +24 -9
- package/src/commands/chat.js +7 -1
- package/src/commands/cli-anything.js +266 -0
- package/src/commands/compliance.js +216 -0
- package/src/commands/dao.js +312 -0
- package/src/commands/dlp.js +278 -0
- package/src/commands/evomap.js +558 -0
- package/src/commands/hardening.js +230 -0
- package/src/commands/matrix.js +168 -0
- package/src/commands/nostr.js +185 -0
- package/src/commands/pqc.js +162 -0
- package/src/commands/scim.js +218 -0
- package/src/commands/serve.js +109 -0
- package/src/commands/siem.js +156 -0
- package/src/commands/social.js +480 -0
- package/src/commands/terraform.js +148 -0
- package/src/constants.js +1 -0
- package/src/index.js +60 -0
- package/src/lib/autonomous-agent.js +487 -0
- package/src/lib/cli-anything-bridge.js +379 -0
- package/src/lib/cli-context-engineering.js +472 -0
- package/src/lib/compliance-manager.js +290 -0
- package/src/lib/content-recommender.js +205 -0
- package/src/lib/dao-governance.js +296 -0
- package/src/lib/dlp-engine.js +304 -0
- package/src/lib/evomap-client.js +135 -0
- package/src/lib/evomap-federation.js +240 -0
- package/src/lib/evomap-governance.js +250 -0
- package/src/lib/evomap-manager.js +227 -0
- package/src/lib/git-integration.js +1 -1
- package/src/lib/hardening-manager.js +275 -0
- package/src/lib/llm-providers.js +14 -1
- package/src/lib/matrix-bridge.js +196 -0
- package/src/lib/nostr-bridge.js +195 -0
- package/src/lib/permanent-memory.js +370 -0
- package/src/lib/plan-mode.js +211 -0
- package/src/lib/pqc-manager.js +196 -0
- package/src/lib/scim-manager.js +212 -0
- package/src/lib/session-manager.js +38 -0
- package/src/lib/siem-exporter.js +137 -0
- package/src/lib/social-manager.js +283 -0
- package/src/lib/task-model-selector.js +232 -0
- package/src/lib/terraform-manager.js +201 -0
- package/src/lib/ws-server.js +474 -0
- package/src/repl/agent-repl.js +796 -41
- package/src/repl/chat-repl.js +14 -6
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hardening commands
|
|
3
|
+
* chainlesschain hardening baseline|audit
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { logger } from "../lib/logger.js";
|
|
8
|
+
import { bootstrap, shutdown } from "../runtime/bootstrap.js";
|
|
9
|
+
import {
|
|
10
|
+
ensureHardeningTables,
|
|
11
|
+
collectBaseline,
|
|
12
|
+
compareBaseline,
|
|
13
|
+
listBaselines,
|
|
14
|
+
runAudit,
|
|
15
|
+
getAuditReports,
|
|
16
|
+
getAuditReport,
|
|
17
|
+
} from "../lib/hardening-manager.js";
|
|
18
|
+
|
|
19
|
+
export function registerHardeningCommand(program) {
|
|
20
|
+
const hardening = program
|
|
21
|
+
.command("hardening")
|
|
22
|
+
.description(
|
|
23
|
+
"Security hardening — baselines, audits, regression detection",
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// baseline subcommands
|
|
27
|
+
const baseline = hardening
|
|
28
|
+
.command("baseline")
|
|
29
|
+
.description("Performance baseline management");
|
|
30
|
+
|
|
31
|
+
baseline
|
|
32
|
+
.command("collect <name>")
|
|
33
|
+
.description("Collect a performance baseline")
|
|
34
|
+
.option("-v, --version <version>", "Baseline version", "1.0.0")
|
|
35
|
+
.action(async (name, options) => {
|
|
36
|
+
try {
|
|
37
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
38
|
+
if (!ctx.db) {
|
|
39
|
+
logger.error("Database not available");
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
const db = ctx.db.getDatabase();
|
|
43
|
+
ensureHardeningTables(db);
|
|
44
|
+
|
|
45
|
+
const result = collectBaseline(db, name, options.version);
|
|
46
|
+
logger.success("Baseline collected");
|
|
47
|
+
logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(result.id)}`);
|
|
48
|
+
logger.log(` ${chalk.bold("Name:")} ${result.name}`);
|
|
49
|
+
logger.log(` ${chalk.bold("Version:")} ${result.version}`);
|
|
50
|
+
logger.log(` ${chalk.bold("Status:")} ${result.status}`);
|
|
51
|
+
await shutdown();
|
|
52
|
+
} catch (err) {
|
|
53
|
+
logger.error(`Failed: ${err.message}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
baseline
|
|
59
|
+
.command("compare <baseline-id>")
|
|
60
|
+
.description("Compare baseline against current or another baseline")
|
|
61
|
+
.option("-c, --current <id>", "Current baseline ID to compare against")
|
|
62
|
+
.option("--json", "Output as JSON")
|
|
63
|
+
.action(async (baselineId, options) => {
|
|
64
|
+
try {
|
|
65
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
66
|
+
if (!ctx.db) {
|
|
67
|
+
logger.error("Database not available");
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
const db = ctx.db.getDatabase();
|
|
71
|
+
ensureHardeningTables(db);
|
|
72
|
+
|
|
73
|
+
const result = compareBaseline(baselineId, options.current);
|
|
74
|
+
if (options.json) {
|
|
75
|
+
console.log(JSON.stringify(result, null, 2));
|
|
76
|
+
} else {
|
|
77
|
+
logger.log(
|
|
78
|
+
` ${chalk.bold("Regressions:")} ${result.hasRegressions ? chalk.red("Yes") : chalk.green("No")}`,
|
|
79
|
+
);
|
|
80
|
+
logger.log(` ${chalk.bold("Summary:")} ${result.summary}`);
|
|
81
|
+
for (const r of result.regressions) {
|
|
82
|
+
logger.log(
|
|
83
|
+
` ${chalk.yellow(r.metric)}: ${r.ratio.toFixed(2)}x (threshold: ${r.threshold}x)`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
await shutdown();
|
|
88
|
+
} catch (err) {
|
|
89
|
+
logger.error(`Failed: ${err.message}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
baseline
|
|
95
|
+
.command("list")
|
|
96
|
+
.description("List collected baselines")
|
|
97
|
+
.option("--json", "Output as JSON")
|
|
98
|
+
.action(async (options) => {
|
|
99
|
+
try {
|
|
100
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
101
|
+
if (!ctx.db) {
|
|
102
|
+
logger.error("Database not available");
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
const db = ctx.db.getDatabase();
|
|
106
|
+
ensureHardeningTables(db);
|
|
107
|
+
|
|
108
|
+
const baselines = listBaselines();
|
|
109
|
+
if (options.json) {
|
|
110
|
+
console.log(JSON.stringify(baselines, null, 2));
|
|
111
|
+
} else if (baselines.length === 0) {
|
|
112
|
+
logger.info("No baselines collected.");
|
|
113
|
+
} else {
|
|
114
|
+
for (const b of baselines) {
|
|
115
|
+
logger.log(
|
|
116
|
+
` ${chalk.cyan(b.id.slice(0, 8))} ${b.name} v${b.version} [${b.status}] samples=${b.sampleCount}`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
await shutdown();
|
|
121
|
+
} catch (err) {
|
|
122
|
+
logger.error(`Failed: ${err.message}`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// audit subcommands
|
|
128
|
+
const audit = hardening
|
|
129
|
+
.command("audit")
|
|
130
|
+
.description("Security audit management");
|
|
131
|
+
|
|
132
|
+
audit
|
|
133
|
+
.command("run <name>")
|
|
134
|
+
.description("Run a security hardening audit")
|
|
135
|
+
.action(async (name) => {
|
|
136
|
+
try {
|
|
137
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
138
|
+
if (!ctx.db) {
|
|
139
|
+
logger.error("Database not available");
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
const db = ctx.db.getDatabase();
|
|
143
|
+
ensureHardeningTables(db);
|
|
144
|
+
|
|
145
|
+
const result = runAudit(db, name);
|
|
146
|
+
logger.success(`Audit complete: score ${result.score}%`);
|
|
147
|
+
logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(result.id)}`);
|
|
148
|
+
logger.log(
|
|
149
|
+
` ${chalk.bold("Passed:")} ${result.passed}/${result.checks.length}`,
|
|
150
|
+
);
|
|
151
|
+
logger.log(
|
|
152
|
+
` ${chalk.bold("Failed:")} ${result.failed}/${result.checks.length}`,
|
|
153
|
+
);
|
|
154
|
+
for (const rec of result.recommendations) {
|
|
155
|
+
logger.log(` ${chalk.yellow("→")} ${rec}`);
|
|
156
|
+
}
|
|
157
|
+
await shutdown();
|
|
158
|
+
} catch (err) {
|
|
159
|
+
logger.error(`Failed: ${err.message}`);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
audit
|
|
165
|
+
.command("reports")
|
|
166
|
+
.description("List audit reports")
|
|
167
|
+
.option("--json", "Output as JSON")
|
|
168
|
+
.action(async (options) => {
|
|
169
|
+
try {
|
|
170
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
171
|
+
if (!ctx.db) {
|
|
172
|
+
logger.error("Database not available");
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
const db = ctx.db.getDatabase();
|
|
176
|
+
ensureHardeningTables(db);
|
|
177
|
+
|
|
178
|
+
const reports = getAuditReports();
|
|
179
|
+
if (options.json) {
|
|
180
|
+
console.log(JSON.stringify(reports, null, 2));
|
|
181
|
+
} else if (reports.length === 0) {
|
|
182
|
+
logger.info("No audit reports.");
|
|
183
|
+
} else {
|
|
184
|
+
for (const r of reports) {
|
|
185
|
+
logger.log(
|
|
186
|
+
` ${chalk.cyan(r.id.slice(0, 8))} ${r.name} score=${r.score}% passed=${r.passed} failed=${r.failed}`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
await shutdown();
|
|
191
|
+
} catch (err) {
|
|
192
|
+
logger.error(`Failed: ${err.message}`);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
audit
|
|
198
|
+
.command("report <audit-id>")
|
|
199
|
+
.description("Show a specific audit report")
|
|
200
|
+
.option("--json", "Output as JSON")
|
|
201
|
+
.action(async (auditId, options) => {
|
|
202
|
+
try {
|
|
203
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
204
|
+
if (!ctx.db) {
|
|
205
|
+
logger.error("Database not available");
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
const db = ctx.db.getDatabase();
|
|
209
|
+
ensureHardeningTables(db);
|
|
210
|
+
|
|
211
|
+
const report = getAuditReport(auditId);
|
|
212
|
+
if (options.json) {
|
|
213
|
+
console.log(JSON.stringify(report, null, 2));
|
|
214
|
+
} else {
|
|
215
|
+
logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(report.id)}`);
|
|
216
|
+
logger.log(` ${chalk.bold("Name:")} ${report.name}`);
|
|
217
|
+
logger.log(` ${chalk.bold("Score:")} ${report.score}%`);
|
|
218
|
+
for (const c of report.checks) {
|
|
219
|
+
const icon =
|
|
220
|
+
c.status === "pass" ? chalk.green("✓") : chalk.red("✗");
|
|
221
|
+
logger.log(` ${icon} ${c.name}: ${c.detail}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
await shutdown();
|
|
225
|
+
} catch (err) {
|
|
226
|
+
logger.error(`Failed: ${err.message}`);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Matrix commands
|
|
3
|
+
* chainlesschain matrix login|rooms|send|messages|join
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { logger } from "../lib/logger.js";
|
|
8
|
+
import { bootstrap, shutdown } from "../runtime/bootstrap.js";
|
|
9
|
+
import {
|
|
10
|
+
ensureMatrixTables,
|
|
11
|
+
login,
|
|
12
|
+
listRooms,
|
|
13
|
+
sendMessage,
|
|
14
|
+
getMessages,
|
|
15
|
+
joinRoom,
|
|
16
|
+
getLoginState,
|
|
17
|
+
} from "../lib/matrix-bridge.js";
|
|
18
|
+
|
|
19
|
+
export function registerMatrixCommand(program) {
|
|
20
|
+
const matrix = program
|
|
21
|
+
.command("matrix")
|
|
22
|
+
.description("Matrix bridge — rooms, messaging, E2EE");
|
|
23
|
+
|
|
24
|
+
matrix
|
|
25
|
+
.command("login")
|
|
26
|
+
.description("Login to a Matrix homeserver")
|
|
27
|
+
.option("-s, --server <url>", "Homeserver URL", "https://matrix.org")
|
|
28
|
+
.requiredOption("-u, --user <id>", "User ID (e.g. @user:matrix.org)")
|
|
29
|
+
.requiredOption("-p, --password <pass>", "Password")
|
|
30
|
+
.action(async (options) => {
|
|
31
|
+
try {
|
|
32
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
33
|
+
if (!ctx.db) {
|
|
34
|
+
logger.error("Database not available");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
const db = ctx.db.getDatabase();
|
|
38
|
+
ensureMatrixTables(db);
|
|
39
|
+
|
|
40
|
+
const result = login(
|
|
41
|
+
db,
|
|
42
|
+
options.server,
|
|
43
|
+
options.user,
|
|
44
|
+
options.password,
|
|
45
|
+
);
|
|
46
|
+
logger.success(`Logged in as ${chalk.cyan(result.userId)}`);
|
|
47
|
+
logger.log(` ${chalk.bold("Server:")} ${result.homeserver}`);
|
|
48
|
+
logger.log(` ${chalk.bold("Token:")} ${result.accessToken}`);
|
|
49
|
+
await shutdown();
|
|
50
|
+
} catch (err) {
|
|
51
|
+
logger.error(`Failed: ${err.message}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
matrix
|
|
57
|
+
.command("rooms")
|
|
58
|
+
.description("List joined rooms")
|
|
59
|
+
.option("--json", "Output as JSON")
|
|
60
|
+
.action(async (options) => {
|
|
61
|
+
try {
|
|
62
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
63
|
+
if (!ctx.db) {
|
|
64
|
+
logger.error("Database not available");
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
const db = ctx.db.getDatabase();
|
|
68
|
+
ensureMatrixTables(db);
|
|
69
|
+
|
|
70
|
+
const rooms = listRooms();
|
|
71
|
+
if (options.json) {
|
|
72
|
+
console.log(JSON.stringify(rooms, null, 2));
|
|
73
|
+
} else if (rooms.length === 0) {
|
|
74
|
+
logger.info("No rooms joined.");
|
|
75
|
+
} else {
|
|
76
|
+
for (const r of rooms) {
|
|
77
|
+
logger.log(
|
|
78
|
+
` ${chalk.cyan(r.roomId)} ${r.name || ""} members=${r.memberCount} encrypted=${r.isEncrypted}`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
await shutdown();
|
|
83
|
+
} catch (err) {
|
|
84
|
+
logger.error(`Failed: ${err.message}`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
matrix
|
|
90
|
+
.command("send <room-id> <message>")
|
|
91
|
+
.description("Send a message to a room")
|
|
92
|
+
.option("-t, --type <msgtype>", "Message type", "m.text")
|
|
93
|
+
.action(async (roomId, message, options) => {
|
|
94
|
+
try {
|
|
95
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
96
|
+
if (!ctx.db) {
|
|
97
|
+
logger.error("Database not available");
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
const db = ctx.db.getDatabase();
|
|
101
|
+
ensureMatrixTables(db);
|
|
102
|
+
|
|
103
|
+
const result = sendMessage(db, roomId, message, options.type);
|
|
104
|
+
logger.success(`Message sent to ${chalk.cyan(roomId)}`);
|
|
105
|
+
logger.log(` ${chalk.bold("Event:")} ${result.event.eventId}`);
|
|
106
|
+
await shutdown();
|
|
107
|
+
} catch (err) {
|
|
108
|
+
logger.error(`Failed: ${err.message}`);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
matrix
|
|
114
|
+
.command("messages <room-id>")
|
|
115
|
+
.description("Get messages from a room")
|
|
116
|
+
.option("-n, --limit <n>", "Max messages", "50")
|
|
117
|
+
.option("--json", "Output as JSON")
|
|
118
|
+
.action(async (roomId, options) => {
|
|
119
|
+
try {
|
|
120
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
121
|
+
if (!ctx.db) {
|
|
122
|
+
logger.error("Database not available");
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
const db = ctx.db.getDatabase();
|
|
126
|
+
ensureMatrixTables(db);
|
|
127
|
+
|
|
128
|
+
const messages = getMessages(roomId, {
|
|
129
|
+
limit: parseInt(options.limit),
|
|
130
|
+
});
|
|
131
|
+
if (options.json) {
|
|
132
|
+
console.log(JSON.stringify(messages, null, 2));
|
|
133
|
+
} else if (messages.length === 0) {
|
|
134
|
+
logger.info("No messages found.");
|
|
135
|
+
} else {
|
|
136
|
+
for (const m of messages) {
|
|
137
|
+
logger.log(` ${chalk.gray(m.sender)} ${m.content.body}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
await shutdown();
|
|
141
|
+
} catch (err) {
|
|
142
|
+
logger.error(`Failed: ${err.message}`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
matrix
|
|
148
|
+
.command("join <room-id>")
|
|
149
|
+
.description("Join a Matrix room")
|
|
150
|
+
.action(async (roomId) => {
|
|
151
|
+
try {
|
|
152
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
153
|
+
if (!ctx.db) {
|
|
154
|
+
logger.error("Database not available");
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
const db = ctx.db.getDatabase();
|
|
158
|
+
ensureMatrixTables(db);
|
|
159
|
+
|
|
160
|
+
const result = joinRoom(db, roomId);
|
|
161
|
+
logger.success(`Joined room ${chalk.cyan(result.room.roomId)}`);
|
|
162
|
+
await shutdown();
|
|
163
|
+
} catch (err) {
|
|
164
|
+
logger.error(`Failed: ${err.message}`);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nostr commands
|
|
3
|
+
* chainlesschain nostr relays|add-relay|publish|events|keygen|map-did
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { logger } from "../lib/logger.js";
|
|
8
|
+
import { bootstrap, shutdown } from "../runtime/bootstrap.js";
|
|
9
|
+
import {
|
|
10
|
+
ensureNostrTables,
|
|
11
|
+
listRelays,
|
|
12
|
+
addRelay,
|
|
13
|
+
publishEvent,
|
|
14
|
+
getEvents,
|
|
15
|
+
generateKeypair,
|
|
16
|
+
mapDid,
|
|
17
|
+
} from "../lib/nostr-bridge.js";
|
|
18
|
+
|
|
19
|
+
export function registerNostrCommand(program) {
|
|
20
|
+
const nostr = program
|
|
21
|
+
.command("nostr")
|
|
22
|
+
.description("Nostr bridge — relays, events, keypairs, DID mapping");
|
|
23
|
+
|
|
24
|
+
nostr
|
|
25
|
+
.command("relays")
|
|
26
|
+
.description("List configured Nostr relays")
|
|
27
|
+
.option("--json", "Output as JSON")
|
|
28
|
+
.action(async (options) => {
|
|
29
|
+
try {
|
|
30
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
31
|
+
if (!ctx.db) {
|
|
32
|
+
logger.error("Database not available");
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
const db = ctx.db.getDatabase();
|
|
36
|
+
ensureNostrTables(db);
|
|
37
|
+
|
|
38
|
+
const relays = listRelays();
|
|
39
|
+
if (options.json) {
|
|
40
|
+
console.log(JSON.stringify(relays, null, 2));
|
|
41
|
+
} else if (relays.length === 0) {
|
|
42
|
+
logger.info(
|
|
43
|
+
"No relays configured. Use `nostr add-relay <url>` to add one.",
|
|
44
|
+
);
|
|
45
|
+
} else {
|
|
46
|
+
for (const r of relays) {
|
|
47
|
+
logger.log(
|
|
48
|
+
` ${chalk.cyan(r.url)} [${r.status}] events=${r.eventCount}`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
await shutdown();
|
|
53
|
+
} catch (err) {
|
|
54
|
+
logger.error(`Failed: ${err.message}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
nostr
|
|
60
|
+
.command("add-relay <url>")
|
|
61
|
+
.description("Add a Nostr relay")
|
|
62
|
+
.action(async (url) => {
|
|
63
|
+
try {
|
|
64
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
65
|
+
if (!ctx.db) {
|
|
66
|
+
logger.error("Database not available");
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
const db = ctx.db.getDatabase();
|
|
70
|
+
ensureNostrTables(db);
|
|
71
|
+
|
|
72
|
+
const relay = addRelay(db, url);
|
|
73
|
+
logger.success(`Relay added: ${chalk.cyan(relay.url)}`);
|
|
74
|
+
await shutdown();
|
|
75
|
+
} catch (err) {
|
|
76
|
+
logger.error(`Failed: ${err.message}`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
nostr
|
|
82
|
+
.command("publish <content>")
|
|
83
|
+
.description("Publish a text note event")
|
|
84
|
+
.option("-k, --kind <n>", "Event kind", "1")
|
|
85
|
+
.option("-p, --pubkey <key>", "Author public key")
|
|
86
|
+
.action(async (content, options) => {
|
|
87
|
+
try {
|
|
88
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
89
|
+
if (!ctx.db) {
|
|
90
|
+
logger.error("Database not available");
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
const db = ctx.db.getDatabase();
|
|
94
|
+
ensureNostrTables(db);
|
|
95
|
+
|
|
96
|
+
const result = publishEvent(
|
|
97
|
+
db,
|
|
98
|
+
parseInt(options.kind),
|
|
99
|
+
content,
|
|
100
|
+
options.pubkey,
|
|
101
|
+
);
|
|
102
|
+
logger.success("Event published");
|
|
103
|
+
logger.log(
|
|
104
|
+
` ${chalk.bold("ID:")} ${chalk.cyan(result.event.id.slice(0, 16))}...`,
|
|
105
|
+
);
|
|
106
|
+
logger.log(` ${chalk.bold("Sent:")} ${result.sentCount} relay(s)`);
|
|
107
|
+
await shutdown();
|
|
108
|
+
} catch (err) {
|
|
109
|
+
logger.error(`Failed: ${err.message}`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
nostr
|
|
115
|
+
.command("events")
|
|
116
|
+
.description("List events")
|
|
117
|
+
.option("-k, --kind <n>", "Filter by event kind")
|
|
118
|
+
.option("-n, --limit <n>", "Max results", "50")
|
|
119
|
+
.option("--json", "Output as JSON")
|
|
120
|
+
.action(async (options) => {
|
|
121
|
+
try {
|
|
122
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
123
|
+
if (!ctx.db) {
|
|
124
|
+
logger.error("Database not available");
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
const db = ctx.db.getDatabase();
|
|
128
|
+
ensureNostrTables(db);
|
|
129
|
+
|
|
130
|
+
const filter = { limit: parseInt(options.limit) };
|
|
131
|
+
if (options.kind) filter.kinds = [parseInt(options.kind)];
|
|
132
|
+
const events = getEvents(filter);
|
|
133
|
+
if (options.json) {
|
|
134
|
+
console.log(JSON.stringify(events, null, 2));
|
|
135
|
+
} else if (events.length === 0) {
|
|
136
|
+
logger.info("No events found.");
|
|
137
|
+
} else {
|
|
138
|
+
for (const e of events) {
|
|
139
|
+
logger.log(
|
|
140
|
+
` ${chalk.cyan(e.id.slice(0, 12))} kind=${e.kind} "${e.content.slice(0, 60)}"`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
await shutdown();
|
|
145
|
+
} catch (err) {
|
|
146
|
+
logger.error(`Failed: ${err.message}`);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
nostr
|
|
152
|
+
.command("keygen")
|
|
153
|
+
.description("Generate a Nostr keypair")
|
|
154
|
+
.option("--json", "Output as JSON")
|
|
155
|
+
.action(async (options) => {
|
|
156
|
+
try {
|
|
157
|
+
const kp = generateKeypair();
|
|
158
|
+
if (options.json) {
|
|
159
|
+
console.log(JSON.stringify(kp, null, 2));
|
|
160
|
+
} else {
|
|
161
|
+
logger.success("Keypair generated");
|
|
162
|
+
logger.log(` ${chalk.bold("Public:")} ${chalk.cyan(kp.publicKey)}`);
|
|
163
|
+
logger.log(
|
|
164
|
+
` ${chalk.bold("Private:")} ${kp.privateKey.slice(0, 16)}...`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
} catch (err) {
|
|
168
|
+
logger.error(`Failed: ${err.message}`);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
nostr
|
|
174
|
+
.command("map-did <did> <pubkey>")
|
|
175
|
+
.description("Map a DID identity to a Nostr pubkey")
|
|
176
|
+
.action(async (did, pubkey) => {
|
|
177
|
+
try {
|
|
178
|
+
const result = mapDid(did, pubkey);
|
|
179
|
+
logger.success(`DID ${chalk.cyan(did)} mapped to Nostr pubkey`);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
logger.error(`Failed: ${err.message}`);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|