chainlesschain 0.47.8 → 0.49.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.
- package/bin/chainlesschain.js +0 -0
- package/package.json +10 -8
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AppLayout-6SPt_8Y_.js → AppLayout-Rvi759IS.js} +1 -1
- package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +1 -0
- package/src/assets/web-panel/assets/{Dashboard-Br7kCwKJ.js → Dashboard-DBhFxXYQ.js} +2 -2
- package/src/assets/web-panel/assets/{index-tN-8TosE.js → index-uL0cZ8N_.js} +2 -2
- package/src/assets/web-panel/index.html +2 -2
- package/src/commands/activitypub.js +533 -0
- package/src/commands/codegen.js +303 -0
- package/src/commands/collab.js +482 -0
- package/src/commands/compliance.js +597 -6
- package/src/commands/crosschain.js +382 -0
- package/src/commands/dbevo.js +388 -0
- package/src/commands/dev.js +411 -0
- package/src/commands/federation.js +427 -0
- package/src/commands/fusion.js +332 -0
- package/src/commands/governance.js +505 -0
- package/src/commands/hardening.js +110 -0
- package/src/commands/incentive.js +373 -0
- package/src/commands/inference.js +304 -0
- package/src/commands/infra.js +361 -0
- package/src/commands/kg.js +371 -0
- package/src/commands/marketplace.js +326 -0
- package/src/commands/matrix.js +283 -0
- package/src/commands/mcp.js +441 -18
- package/src/commands/nlprog.js +329 -0
- package/src/commands/nostr.js +196 -7
- package/src/commands/ops.js +408 -0
- package/src/commands/perception.js +385 -0
- package/src/commands/pqc.js +34 -0
- package/src/commands/privacy.js +345 -0
- package/src/commands/quantization.js +280 -0
- package/src/commands/recommend.js +336 -0
- package/src/commands/reputation.js +349 -0
- package/src/commands/runtime.js +500 -0
- package/src/commands/sla.js +352 -0
- package/src/commands/social.js +265 -0
- package/src/commands/stress.js +252 -0
- package/src/commands/tech.js +268 -0
- package/src/commands/tenant.js +576 -0
- package/src/commands/trust.js +366 -0
- package/src/harness/mcp-client.js +330 -54
- package/src/index.js +114 -0
- package/src/lib/activitypub-bridge.js +623 -0
- package/src/lib/aiops.js +523 -0
- package/src/lib/autonomous-developer.js +524 -0
- package/src/lib/code-agent.js +442 -0
- package/src/lib/collaboration-governance.js +556 -0
- package/src/lib/community-governance.js +649 -0
- package/src/lib/compliance-framework-reporter.js +600 -0
- package/src/lib/content-recommendation.js +600 -0
- package/src/lib/cross-chain.js +669 -0
- package/src/lib/dbevo.js +669 -0
- package/src/lib/decentral-infra.js +445 -0
- package/src/lib/federation-hardening.js +587 -0
- package/src/lib/hardening-manager.js +409 -0
- package/src/lib/inference-network.js +407 -0
- package/src/lib/knowledge-graph.js +530 -0
- package/src/lib/matrix-bridge.js +252 -0
- package/src/lib/mcp-client.js +3 -0
- package/src/lib/mcp-registry.js +347 -0
- package/src/lib/mcp-scaffold.js +385 -0
- package/src/lib/multimodal.js +698 -0
- package/src/lib/nl-programming.js +595 -0
- package/src/lib/nostr-bridge.js +214 -38
- package/src/lib/perception.js +500 -0
- package/src/lib/pqc-manager.js +141 -9
- package/src/lib/privacy-computing.js +575 -0
- package/src/lib/protocol-fusion.js +535 -0
- package/src/lib/quantization.js +362 -0
- package/src/lib/reputation-optimizer.js +509 -0
- package/src/lib/skill-marketplace.js +397 -0
- package/src/lib/sla-manager.js +484 -0
- package/src/lib/social-graph.js +408 -0
- package/src/lib/stix-parser.js +167 -0
- package/src/lib/stress-tester.js +383 -0
- package/src/lib/tech-learning-engine.js +651 -0
- package/src/lib/tenant-saas.js +831 -0
- package/src/lib/threat-intel.js +268 -0
- package/src/lib/token-incentive.js +513 -0
- package/src/lib/topic-classifier.js +400 -0
- package/src/lib/trust-security.js +473 -0
- package/src/lib/ueba.js +403 -0
- package/src/lib/universal-runtime.js +771 -0
- package/src/assets/web-panel/assets/Dashboard-CKeMmCoT.css +0 -1
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SLA commands
|
|
3
|
+
* chainlesschain sla tiers|create|list|show|terminate|record|metrics|check|violations|compensate|report
|
|
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
|
+
ensureSlaTables,
|
|
11
|
+
listTiers,
|
|
12
|
+
createSLA,
|
|
13
|
+
listSLAs,
|
|
14
|
+
getSLA,
|
|
15
|
+
terminateSLA,
|
|
16
|
+
recordMetric,
|
|
17
|
+
getSLAMetrics,
|
|
18
|
+
checkViolations,
|
|
19
|
+
listViolations,
|
|
20
|
+
calculateCompensation,
|
|
21
|
+
generateReport,
|
|
22
|
+
SLA_TERMS,
|
|
23
|
+
VIOLATION_SEVERITY,
|
|
24
|
+
} from "../lib/sla-manager.js";
|
|
25
|
+
|
|
26
|
+
function _dbFromCtx(ctx) {
|
|
27
|
+
if (!ctx.db) {
|
|
28
|
+
logger.error("Database not available");
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
const db = ctx.db.getDatabase();
|
|
32
|
+
ensureSlaTables(db);
|
|
33
|
+
return db;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function registerSlaCommand(program) {
|
|
37
|
+
const sla = program
|
|
38
|
+
.command("sla")
|
|
39
|
+
.description(
|
|
40
|
+
"Cross-org SLA management — contracts, metrics, violations, compensation",
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
sla
|
|
44
|
+
.command("tiers")
|
|
45
|
+
.description("List built-in SLA tiers")
|
|
46
|
+
.option("--json", "Output as JSON")
|
|
47
|
+
.action((options) => {
|
|
48
|
+
const tiers = listTiers();
|
|
49
|
+
if (options.json) {
|
|
50
|
+
console.log(JSON.stringify(tiers, null, 2));
|
|
51
|
+
} else {
|
|
52
|
+
for (const t of tiers) {
|
|
53
|
+
logger.log(
|
|
54
|
+
` ${chalk.cyan(t.name.padEnd(8))} availability=${t.availability} p95<=${t.maxResponseTime}ms rps>=${t.minThroughput} comp=${(t.compensationRate * 100).toFixed(1)}%`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
sla
|
|
61
|
+
.command("create <org-id>")
|
|
62
|
+
.description("Create a new SLA contract")
|
|
63
|
+
.option("-t, --tier <tier>", "SLA tier (gold|silver|bronze)", "silver")
|
|
64
|
+
.option("-d, --duration <ms>", "Contract duration in ms", parseInt)
|
|
65
|
+
.option("-f, --fee <amount>", "Monthly fee", parseFloat, 0)
|
|
66
|
+
.option("--json", "Output as JSON")
|
|
67
|
+
.action(async (orgId, options) => {
|
|
68
|
+
try {
|
|
69
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
70
|
+
const db = _dbFromCtx(ctx);
|
|
71
|
+
const contract = createSLA(db, {
|
|
72
|
+
orgId,
|
|
73
|
+
tier: options.tier,
|
|
74
|
+
duration: options.duration,
|
|
75
|
+
monthlyFee: options.fee,
|
|
76
|
+
});
|
|
77
|
+
if (options.json) {
|
|
78
|
+
console.log(JSON.stringify(contract, null, 2));
|
|
79
|
+
} else {
|
|
80
|
+
logger.success(`SLA contract created`);
|
|
81
|
+
logger.log(
|
|
82
|
+
` ${chalk.bold("SLA ID:")} ${chalk.cyan(contract.slaId)}`,
|
|
83
|
+
);
|
|
84
|
+
logger.log(` ${chalk.bold("Org:")} ${contract.orgId}`);
|
|
85
|
+
logger.log(` ${chalk.bold("Tier:")} ${contract.tier}`);
|
|
86
|
+
logger.log(` ${chalk.bold("Fee:")} ${contract.monthlyFee}/month`);
|
|
87
|
+
logger.log(
|
|
88
|
+
` ${chalk.bold("Expires:")} ${new Date(contract.endDate).toISOString()}`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
await shutdown();
|
|
92
|
+
} catch (err) {
|
|
93
|
+
logger.error(`Failed: ${err.message}`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
sla
|
|
99
|
+
.command("list")
|
|
100
|
+
.description("List SLA contracts")
|
|
101
|
+
.option("-o, --org <id>", "Filter by orgId")
|
|
102
|
+
.option("-t, --tier <tier>", "Filter by tier")
|
|
103
|
+
.option(
|
|
104
|
+
"-s, --status <status>",
|
|
105
|
+
"Filter by status (active|expired|terminated)",
|
|
106
|
+
)
|
|
107
|
+
.option("--limit <n>", "Maximum entries", parseInt, 50)
|
|
108
|
+
.option("--json", "Output as JSON")
|
|
109
|
+
.action(async (options) => {
|
|
110
|
+
try {
|
|
111
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
112
|
+
_dbFromCtx(ctx);
|
|
113
|
+
const rows = listSLAs({
|
|
114
|
+
orgId: options.org,
|
|
115
|
+
tier: options.tier,
|
|
116
|
+
status: options.status,
|
|
117
|
+
limit: options.limit,
|
|
118
|
+
});
|
|
119
|
+
if (options.json) {
|
|
120
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
121
|
+
} else if (rows.length === 0) {
|
|
122
|
+
logger.info("No SLA contracts recorded.");
|
|
123
|
+
} else {
|
|
124
|
+
for (const r of rows) {
|
|
125
|
+
logger.log(
|
|
126
|
+
` ${chalk.cyan(r.slaId.slice(0, 8))} org=${r.orgId.padEnd(16)} tier=${r.tier.padEnd(7)} [${r.status}]`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
await shutdown();
|
|
131
|
+
} catch (err) {
|
|
132
|
+
logger.error(`Failed: ${err.message}`);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
sla
|
|
138
|
+
.command("show <sla-id>")
|
|
139
|
+
.description("Show a single SLA contract")
|
|
140
|
+
.option("--json", "Output as JSON")
|
|
141
|
+
.action(async (slaId, options) => {
|
|
142
|
+
try {
|
|
143
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
144
|
+
_dbFromCtx(ctx);
|
|
145
|
+
const r = getSLA(slaId);
|
|
146
|
+
if (options.json) {
|
|
147
|
+
console.log(JSON.stringify(r, null, 2));
|
|
148
|
+
} else {
|
|
149
|
+
logger.log(` ${chalk.bold("SLA ID:")} ${chalk.cyan(r.slaId)}`);
|
|
150
|
+
logger.log(` ${chalk.bold("Org:")} ${r.orgId}`);
|
|
151
|
+
logger.log(` ${chalk.bold("Tier:")} ${r.tier}`);
|
|
152
|
+
logger.log(` ${chalk.bold("Status:")} ${r.status}`);
|
|
153
|
+
logger.log(` ${chalk.bold("Terms:")} ${JSON.stringify(r.terms)}`);
|
|
154
|
+
}
|
|
155
|
+
await shutdown();
|
|
156
|
+
} catch (err) {
|
|
157
|
+
logger.error(`Failed: ${err.message}`);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
sla
|
|
163
|
+
.command("terminate <sla-id>")
|
|
164
|
+
.description("Terminate an SLA contract")
|
|
165
|
+
.action(async (slaId) => {
|
|
166
|
+
try {
|
|
167
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
168
|
+
const db = _dbFromCtx(ctx);
|
|
169
|
+
const r = terminateSLA(db, slaId);
|
|
170
|
+
logger.success(`SLA ${slaId.slice(0, 8)} → ${r.status}`);
|
|
171
|
+
await shutdown();
|
|
172
|
+
} catch (err) {
|
|
173
|
+
logger.error(`Failed: ${err.message}`);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
sla
|
|
179
|
+
.command("record <sla-id> <term> <value>")
|
|
180
|
+
.description(
|
|
181
|
+
`Record a metric (term: ${Object.values(SLA_TERMS).join("|")})`,
|
|
182
|
+
)
|
|
183
|
+
.option("--json", "Output as JSON")
|
|
184
|
+
.action(async (slaId, term, value, options) => {
|
|
185
|
+
try {
|
|
186
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
187
|
+
const db = _dbFromCtx(ctx);
|
|
188
|
+
const m = recordMetric(db, slaId, term, Number(value));
|
|
189
|
+
if (options.json) {
|
|
190
|
+
console.log(JSON.stringify(m, null, 2));
|
|
191
|
+
} else {
|
|
192
|
+
logger.success(`Metric recorded`);
|
|
193
|
+
logger.log(` ${chalk.bold("Term:")} ${m.term}`);
|
|
194
|
+
logger.log(` ${chalk.bold("Value:")} ${m.value}`);
|
|
195
|
+
}
|
|
196
|
+
await shutdown();
|
|
197
|
+
} catch (err) {
|
|
198
|
+
logger.error(`Failed: ${err.message}`);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
sla
|
|
204
|
+
.command("metrics <sla-id>")
|
|
205
|
+
.description("Show aggregated metrics for an SLA")
|
|
206
|
+
.option("--json", "Output as JSON")
|
|
207
|
+
.action(async (slaId, options) => {
|
|
208
|
+
try {
|
|
209
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
210
|
+
_dbFromCtx(ctx);
|
|
211
|
+
const m = getSLAMetrics(slaId);
|
|
212
|
+
if (options.json) {
|
|
213
|
+
console.log(JSON.stringify(m, null, 2));
|
|
214
|
+
} else {
|
|
215
|
+
logger.log(` ${chalk.bold("Samples:")} ${m.totalSamples}`);
|
|
216
|
+
for (const [term, agg] of Object.entries(m.byTerm)) {
|
|
217
|
+
logger.log(
|
|
218
|
+
` ${chalk.cyan(term.padEnd(15))} mean=${agg.mean.toFixed(4)} p95=${agg.p95.toFixed(4)} (n=${agg.count})`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
await shutdown();
|
|
223
|
+
} catch (err) {
|
|
224
|
+
logger.error(`Failed: ${err.message}`);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
sla
|
|
230
|
+
.command("check <sla-id>")
|
|
231
|
+
.description("Detect violations against the contract's terms")
|
|
232
|
+
.option("--json", "Output as JSON")
|
|
233
|
+
.action(async (slaId, options) => {
|
|
234
|
+
try {
|
|
235
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
236
|
+
const db = _dbFromCtx(ctx);
|
|
237
|
+
const r = checkViolations(db, slaId);
|
|
238
|
+
if (options.json) {
|
|
239
|
+
console.log(JSON.stringify(r, null, 2));
|
|
240
|
+
} else if (r.totalViolations === 0) {
|
|
241
|
+
logger.success("No violations detected.");
|
|
242
|
+
} else {
|
|
243
|
+
logger.warn(`${r.totalViolations} violation(s) detected`);
|
|
244
|
+
for (const v of r.violations) {
|
|
245
|
+
logger.log(
|
|
246
|
+
` ${chalk.yellow(v.term.padEnd(15))} [${v.severity}] expected=${v.expectedValue} actual=${v.actualValue} dev=${v.deviationPercent}%`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
await shutdown();
|
|
251
|
+
} catch (err) {
|
|
252
|
+
logger.error(`Failed: ${err.message}`);
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
sla
|
|
258
|
+
.command("violations")
|
|
259
|
+
.description("List recorded violations")
|
|
260
|
+
.option("-s, --sla <id>", "Filter by SLA ID")
|
|
261
|
+
.option(
|
|
262
|
+
"-S, --severity <level>",
|
|
263
|
+
`Filter by severity (${Object.values(VIOLATION_SEVERITY).join("|")})`,
|
|
264
|
+
)
|
|
265
|
+
.option("--limit <n>", "Maximum entries", parseInt, 50)
|
|
266
|
+
.option("--json", "Output as JSON")
|
|
267
|
+
.action(async (options) => {
|
|
268
|
+
try {
|
|
269
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
270
|
+
_dbFromCtx(ctx);
|
|
271
|
+
const rows = listViolations({
|
|
272
|
+
slaId: options.sla,
|
|
273
|
+
severity: options.severity,
|
|
274
|
+
limit: options.limit,
|
|
275
|
+
});
|
|
276
|
+
if (options.json) {
|
|
277
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
278
|
+
} else if (rows.length === 0) {
|
|
279
|
+
logger.info("No violations recorded.");
|
|
280
|
+
} else {
|
|
281
|
+
for (const v of rows) {
|
|
282
|
+
logger.log(
|
|
283
|
+
` ${chalk.cyan(v.violationId.slice(0, 8))} ${v.term.padEnd(15)} [${v.severity}] dev=${v.deviationPercent}%`,
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
await shutdown();
|
|
288
|
+
} catch (err) {
|
|
289
|
+
logger.error(`Failed: ${err.message}`);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
sla
|
|
295
|
+
.command("compensate <violation-id>")
|
|
296
|
+
.description("Calculate compensation for a violation")
|
|
297
|
+
.option("--json", "Output as JSON")
|
|
298
|
+
.action(async (violationId, options) => {
|
|
299
|
+
try {
|
|
300
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
301
|
+
const db = _dbFromCtx(ctx);
|
|
302
|
+
const r = calculateCompensation(db, violationId);
|
|
303
|
+
if (options.json) {
|
|
304
|
+
console.log(JSON.stringify(r, null, 2));
|
|
305
|
+
} else {
|
|
306
|
+
logger.log(` ${chalk.bold("Base:")} ${r.base}`);
|
|
307
|
+
logger.log(` ${chalk.bold("Multiplier:")} ${r.multiplier}`);
|
|
308
|
+
logger.log(` ${chalk.bold("Amount:")} ${chalk.green(r.amount)}`);
|
|
309
|
+
}
|
|
310
|
+
await shutdown();
|
|
311
|
+
} catch (err) {
|
|
312
|
+
logger.error(`Failed: ${err.message}`);
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
sla
|
|
318
|
+
.command("report <sla-id>")
|
|
319
|
+
.description("Generate an SLA compliance report")
|
|
320
|
+
.option("--start <ms>", "Start of window (ms since epoch)", parseInt)
|
|
321
|
+
.option("--end <ms>", "End of window (ms since epoch)", parseInt)
|
|
322
|
+
.option("--json", "Output as JSON")
|
|
323
|
+
.action(async (slaId, options) => {
|
|
324
|
+
try {
|
|
325
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
326
|
+
_dbFromCtx(ctx);
|
|
327
|
+
const r = generateReport(slaId, {
|
|
328
|
+
startDate: options.start,
|
|
329
|
+
endDate: options.end,
|
|
330
|
+
});
|
|
331
|
+
if (options.json) {
|
|
332
|
+
console.log(JSON.stringify(r, null, 2));
|
|
333
|
+
} else {
|
|
334
|
+
logger.log(` ${chalk.bold("SLA:")} ${chalk.cyan(r.slaId)}`);
|
|
335
|
+
logger.log(` ${chalk.bold("Tier:")} ${r.tier}`);
|
|
336
|
+
logger.log(
|
|
337
|
+
` ${chalk.bold("Compliance:")} ${(r.compliance * 100).toFixed(2)}%`,
|
|
338
|
+
);
|
|
339
|
+
logger.log(
|
|
340
|
+
` ${chalk.bold("Violations:")} ${r.violations.total} (comp=${r.violations.totalCompensation})`,
|
|
341
|
+
);
|
|
342
|
+
for (const [sev, n] of Object.entries(r.violations.bySeverity)) {
|
|
343
|
+
if (n > 0) logger.log(` ${chalk.yellow(sev.padEnd(10))} ${n}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
await shutdown();
|
|
347
|
+
} catch (err) {
|
|
348
|
+
logger.error(`Failed: ${err.message}`);
|
|
349
|
+
process.exit(1);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
}
|
package/src/commands/social.js
CHANGED
|
@@ -24,6 +24,17 @@ import {
|
|
|
24
24
|
getChatThreads,
|
|
25
25
|
getSocialStats,
|
|
26
26
|
} from "../lib/social-manager.js";
|
|
27
|
+
import { classifyTopic, detectLanguage } from "../lib/topic-classifier.js";
|
|
28
|
+
import {
|
|
29
|
+
ensureGraphTables,
|
|
30
|
+
addEdge as graphAddEdge,
|
|
31
|
+
removeEdge as graphRemoveEdge,
|
|
32
|
+
getNeighbors as graphGetNeighbors,
|
|
33
|
+
getGraphSnapshot,
|
|
34
|
+
loadFromDb as graphLoadFromDb,
|
|
35
|
+
subscribe as graphSubscribe,
|
|
36
|
+
EDGE_TYPES,
|
|
37
|
+
} from "../lib/social-graph.js";
|
|
27
38
|
|
|
28
39
|
export function registerSocialCommand(program) {
|
|
29
40
|
const social = program
|
|
@@ -477,4 +488,258 @@ export function registerSocialCommand(program) {
|
|
|
477
488
|
process.exit(1);
|
|
478
489
|
}
|
|
479
490
|
});
|
|
491
|
+
|
|
492
|
+
// ── Analyze (topic classification, language-aware) ──────────
|
|
493
|
+
|
|
494
|
+
social
|
|
495
|
+
.command("analyze <text>")
|
|
496
|
+
.description("Classify text into topics (language-aware, multilingual)")
|
|
497
|
+
.option("-k, --top-k <n>", "Top-K topics to return", "3")
|
|
498
|
+
.option("--lang <code>", "Override detected language (zh|ja|en|other)")
|
|
499
|
+
.option("--min-score <n>", "Drop topics with rawScore <= this", "0")
|
|
500
|
+
.option("--json", "Output as JSON")
|
|
501
|
+
.action((text, options) => {
|
|
502
|
+
try {
|
|
503
|
+
const topK = Math.max(1, parseInt(options.topK, 10) || 3);
|
|
504
|
+
const minScore = Number(options.minScore) || 0;
|
|
505
|
+
const result = classifyTopic(text, {
|
|
506
|
+
topK,
|
|
507
|
+
lang: options.lang,
|
|
508
|
+
minScore,
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
if (options.json) {
|
|
512
|
+
console.log(JSON.stringify(result, null, 2));
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
logger.log(
|
|
517
|
+
` ${chalk.bold("Language:")} ${chalk.cyan(result.language)}`,
|
|
518
|
+
);
|
|
519
|
+
logger.log(` ${chalk.bold("Tokens:")} ${result.tokens.length}`);
|
|
520
|
+
if (result.topics.length === 0) {
|
|
521
|
+
logger.log(` ${chalk.dim("(no topic matched)")}`);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
logger.log(` ${chalk.bold("Top topics:")}`);
|
|
525
|
+
for (const t of result.topics) {
|
|
526
|
+
const pct = (t.score * 100).toFixed(1);
|
|
527
|
+
logger.log(
|
|
528
|
+
` ${chalk.cyan(t.topic.padEnd(16))} ${pct}% ` +
|
|
529
|
+
`${chalk.dim(`(raw=${t.rawScore}, hits=${t.hits})`)}`,
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
} catch (err) {
|
|
533
|
+
logger.error(`Failed: ${err.message}`);
|
|
534
|
+
process.exit(1);
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// Language-only quick helper.
|
|
539
|
+
social
|
|
540
|
+
.command("detect-lang <text>")
|
|
541
|
+
.description("Detect dominant language of text (zh|ja|en|other)")
|
|
542
|
+
.option("--json", "Output as JSON")
|
|
543
|
+
.action((text, options) => {
|
|
544
|
+
const language = detectLanguage(text);
|
|
545
|
+
if (options.json) {
|
|
546
|
+
console.log(JSON.stringify({ language }));
|
|
547
|
+
} else {
|
|
548
|
+
logger.log(chalk.cyan(language));
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// ── Social Graph (realtime) ─────────────────────────────────
|
|
553
|
+
|
|
554
|
+
const graph = social
|
|
555
|
+
.command("graph")
|
|
556
|
+
.description("Social graph — typed edges, neighbors, live event stream");
|
|
557
|
+
|
|
558
|
+
graph
|
|
559
|
+
.command("add-edge <source> <target>")
|
|
560
|
+
.description(`Add a directed edge (types: ${EDGE_TYPES.join("|")})`)
|
|
561
|
+
.option("-t, --type <type>", "Edge type", "follow")
|
|
562
|
+
.option("-w, --weight <n>", "Edge weight", "1.0")
|
|
563
|
+
.option("-m, --metadata <json>", "JSON-encoded metadata")
|
|
564
|
+
.option("--json", "Output as JSON")
|
|
565
|
+
.action(async (source, target, options) => {
|
|
566
|
+
try {
|
|
567
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
568
|
+
if (!ctx.db) {
|
|
569
|
+
logger.error("Database not available");
|
|
570
|
+
process.exit(1);
|
|
571
|
+
}
|
|
572
|
+
const db = ctx.db.getDatabase();
|
|
573
|
+
ensureGraphTables(db);
|
|
574
|
+
graphLoadFromDb(db);
|
|
575
|
+
|
|
576
|
+
const metadata = options.metadata ? JSON.parse(options.metadata) : null;
|
|
577
|
+
const result = graphAddEdge(db, source, target, options.type, {
|
|
578
|
+
weight: Number(options.weight) || 1.0,
|
|
579
|
+
metadata,
|
|
580
|
+
});
|
|
581
|
+
if (options.json) {
|
|
582
|
+
console.log(JSON.stringify(result, null, 2));
|
|
583
|
+
} else {
|
|
584
|
+
const verb = result.created ? "added" : "updated";
|
|
585
|
+
logger.success(
|
|
586
|
+
`Edge ${verb}: ${chalk.cyan(source)} --${options.type}→ ${chalk.cyan(target)}`,
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
await shutdown();
|
|
590
|
+
} catch (err) {
|
|
591
|
+
logger.error(`Failed: ${err.message}`);
|
|
592
|
+
process.exit(1);
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
graph
|
|
597
|
+
.command("remove-edge <source> <target>")
|
|
598
|
+
.description("Remove a directed edge")
|
|
599
|
+
.option("-t, --type <type>", "Edge type", "follow")
|
|
600
|
+
.option("--json", "Output as JSON")
|
|
601
|
+
.action(async (source, target, options) => {
|
|
602
|
+
try {
|
|
603
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
604
|
+
if (!ctx.db) {
|
|
605
|
+
logger.error("Database not available");
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
608
|
+
const db = ctx.db.getDatabase();
|
|
609
|
+
ensureGraphTables(db);
|
|
610
|
+
graphLoadFromDb(db);
|
|
611
|
+
|
|
612
|
+
const result = graphRemoveEdge(db, source, target, options.type);
|
|
613
|
+
if (options.json) {
|
|
614
|
+
console.log(JSON.stringify(result, null, 2));
|
|
615
|
+
} else if (result.removed) {
|
|
616
|
+
logger.success(
|
|
617
|
+
`Edge removed: ${chalk.cyan(source)} --${options.type}→ ${chalk.cyan(target)}`,
|
|
618
|
+
);
|
|
619
|
+
} else {
|
|
620
|
+
logger.warn("Edge not found");
|
|
621
|
+
}
|
|
622
|
+
await shutdown();
|
|
623
|
+
} catch (err) {
|
|
624
|
+
logger.error(`Failed: ${err.message}`);
|
|
625
|
+
process.exit(1);
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
graph
|
|
630
|
+
.command("neighbors <did>")
|
|
631
|
+
.description("List neighbors of a DID")
|
|
632
|
+
.option("-d, --direction <dir>", "Direction: out | in | both", "both")
|
|
633
|
+
.option("-t, --type <type>", "Filter by edge type")
|
|
634
|
+
.option("--json", "Output as JSON")
|
|
635
|
+
.action(async (did, options) => {
|
|
636
|
+
try {
|
|
637
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
638
|
+
if (!ctx.db) {
|
|
639
|
+
logger.error("Database not available");
|
|
640
|
+
process.exit(1);
|
|
641
|
+
}
|
|
642
|
+
const db = ctx.db.getDatabase();
|
|
643
|
+
ensureGraphTables(db);
|
|
644
|
+
graphLoadFromDb(db);
|
|
645
|
+
|
|
646
|
+
const neighbors = graphGetNeighbors(did, {
|
|
647
|
+
direction: options.direction,
|
|
648
|
+
edgeType: options.type,
|
|
649
|
+
});
|
|
650
|
+
if (options.json) {
|
|
651
|
+
console.log(JSON.stringify({ did, neighbors }, null, 2));
|
|
652
|
+
} else if (neighbors.length === 0) {
|
|
653
|
+
logger.log(chalk.dim("(no neighbors)"));
|
|
654
|
+
} else {
|
|
655
|
+
for (const n of neighbors) logger.log(` ${chalk.cyan(n)}`);
|
|
656
|
+
}
|
|
657
|
+
await shutdown();
|
|
658
|
+
} catch (err) {
|
|
659
|
+
logger.error(`Failed: ${err.message}`);
|
|
660
|
+
process.exit(1);
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
graph
|
|
665
|
+
.command("snapshot")
|
|
666
|
+
.description("Dump the full graph (nodes + edges + stats)")
|
|
667
|
+
.option("-t, --type <type>", "Filter by edge type")
|
|
668
|
+
.option("--json", "Output as JSON (default: yes — snapshot is raw)")
|
|
669
|
+
.action(async (options) => {
|
|
670
|
+
try {
|
|
671
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
672
|
+
if (!ctx.db) {
|
|
673
|
+
logger.error("Database not available");
|
|
674
|
+
process.exit(1);
|
|
675
|
+
}
|
|
676
|
+
const db = ctx.db.getDatabase();
|
|
677
|
+
ensureGraphTables(db);
|
|
678
|
+
graphLoadFromDb(db);
|
|
679
|
+
|
|
680
|
+
const snapshot = getGraphSnapshot({ edgeType: options.type });
|
|
681
|
+
console.log(JSON.stringify(snapshot, null, 2));
|
|
682
|
+
await shutdown();
|
|
683
|
+
} catch (err) {
|
|
684
|
+
logger.error(`Failed: ${err.message}`);
|
|
685
|
+
process.exit(1);
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
graph
|
|
690
|
+
.command("watch")
|
|
691
|
+
.description("Stream graph change events as NDJSON on stdout")
|
|
692
|
+
.option("-e, --events <list>", "Comma-separated event types (default: all)")
|
|
693
|
+
.option("--once", "Emit the first event then exit")
|
|
694
|
+
.action(async (options) => {
|
|
695
|
+
try {
|
|
696
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
697
|
+
if (!ctx.db) {
|
|
698
|
+
logger.error("Database not available");
|
|
699
|
+
process.exit(1);
|
|
700
|
+
}
|
|
701
|
+
const db = ctx.db.getDatabase();
|
|
702
|
+
ensureGraphTables(db);
|
|
703
|
+
graphLoadFromDb(db);
|
|
704
|
+
|
|
705
|
+
const events = options.events
|
|
706
|
+
? options.events
|
|
707
|
+
.split(",")
|
|
708
|
+
.map((s) => s.trim())
|
|
709
|
+
.filter(Boolean)
|
|
710
|
+
: null;
|
|
711
|
+
|
|
712
|
+
// Announce subscription on stdout so pipelines know the stream is live.
|
|
713
|
+
process.stdout.write(
|
|
714
|
+
JSON.stringify({
|
|
715
|
+
type: "watch.started",
|
|
716
|
+
events: events || "all",
|
|
717
|
+
at: new Date().toISOString(),
|
|
718
|
+
}) + "\n",
|
|
719
|
+
);
|
|
720
|
+
|
|
721
|
+
let unsubscribe = null;
|
|
722
|
+
const stop = async () => {
|
|
723
|
+
if (unsubscribe) unsubscribe();
|
|
724
|
+
await shutdown();
|
|
725
|
+
process.exit(0);
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
unsubscribe = graphSubscribe(
|
|
729
|
+
(evt) => {
|
|
730
|
+
process.stdout.write(
|
|
731
|
+
JSON.stringify({ ...evt, at: new Date().toISOString() }) + "\n",
|
|
732
|
+
);
|
|
733
|
+
if (options.once) void stop();
|
|
734
|
+
},
|
|
735
|
+
events ? { events } : undefined,
|
|
736
|
+
);
|
|
737
|
+
|
|
738
|
+
process.on("SIGINT", stop);
|
|
739
|
+
process.on("SIGTERM", stop);
|
|
740
|
+
} catch (err) {
|
|
741
|
+
logger.error(`Failed: ${err.message}`);
|
|
742
|
+
process.exit(1);
|
|
743
|
+
}
|
|
744
|
+
});
|
|
480
745
|
}
|