chainlesschain 0.51.0 → 0.66.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/package.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AppLayout-Rvi759IS.js → AppLayout-6SPt_8Y_.js} +1 -1
- package/src/assets/web-panel/assets/{Dashboard-DBhFxXYQ.js → Dashboard-Br7kCwKJ.js} +2 -2
- package/src/assets/web-panel/assets/Dashboard-CKeMmCoT.css +1 -0
- package/src/assets/web-panel/assets/{index-uL0cZ8N_.js → index-tN-8TosE.js} +2 -2
- package/src/assets/web-panel/index.html +2 -2
- package/src/commands/agent-network.js +785 -0
- package/src/commands/automation.js +654 -0
- package/src/commands/dao.js +565 -0
- package/src/commands/did-v2.js +620 -0
- package/src/commands/economy.js +578 -0
- package/src/commands/evolution.js +391 -0
- package/src/commands/hmemory.js +442 -0
- package/src/commands/perf.js +433 -0
- package/src/commands/pipeline.js +449 -0
- package/src/commands/plugin-ecosystem.js +517 -0
- package/src/commands/sandbox.js +401 -0
- package/src/commands/social.js +311 -0
- package/src/commands/sso.js +798 -0
- package/src/commands/workflow.js +320 -0
- package/src/commands/zkp.js +227 -1
- package/src/index.js +21 -0
- package/src/lib/agent-economy.js +479 -0
- package/src/lib/agent-network.js +1121 -0
- package/src/lib/automation-engine.js +948 -0
- package/src/lib/dao-governance.js +569 -0
- package/src/lib/did-v2-manager.js +1127 -0
- package/src/lib/evolution-system.js +453 -0
- package/src/lib/hierarchical-memory.js +481 -0
- package/src/lib/perf-tuning.js +734 -0
- package/src/lib/pipeline-orchestrator.js +928 -0
- package/src/lib/plugin-ecosystem.js +1109 -0
- package/src/lib/sandbox-v2.js +306 -0
- package/src/lib/social-graph-analytics.js +707 -0
- package/src/lib/sso-manager.js +841 -0
- package/src/lib/workflow-engine.js +454 -1
- package/src/lib/zkp-engine.js +249 -20
- package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +0 -1
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cc ecosystem` — CLI port of Phase 64 智能插件生态 2.0.
|
|
3
|
+
*
|
|
4
|
+
* Heuristic / record-keeping only — AI recommender, ML code review,
|
|
5
|
+
* real sandbox isolation, packaging and payment gateway stay Desktop.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
REVIEW_SEVERITY,
|
|
12
|
+
PUBLISH_STATUS,
|
|
13
|
+
REVENUE_TYPE,
|
|
14
|
+
INSTALL_STATUS,
|
|
15
|
+
DEP_KIND,
|
|
16
|
+
SANDBOX_RESULT,
|
|
17
|
+
ensurePluginEcosystemTables,
|
|
18
|
+
registerPlugin,
|
|
19
|
+
getPlugin,
|
|
20
|
+
listPlugins,
|
|
21
|
+
updatePluginStats,
|
|
22
|
+
addDependency,
|
|
23
|
+
listDependencies,
|
|
24
|
+
resolveDependencies,
|
|
25
|
+
installPlugin,
|
|
26
|
+
listInstalls,
|
|
27
|
+
uninstallPlugin,
|
|
28
|
+
aiReviewCode,
|
|
29
|
+
getReview,
|
|
30
|
+
listReviews,
|
|
31
|
+
recordSandboxTest,
|
|
32
|
+
getSandboxTest,
|
|
33
|
+
listSandboxTests,
|
|
34
|
+
submitForReview,
|
|
35
|
+
approvePlugin,
|
|
36
|
+
rejectPlugin,
|
|
37
|
+
publishPlugin,
|
|
38
|
+
recordRevenue,
|
|
39
|
+
getDeveloperRevenue,
|
|
40
|
+
recommend,
|
|
41
|
+
getConfig,
|
|
42
|
+
getStats,
|
|
43
|
+
} from "../lib/plugin-ecosystem.js";
|
|
44
|
+
|
|
45
|
+
function _dbFromCtx(cmd) {
|
|
46
|
+
const root = cmd?.parent?.parent ?? cmd?.parent;
|
|
47
|
+
return root?._db;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function _json(v) {
|
|
51
|
+
console.log(JSON.stringify(v, null, 2));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function _parseJsonArg(s, fallback) {
|
|
55
|
+
if (s == null) return fallback;
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(s);
|
|
58
|
+
} catch {
|
|
59
|
+
return fallback;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function registerPluginEcosystemCommand(program) {
|
|
64
|
+
const eco = new Command("ecosystem")
|
|
65
|
+
.alias("eco")
|
|
66
|
+
.description(
|
|
67
|
+
"Plugin Ecosystem 2.0 (Phase 64) — registry + deps + install + AI review + publish + revenue + recommend",
|
|
68
|
+
)
|
|
69
|
+
.hook("preAction", (thisCmd) => {
|
|
70
|
+
const db = _dbFromCtx(thisCmd);
|
|
71
|
+
if (db) ensurePluginEcosystemTables(db);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
/* ── Catalogs ────────────────────────────────────── */
|
|
75
|
+
|
|
76
|
+
eco
|
|
77
|
+
.command("config")
|
|
78
|
+
.description(
|
|
79
|
+
"Show ecosystem config (severities, statuses, rules, defaults)",
|
|
80
|
+
)
|
|
81
|
+
.action(() => _json(getConfig()));
|
|
82
|
+
|
|
83
|
+
eco
|
|
84
|
+
.command("severities")
|
|
85
|
+
.description("List review severities")
|
|
86
|
+
.action(() => _json(Object.values(REVIEW_SEVERITY)));
|
|
87
|
+
|
|
88
|
+
eco
|
|
89
|
+
.command("statuses")
|
|
90
|
+
.description("List publish / install statuses")
|
|
91
|
+
.action(() =>
|
|
92
|
+
_json({
|
|
93
|
+
publish: Object.values(PUBLISH_STATUS),
|
|
94
|
+
install: Object.values(INSTALL_STATUS),
|
|
95
|
+
sandbox: Object.values(SANDBOX_RESULT),
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
eco
|
|
100
|
+
.command("revenue-types")
|
|
101
|
+
.description("List revenue event types")
|
|
102
|
+
.action(() => _json(Object.values(REVENUE_TYPE)));
|
|
103
|
+
|
|
104
|
+
eco
|
|
105
|
+
.command("dep-kinds")
|
|
106
|
+
.description("List dependency kinds")
|
|
107
|
+
.action(() => _json(Object.values(DEP_KIND)));
|
|
108
|
+
|
|
109
|
+
eco
|
|
110
|
+
.command("rules")
|
|
111
|
+
.description("List heuristic AI review rules")
|
|
112
|
+
.action(() => _json(getConfig().reviewRules));
|
|
113
|
+
|
|
114
|
+
/* ── Plugin registry ─────────────────────────────── */
|
|
115
|
+
|
|
116
|
+
eco
|
|
117
|
+
.command("register")
|
|
118
|
+
.description("Register a new plugin (status=draft)")
|
|
119
|
+
.requiredOption("-n, --name <name>", "Plugin name")
|
|
120
|
+
.requiredOption("-v, --version <ver>", "Plugin version")
|
|
121
|
+
.requiredOption("-d, --developer <id>", "Developer ID")
|
|
122
|
+
.option("-c, --category <cat>", "Category")
|
|
123
|
+
.option("-D, --description <text>", "Description")
|
|
124
|
+
.option("-m, --manifest <json>", "Manifest JSON")
|
|
125
|
+
.action((opts, cmd) => {
|
|
126
|
+
const db = _dbFromCtx(cmd);
|
|
127
|
+
if (!db) return console.error("No database");
|
|
128
|
+
_json(
|
|
129
|
+
registerPlugin(db, {
|
|
130
|
+
name: opts.name,
|
|
131
|
+
version: opts.version,
|
|
132
|
+
developerId: opts.developer,
|
|
133
|
+
category: opts.category || null,
|
|
134
|
+
description: opts.description || null,
|
|
135
|
+
manifest: _parseJsonArg(opts.manifest, {}),
|
|
136
|
+
}),
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
eco
|
|
141
|
+
.command("plugins")
|
|
142
|
+
.description("List plugins")
|
|
143
|
+
.option("-c, --category <cat>", "Filter by category")
|
|
144
|
+
.option("-s, --status <status>", "Filter by status")
|
|
145
|
+
.option("-d, --developer <id>", "Filter by developer")
|
|
146
|
+
.option("-l, --limit <n>", "Max rows", (v) => parseInt(v, 10), 100)
|
|
147
|
+
.action((opts, cmd) => {
|
|
148
|
+
const db = _dbFromCtx(cmd);
|
|
149
|
+
if (!db) return console.error("No database");
|
|
150
|
+
_json(
|
|
151
|
+
listPlugins(db, {
|
|
152
|
+
category: opts.category,
|
|
153
|
+
status: opts.status,
|
|
154
|
+
developerId: opts.developer,
|
|
155
|
+
limit: opts.limit,
|
|
156
|
+
}),
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
eco
|
|
161
|
+
.command("show <pluginId>")
|
|
162
|
+
.description("Show plugin details")
|
|
163
|
+
.action((pluginId, _opts, cmd) => {
|
|
164
|
+
const db = _dbFromCtx(cmd);
|
|
165
|
+
if (!db) return console.error("No database");
|
|
166
|
+
const p = getPlugin(db, pluginId);
|
|
167
|
+
if (!p) return console.error(`Plugin not found: ${pluginId}`);
|
|
168
|
+
_json(p);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
eco
|
|
172
|
+
.command("update-stats <pluginId>")
|
|
173
|
+
.description("Update download count / avg rating")
|
|
174
|
+
.option("-d, --downloads <n>", "Download count", (v) => parseInt(v, 10))
|
|
175
|
+
.option("-r, --rating <r>", "Avg rating", (v) => parseFloat(v))
|
|
176
|
+
.action((pluginId, opts, cmd) => {
|
|
177
|
+
const db = _dbFromCtx(cmd);
|
|
178
|
+
if (!db) return console.error("No database");
|
|
179
|
+
_json(
|
|
180
|
+
updatePluginStats(db, pluginId, {
|
|
181
|
+
downloadCount: opts.downloads ?? null,
|
|
182
|
+
avgRating: opts.rating ?? null,
|
|
183
|
+
}),
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
/* ── Dependencies ────────────────────────────────── */
|
|
188
|
+
|
|
189
|
+
eco
|
|
190
|
+
.command("dep-add <pluginId>")
|
|
191
|
+
.description("Add a dependency to a plugin")
|
|
192
|
+
.requiredOption("-p, --dep-plugin <id>", "Dependency plugin ID")
|
|
193
|
+
.requiredOption("-v, --dep-version <ver>", "Required version")
|
|
194
|
+
.option(
|
|
195
|
+
"-k, --kind <kind>",
|
|
196
|
+
"Kind (required/optional/peer)",
|
|
197
|
+
DEP_KIND.REQUIRED,
|
|
198
|
+
)
|
|
199
|
+
.action((pluginId, opts, cmd) => {
|
|
200
|
+
const db = _dbFromCtx(cmd);
|
|
201
|
+
if (!db) return console.error("No database");
|
|
202
|
+
_json(
|
|
203
|
+
addDependency(db, pluginId, {
|
|
204
|
+
depPluginId: opts.depPlugin,
|
|
205
|
+
depVersion: opts.depVersion,
|
|
206
|
+
kind: opts.kind,
|
|
207
|
+
}),
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
eco
|
|
212
|
+
.command("deps <pluginId>")
|
|
213
|
+
.description("List direct dependencies of a plugin")
|
|
214
|
+
.action((pluginId, _opts, cmd) => {
|
|
215
|
+
const db = _dbFromCtx(cmd);
|
|
216
|
+
if (!db) return console.error("No database");
|
|
217
|
+
_json(listDependencies(db, pluginId));
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
eco
|
|
221
|
+
.command("resolve <pluginId>")
|
|
222
|
+
.description("Resolve dependency tree (detects conflicts + cycles)")
|
|
223
|
+
.action((pluginId, _opts, cmd) => {
|
|
224
|
+
const db = _dbFromCtx(cmd);
|
|
225
|
+
if (!db) return console.error("No database");
|
|
226
|
+
_json(resolveDependencies(db, pluginId));
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
/* ── Installation ────────────────────────────────── */
|
|
230
|
+
|
|
231
|
+
eco
|
|
232
|
+
.command("install")
|
|
233
|
+
.description("Install a plugin for a user (auto-fails on cycle/conflict)")
|
|
234
|
+
.requiredOption("-u, --user <id>", "User ID")
|
|
235
|
+
.requiredOption("-p, --plugin <id>", "Plugin ID")
|
|
236
|
+
.option(
|
|
237
|
+
"-v, --version <ver>",
|
|
238
|
+
"Specific version (defaults to plugin version)",
|
|
239
|
+
)
|
|
240
|
+
.option("--no-resolve", "Skip dependency resolution")
|
|
241
|
+
.action((opts, cmd) => {
|
|
242
|
+
const db = _dbFromCtx(cmd);
|
|
243
|
+
if (!db) return console.error("No database");
|
|
244
|
+
_json(
|
|
245
|
+
installPlugin(db, {
|
|
246
|
+
userId: opts.user,
|
|
247
|
+
pluginId: opts.plugin,
|
|
248
|
+
version: opts.version || null,
|
|
249
|
+
autoResolveDeps: opts.resolve !== false,
|
|
250
|
+
}),
|
|
251
|
+
);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
eco
|
|
255
|
+
.command("installs")
|
|
256
|
+
.description("List install records")
|
|
257
|
+
.option("-u, --user <id>", "Filter by user")
|
|
258
|
+
.option("-p, --plugin <id>", "Filter by plugin")
|
|
259
|
+
.option("-s, --status <status>", "Filter by status")
|
|
260
|
+
.option("-l, --limit <n>", "Max rows", (v) => parseInt(v, 10), 100)
|
|
261
|
+
.action((opts, cmd) => {
|
|
262
|
+
const db = _dbFromCtx(cmd);
|
|
263
|
+
if (!db) return console.error("No database");
|
|
264
|
+
_json(
|
|
265
|
+
listInstalls(db, {
|
|
266
|
+
userId: opts.user,
|
|
267
|
+
pluginId: opts.plugin,
|
|
268
|
+
status: opts.status,
|
|
269
|
+
limit: opts.limit,
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
eco
|
|
275
|
+
.command("uninstall <installId>")
|
|
276
|
+
.description("Mark an install as uninstalled")
|
|
277
|
+
.action((installId, _opts, cmd) => {
|
|
278
|
+
const db = _dbFromCtx(cmd);
|
|
279
|
+
if (!db) return console.error("No database");
|
|
280
|
+
_json(uninstallPlugin(db, installId));
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
/* ── AI review (heuristic) ───────────────────────── */
|
|
284
|
+
|
|
285
|
+
eco
|
|
286
|
+
.command("review <pluginId>")
|
|
287
|
+
.description("Run heuristic AI code review (regex rules + score)")
|
|
288
|
+
.option("-c, --code <text>", "Source code (inline)")
|
|
289
|
+
.option(
|
|
290
|
+
"-s, --strictness <level>",
|
|
291
|
+
"lenient | standard | strict",
|
|
292
|
+
"standard",
|
|
293
|
+
)
|
|
294
|
+
.action((pluginId, opts, cmd) => {
|
|
295
|
+
const db = _dbFromCtx(cmd);
|
|
296
|
+
if (!db) return console.error("No database");
|
|
297
|
+
_json(
|
|
298
|
+
aiReviewCode(db, pluginId, {
|
|
299
|
+
sourceCode: opts.code || "",
|
|
300
|
+
strictness: opts.strictness || "standard",
|
|
301
|
+
}),
|
|
302
|
+
);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
eco
|
|
306
|
+
.command("review-show <reviewId>")
|
|
307
|
+
.description("Show a single review record")
|
|
308
|
+
.action((reviewId, _opts, cmd) => {
|
|
309
|
+
const db = _dbFromCtx(cmd);
|
|
310
|
+
if (!db) return console.error("No database");
|
|
311
|
+
const r = getReview(db, reviewId);
|
|
312
|
+
if (!r) return console.error(`Review not found: ${reviewId}`);
|
|
313
|
+
_json(r);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
eco
|
|
317
|
+
.command("reviews")
|
|
318
|
+
.description("List review records")
|
|
319
|
+
.option("-p, --plugin <id>", "Filter by plugin")
|
|
320
|
+
.option("-s, --severity <level>", "Filter by max severity")
|
|
321
|
+
.option("-l, --limit <n>", "Max rows", (v) => parseInt(v, 10), 100)
|
|
322
|
+
.action((opts, cmd) => {
|
|
323
|
+
const db = _dbFromCtx(cmd);
|
|
324
|
+
if (!db) return console.error("No database");
|
|
325
|
+
_json(
|
|
326
|
+
listReviews(db, {
|
|
327
|
+
pluginId: opts.plugin,
|
|
328
|
+
severity: opts.severity,
|
|
329
|
+
limit: opts.limit,
|
|
330
|
+
}),
|
|
331
|
+
);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
/* ── Sandbox tests ───────────────────────────────── */
|
|
335
|
+
|
|
336
|
+
eco
|
|
337
|
+
.command("sandbox <pluginId>")
|
|
338
|
+
.description("Record a sandbox test result (caller pushes outcome)")
|
|
339
|
+
.option("-t, --test-suite <name>", "Test suite name", "default")
|
|
340
|
+
.option(
|
|
341
|
+
"-r, --result <value>",
|
|
342
|
+
"Result (passed/failed/timeout/resource-exceeded)",
|
|
343
|
+
SANDBOX_RESULT.PASSED,
|
|
344
|
+
)
|
|
345
|
+
.option("-m, --metrics <json>", "Metrics as JSON")
|
|
346
|
+
.option("-L, --logs <json>", "Logs array as JSON")
|
|
347
|
+
.option("-d, --duration <ms>", "Duration in ms", (v) => parseInt(v, 10))
|
|
348
|
+
.action((pluginId, opts, cmd) => {
|
|
349
|
+
const db = _dbFromCtx(cmd);
|
|
350
|
+
if (!db) return console.error("No database");
|
|
351
|
+
_json(
|
|
352
|
+
recordSandboxTest(db, pluginId, {
|
|
353
|
+
testSuite: opts.testSuite,
|
|
354
|
+
result: opts.result,
|
|
355
|
+
metrics: _parseJsonArg(opts.metrics, {}),
|
|
356
|
+
logs: _parseJsonArg(opts.logs, []),
|
|
357
|
+
durationMs: opts.duration ?? null,
|
|
358
|
+
}),
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
eco
|
|
363
|
+
.command("sandbox-show <testId>")
|
|
364
|
+
.description("Show a single sandbox test record")
|
|
365
|
+
.action((testId, _opts, cmd) => {
|
|
366
|
+
const db = _dbFromCtx(cmd);
|
|
367
|
+
if (!db) return console.error("No database");
|
|
368
|
+
const t = getSandboxTest(db, testId);
|
|
369
|
+
if (!t) return console.error(`Sandbox test not found: ${testId}`);
|
|
370
|
+
_json(t);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
eco
|
|
374
|
+
.command("sandbox-tests")
|
|
375
|
+
.description("List sandbox test records")
|
|
376
|
+
.option("-p, --plugin <id>", "Filter by plugin")
|
|
377
|
+
.option("-r, --result <value>", "Filter by result")
|
|
378
|
+
.option("-l, --limit <n>", "Max rows", (v) => parseInt(v, 10), 100)
|
|
379
|
+
.action((opts, cmd) => {
|
|
380
|
+
const db = _dbFromCtx(cmd);
|
|
381
|
+
if (!db) return console.error("No database");
|
|
382
|
+
_json(
|
|
383
|
+
listSandboxTests(db, {
|
|
384
|
+
pluginId: opts.plugin,
|
|
385
|
+
result: opts.result,
|
|
386
|
+
limit: opts.limit,
|
|
387
|
+
}),
|
|
388
|
+
);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
/* ── Publish flow ────────────────────────────────── */
|
|
392
|
+
|
|
393
|
+
eco
|
|
394
|
+
.command("submit <pluginId>")
|
|
395
|
+
.description("Submit a plugin for review (draft/rejected → reviewing)")
|
|
396
|
+
.action((pluginId, _opts, cmd) => {
|
|
397
|
+
const db = _dbFromCtx(cmd);
|
|
398
|
+
if (!db) return console.error("No database");
|
|
399
|
+
_json(submitForReview(db, pluginId));
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
eco
|
|
403
|
+
.command("approve <pluginId>")
|
|
404
|
+
.description("Approve a reviewing plugin")
|
|
405
|
+
.action((pluginId, _opts, cmd) => {
|
|
406
|
+
const db = _dbFromCtx(cmd);
|
|
407
|
+
if (!db) return console.error("No database");
|
|
408
|
+
_json(approvePlugin(db, pluginId));
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
eco
|
|
412
|
+
.command("reject <pluginId>")
|
|
413
|
+
.description(
|
|
414
|
+
"Reject a reviewing plugin (appends a rejection review record)",
|
|
415
|
+
)
|
|
416
|
+
.option("-r, --reason <text>", "Rejection reason")
|
|
417
|
+
.action((pluginId, opts, cmd) => {
|
|
418
|
+
const db = _dbFromCtx(cmd);
|
|
419
|
+
if (!db) return console.error("No database");
|
|
420
|
+
_json(rejectPlugin(db, pluginId, opts.reason || "review rejected"));
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
eco
|
|
424
|
+
.command("publish <pluginId>")
|
|
425
|
+
.description("Publish an approved plugin (computes sourceHash)")
|
|
426
|
+
.option("-c, --code <text>", "Source code for hashing")
|
|
427
|
+
.option("-l, --changelog <text>", "Changelog note")
|
|
428
|
+
.action((pluginId, opts, cmd) => {
|
|
429
|
+
const db = _dbFromCtx(cmd);
|
|
430
|
+
if (!db) return console.error("No database");
|
|
431
|
+
_json(
|
|
432
|
+
publishPlugin(db, pluginId, {
|
|
433
|
+
sourceCode: opts.code || "",
|
|
434
|
+
changelog: opts.changelog || "",
|
|
435
|
+
}),
|
|
436
|
+
);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
/* ── Revenue ─────────────────────────────────────── */
|
|
440
|
+
|
|
441
|
+
eco
|
|
442
|
+
.command("rev-record")
|
|
443
|
+
.description("Record a revenue event (splits via developerShareRatio)")
|
|
444
|
+
.requiredOption("-d, --developer <id>", "Developer ID")
|
|
445
|
+
.requiredOption("-p, --plugin <id>", "Plugin ID")
|
|
446
|
+
.requiredOption(
|
|
447
|
+
"-t, --type <type>",
|
|
448
|
+
"Type (download/subscription/donation/premium)",
|
|
449
|
+
)
|
|
450
|
+
.requiredOption("-a, --amount <n>", "Amount (gross)", (v) => parseFloat(v))
|
|
451
|
+
.option("-u, --user <id>", "User ID")
|
|
452
|
+
.option("-r, --ratio <n>", "Developer share ratio (0-1)", (v) =>
|
|
453
|
+
parseFloat(v),
|
|
454
|
+
)
|
|
455
|
+
.action((opts, cmd) => {
|
|
456
|
+
const db = _dbFromCtx(cmd);
|
|
457
|
+
if (!db) return console.error("No database");
|
|
458
|
+
const rec = {
|
|
459
|
+
developerId: opts.developer,
|
|
460
|
+
pluginId: opts.plugin,
|
|
461
|
+
userId: opts.user || null,
|
|
462
|
+
type: opts.type,
|
|
463
|
+
amount: opts.amount,
|
|
464
|
+
};
|
|
465
|
+
if (opts.ratio != null) rec.developerShareRatio = opts.ratio;
|
|
466
|
+
_json(recordRevenue(db, rec));
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
eco
|
|
470
|
+
.command("revenue <developerId>")
|
|
471
|
+
.description("Aggregate a developer's revenue")
|
|
472
|
+
.option("-p, --plugin <id>", "Filter by plugin")
|
|
473
|
+
.option("--from <ts>", "Unix ms >= from", (v) => parseInt(v, 10))
|
|
474
|
+
.option("--to <ts>", "Unix ms <= to", (v) => parseInt(v, 10))
|
|
475
|
+
.action((developerId, opts, cmd) => {
|
|
476
|
+
const db = _dbFromCtx(cmd);
|
|
477
|
+
if (!db) return console.error("No database");
|
|
478
|
+
_json(
|
|
479
|
+
getDeveloperRevenue(db, developerId, {
|
|
480
|
+
from: opts.from ?? null,
|
|
481
|
+
to: opts.to ?? null,
|
|
482
|
+
pluginId: opts.plugin || null,
|
|
483
|
+
}),
|
|
484
|
+
);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
/* ── Recommendation ──────────────────────────────── */
|
|
488
|
+
|
|
489
|
+
eco
|
|
490
|
+
.command("recommend")
|
|
491
|
+
.description("Heuristic plugin recommendations for a user")
|
|
492
|
+
.requiredOption("-u, --user <id>", "User ID")
|
|
493
|
+
.option("-c, --category <cat>", "Restrict to a category")
|
|
494
|
+
.option("-l, --limit <n>", "Max recommendations", (v) => parseInt(v, 10))
|
|
495
|
+
.action((opts, cmd) => {
|
|
496
|
+
const db = _dbFromCtx(cmd);
|
|
497
|
+
if (!db) return console.error("No database");
|
|
498
|
+
const arg = { userId: opts.user };
|
|
499
|
+
if (opts.category) arg.category = opts.category;
|
|
500
|
+
if (opts.limit != null) arg.limit = opts.limit;
|
|
501
|
+
_json(recommend(db, arg));
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
/* ── Stats ───────────────────────────────────────── */
|
|
505
|
+
|
|
506
|
+
eco
|
|
507
|
+
.command("stats")
|
|
508
|
+
.description("Show ecosystem stats")
|
|
509
|
+
.action((_opts, cmd) => {
|
|
510
|
+
const db = _dbFromCtx(cmd);
|
|
511
|
+
if (!db) return console.error("No database");
|
|
512
|
+
_json(getStats(db));
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
program.addCommand(eco);
|
|
516
|
+
return eco;
|
|
517
|
+
}
|