chainlesschain 0.49.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/ipfs.js +392 -0
- package/src/commands/multimodal.js +404 -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 +27 -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/ipfs-storage.js +575 -0
- package/src/lib/multimodal.js +39 -12
- 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,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cc ipfs` — CLI surface for Phase 17 IPFS 去中心化存储.
|
|
3
|
+
*
|
|
4
|
+
* Exposes node lifecycle, content add/get/list, pin/unpin, storage
|
|
5
|
+
* stats, garbage collection, quota, and knowledge attachment linkage.
|
|
6
|
+
*
|
|
7
|
+
* Note: CIDs are simulated sha256 hashes; real libp2p/Helia is not
|
|
8
|
+
* ported. Content is local-only and encrypted with AES-256-GCM when
|
|
9
|
+
* `--encrypt` is set.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from "fs";
|
|
13
|
+
import { Command } from "commander";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
NODE_MODE,
|
|
17
|
+
NODE_STATUS,
|
|
18
|
+
ensureIpfsTables,
|
|
19
|
+
startNode,
|
|
20
|
+
stopNode,
|
|
21
|
+
getNodeStatus,
|
|
22
|
+
setMode,
|
|
23
|
+
addContent,
|
|
24
|
+
getContent,
|
|
25
|
+
hasContent,
|
|
26
|
+
listContent,
|
|
27
|
+
pin,
|
|
28
|
+
unpin,
|
|
29
|
+
listPins,
|
|
30
|
+
getStorageStats,
|
|
31
|
+
garbageCollect,
|
|
32
|
+
setQuota,
|
|
33
|
+
addKnowledgeAttachment,
|
|
34
|
+
getKnowledgeAttachments,
|
|
35
|
+
} from "../lib/ipfs-storage.js";
|
|
36
|
+
|
|
37
|
+
function _dbFromCtx(cmd) {
|
|
38
|
+
const root = cmd?.parent?.parent ?? cmd?.parent;
|
|
39
|
+
return root?._db;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function _parseJson(raw) {
|
|
43
|
+
if (raw == null) return null;
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(raw);
|
|
46
|
+
} catch (_e) {
|
|
47
|
+
return raw;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function _readContent(opts) {
|
|
52
|
+
if (opts.file) return fs.readFileSync(opts.file);
|
|
53
|
+
if (opts.text != null) return opts.text;
|
|
54
|
+
if (opts.json != null) return opts.json;
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function registerIpfsCommand(program) {
|
|
59
|
+
const ipfs = new Command("ipfs")
|
|
60
|
+
.description("IPFS decentralized storage (Phase 17)")
|
|
61
|
+
.hook("preAction", (thisCmd) => {
|
|
62
|
+
const db = _dbFromCtx(thisCmd);
|
|
63
|
+
if (db) ensureIpfsTables(db);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/* ── Catalogs ─────────────────────────────────────── */
|
|
67
|
+
|
|
68
|
+
ipfs
|
|
69
|
+
.command("modes")
|
|
70
|
+
.description("List node modes (embedded/external)")
|
|
71
|
+
.option("--json", "JSON output")
|
|
72
|
+
.action((opts) => {
|
|
73
|
+
const modes = Object.values(NODE_MODE);
|
|
74
|
+
if (opts.json) return console.log(JSON.stringify(modes, null, 2));
|
|
75
|
+
for (const m of modes) console.log(` ${m}`);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
ipfs
|
|
79
|
+
.command("statuses")
|
|
80
|
+
.description("List node statuses")
|
|
81
|
+
.option("--json", "JSON output")
|
|
82
|
+
.action((opts) => {
|
|
83
|
+
const statuses = Object.values(NODE_STATUS);
|
|
84
|
+
if (opts.json) return console.log(JSON.stringify(statuses, null, 2));
|
|
85
|
+
for (const s of statuses) console.log(` ${s}`);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
/* ── Node lifecycle ───────────────────────────────── */
|
|
89
|
+
|
|
90
|
+
ipfs
|
|
91
|
+
.command("node-start")
|
|
92
|
+
.description("Start the IPFS node")
|
|
93
|
+
.option("-m, --mode <embedded|external>", "Node mode")
|
|
94
|
+
.option("--json", "JSON output")
|
|
95
|
+
.action((opts) => {
|
|
96
|
+
const db = _dbFromCtx(ipfs);
|
|
97
|
+
const r = startNode(db, { mode: opts.mode });
|
|
98
|
+
if (opts.json) return console.log(JSON.stringify(r, null, 2));
|
|
99
|
+
if (!r.started) return console.log(`Failed to start node: ${r.reason}`);
|
|
100
|
+
console.log(`Node started (mode=${r.mode}, peerId=${r.peerId})`);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
ipfs
|
|
104
|
+
.command("node-stop")
|
|
105
|
+
.description("Stop the IPFS node")
|
|
106
|
+
.option("--json", "JSON output")
|
|
107
|
+
.action((opts) => {
|
|
108
|
+
const db = _dbFromCtx(ipfs);
|
|
109
|
+
const r = stopNode(db);
|
|
110
|
+
if (opts.json) return console.log(JSON.stringify(r, null, 2));
|
|
111
|
+
if (!r.stopped) return console.log(`Failed to stop node: ${r.reason}`);
|
|
112
|
+
console.log("Node stopped");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
ipfs
|
|
116
|
+
.command("node-status")
|
|
117
|
+
.description("Show node status and uptime")
|
|
118
|
+
.option("--json", "JSON output")
|
|
119
|
+
.action((opts) => {
|
|
120
|
+
const r = getNodeStatus();
|
|
121
|
+
if (opts.json) return console.log(JSON.stringify(r, null, 2));
|
|
122
|
+
console.log(`Status: ${r.status}`);
|
|
123
|
+
console.log(`Mode: ${r.mode}`);
|
|
124
|
+
console.log(`PeerId: ${r.peerId || "-"}`);
|
|
125
|
+
console.log(`Uptime: ${r.uptimeMs}ms`);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
ipfs
|
|
129
|
+
.command("set-mode <mode>")
|
|
130
|
+
.description("Set node mode (stop node first)")
|
|
131
|
+
.option("--json", "JSON output")
|
|
132
|
+
.action((mode, opts) => {
|
|
133
|
+
const db = _dbFromCtx(ipfs);
|
|
134
|
+
const r = setMode(db, mode);
|
|
135
|
+
if (opts.json) return console.log(JSON.stringify(r, null, 2));
|
|
136
|
+
if (!r.set) return console.log(`Failed: ${r.reason}`);
|
|
137
|
+
console.log(`Mode set to ${r.mode}`);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
/* ── Content operations ───────────────────────────── */
|
|
141
|
+
|
|
142
|
+
ipfs
|
|
143
|
+
.command("add")
|
|
144
|
+
.description("Add content to the store")
|
|
145
|
+
.option("-f, --file <path>", "Read content from file")
|
|
146
|
+
.option("-t, --text <text>", "Inline text content")
|
|
147
|
+
.option("--json-body <json>", "Inline JSON body (stored as JSON)")
|
|
148
|
+
.option("--filename <name>", "Filename metadata")
|
|
149
|
+
.option("--mime <type>", "MIME type")
|
|
150
|
+
.option("--encrypt", "Encrypt payload with AES-256-GCM")
|
|
151
|
+
.option("--pin", "Pin after add")
|
|
152
|
+
.option("--knowledge <id>", "Associate with knowledge item")
|
|
153
|
+
.option("--meta <json>", "Metadata JSON")
|
|
154
|
+
.option("--json", "JSON output")
|
|
155
|
+
.action((opts) => {
|
|
156
|
+
const db = _dbFromCtx(ipfs);
|
|
157
|
+
const content = _readContent({
|
|
158
|
+
file: opts.file,
|
|
159
|
+
text: opts.text,
|
|
160
|
+
json: opts.jsonBody,
|
|
161
|
+
});
|
|
162
|
+
if (content == null)
|
|
163
|
+
return console.log(
|
|
164
|
+
"Must provide one of --file, --text, or --json-body",
|
|
165
|
+
);
|
|
166
|
+
const r = addContent(db, content, {
|
|
167
|
+
filename: opts.filename,
|
|
168
|
+
mimeType: opts.mime,
|
|
169
|
+
encrypt: Boolean(opts.encrypt),
|
|
170
|
+
pin: Boolean(opts.pin),
|
|
171
|
+
knowledgeId: opts.knowledge,
|
|
172
|
+
metadata: _parseJson(opts.meta),
|
|
173
|
+
});
|
|
174
|
+
if (opts.json) return console.log(JSON.stringify(r, null, 2));
|
|
175
|
+
if (!r.added) return console.log(`Failed: ${r.reason}`);
|
|
176
|
+
console.log(
|
|
177
|
+
`Added cid=${r.cid} size=${r.size}${r.duplicate ? " (duplicate)" : ""}${r.pinned ? " [pinned]" : ""}${r.encrypted ? " [encrypted]" : ""}`,
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
ipfs
|
|
182
|
+
.command("get <cid>")
|
|
183
|
+
.description("Get content by CID")
|
|
184
|
+
.option("-o, --out <path>", "Write to file")
|
|
185
|
+
.option("-s, --string", "Decode as UTF-8 string")
|
|
186
|
+
.option("--json", "JSON output (includes base64 body)")
|
|
187
|
+
.action((cid, opts) => {
|
|
188
|
+
const db = _dbFromCtx(ipfs);
|
|
189
|
+
const r = getContent(db, cid, { asString: Boolean(opts.string) });
|
|
190
|
+
if (!r) return console.log("Not found (node running?)");
|
|
191
|
+
if (opts.out) {
|
|
192
|
+
const buf = Buffer.from(r.base64, "base64");
|
|
193
|
+
fs.writeFileSync(opts.out, buf);
|
|
194
|
+
console.log(`Wrote ${buf.length} bytes to ${opts.out}`);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (opts.json) return console.log(JSON.stringify(r, null, 2));
|
|
198
|
+
console.log(`CID: ${r.cid}`);
|
|
199
|
+
console.log(`Size: ${r.size}`);
|
|
200
|
+
console.log(`Filename: ${r.filename || "-"}`);
|
|
201
|
+
console.log(`MIME: ${r.mimeType || "-"}`);
|
|
202
|
+
console.log(`Pinned: ${r.pinned}`);
|
|
203
|
+
console.log(`Encrypted:${r.encrypted}`);
|
|
204
|
+
if (opts.string)
|
|
205
|
+
console.log(`\n--- content ---\n${r.content.toString()}`);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
ipfs
|
|
209
|
+
.command("show <cid>")
|
|
210
|
+
.description("Show content metadata (no payload)")
|
|
211
|
+
.option("--json", "JSON output")
|
|
212
|
+
.action((cid, opts) => {
|
|
213
|
+
const db = _dbFromCtx(ipfs);
|
|
214
|
+
const exists = hasContent(db, cid);
|
|
215
|
+
if (!exists)
|
|
216
|
+
return opts.json
|
|
217
|
+
? console.log(JSON.stringify({ exists: false }, null, 2))
|
|
218
|
+
: console.log("Not found");
|
|
219
|
+
const rows = listContent(db, { limit: 10000 });
|
|
220
|
+
const r = rows.find((c) => c.cid === cid);
|
|
221
|
+
if (opts.json) return console.log(JSON.stringify(r, null, 2));
|
|
222
|
+
console.log(`CID: ${r.cid}`);
|
|
223
|
+
console.log(`Size: ${r.size}`);
|
|
224
|
+
console.log(`Filename: ${r.filename || "-"}`);
|
|
225
|
+
console.log(`MIME: ${r.mime_type || "-"}`);
|
|
226
|
+
console.log(`Pinned: ${Boolean(r.pinned)}`);
|
|
227
|
+
console.log(`Encrypted: ${Boolean(r.encrypted)}`);
|
|
228
|
+
console.log(`Knowledge: ${r.knowledge_id || "-"}`);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
ipfs
|
|
232
|
+
.command("list")
|
|
233
|
+
.description("List content entries")
|
|
234
|
+
.option("-p, --pinned", "Only pinned")
|
|
235
|
+
.option("-u, --unpinned", "Only unpinned")
|
|
236
|
+
.option("-k, --knowledge <id>", "Filter by knowledge id")
|
|
237
|
+
.option("-n, --limit <n>", "Limit", (v) => parseInt(v, 10), 50)
|
|
238
|
+
.option("--json", "JSON output")
|
|
239
|
+
.action((opts) => {
|
|
240
|
+
const db = _dbFromCtx(ipfs);
|
|
241
|
+
let pinnedFilter;
|
|
242
|
+
if (opts.pinned) pinnedFilter = true;
|
|
243
|
+
else if (opts.unpinned) pinnedFilter = false;
|
|
244
|
+
const rows = listContent(db, {
|
|
245
|
+
pinned: pinnedFilter,
|
|
246
|
+
knowledgeId: opts.knowledge,
|
|
247
|
+
limit: opts.limit,
|
|
248
|
+
});
|
|
249
|
+
if (opts.json) return console.log(JSON.stringify(rows, null, 2));
|
|
250
|
+
if (rows.length === 0) return console.log("No content");
|
|
251
|
+
for (const r of rows) {
|
|
252
|
+
const flags =
|
|
253
|
+
`${r.pinned ? "P" : "-"}${r.encrypted ? "E" : "-"}` +
|
|
254
|
+
(r.knowledge_id ? "K" : "-");
|
|
255
|
+
console.log(
|
|
256
|
+
` ${r.cid.slice(0, 16)}… ${flags} ${String(r.size).padStart(7)} ${r.filename || "(unnamed)"}`,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
/* ── Pin management ───────────────────────────────── */
|
|
262
|
+
|
|
263
|
+
ipfs
|
|
264
|
+
.command("pin <cid>")
|
|
265
|
+
.description("Pin a CID (quota-checked)")
|
|
266
|
+
.option("--json", "JSON output")
|
|
267
|
+
.action((cid, opts) => {
|
|
268
|
+
const db = _dbFromCtx(ipfs);
|
|
269
|
+
const r = pin(db, cid);
|
|
270
|
+
if (opts.json) return console.log(JSON.stringify(r, null, 2));
|
|
271
|
+
if (!r.pinned) return console.log(`Failed: ${r.reason}`);
|
|
272
|
+
console.log(`Pinned ${r.cid}`);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
ipfs
|
|
276
|
+
.command("unpin <cid>")
|
|
277
|
+
.description("Unpin a CID")
|
|
278
|
+
.option("--json", "JSON output")
|
|
279
|
+
.action((cid, opts) => {
|
|
280
|
+
const db = _dbFromCtx(ipfs);
|
|
281
|
+
const r = unpin(db, cid);
|
|
282
|
+
if (opts.json) return console.log(JSON.stringify(r, null, 2));
|
|
283
|
+
if (!r.unpinned) return console.log(`Failed: ${r.reason}`);
|
|
284
|
+
console.log(`Unpinned ${r.cid}`);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
ipfs
|
|
288
|
+
.command("pins")
|
|
289
|
+
.description("List pinned content")
|
|
290
|
+
.option("-n, --limit <n>", "Limit", (v) => parseInt(v, 10), 50)
|
|
291
|
+
.option(
|
|
292
|
+
"-s, --sort <field>",
|
|
293
|
+
"Sort by (created_at|size|filename)",
|
|
294
|
+
"created_at",
|
|
295
|
+
)
|
|
296
|
+
.option("--json", "JSON output")
|
|
297
|
+
.action((opts) => {
|
|
298
|
+
const db = _dbFromCtx(ipfs);
|
|
299
|
+
const rows = listPins(db, { limit: opts.limit, sortBy: opts.sort });
|
|
300
|
+
if (opts.json) return console.log(JSON.stringify(rows, null, 2));
|
|
301
|
+
if (rows.length === 0) return console.log("No pinned content");
|
|
302
|
+
for (const r of rows)
|
|
303
|
+
console.log(
|
|
304
|
+
` ${r.cid.slice(0, 16)}… ${String(r.size).padStart(7)} ${r.filename || "(unnamed)"}`,
|
|
305
|
+
);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
/* ── Stats / GC / quota ───────────────────────────── */
|
|
309
|
+
|
|
310
|
+
ipfs
|
|
311
|
+
.command("stats")
|
|
312
|
+
.description("Storage statistics")
|
|
313
|
+
.option("--json", "JSON output")
|
|
314
|
+
.action((opts) => {
|
|
315
|
+
const r = getStorageStats();
|
|
316
|
+
if (opts.json) return console.log(JSON.stringify(r, null, 2));
|
|
317
|
+
console.log(`Total content: ${r.totalContent}`);
|
|
318
|
+
console.log(`Pinned: ${r.pinnedCount}`);
|
|
319
|
+
console.log(`Encrypted: ${r.encryptedCount}`);
|
|
320
|
+
console.log(`Total bytes: ${r.totalBytes}`);
|
|
321
|
+
console.log(`Pinned bytes: ${r.pinnedBytes}`);
|
|
322
|
+
console.log(`Quota: ${r.quotaBytes}`);
|
|
323
|
+
console.log(`Usage: ${r.usagePercent}%`);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
ipfs
|
|
327
|
+
.command("gc")
|
|
328
|
+
.description("Garbage-collect unpinned content")
|
|
329
|
+
.option("--json", "JSON output")
|
|
330
|
+
.action((opts) => {
|
|
331
|
+
const db = _dbFromCtx(ipfs);
|
|
332
|
+
const r = garbageCollect(db);
|
|
333
|
+
if (opts.json) return console.log(JSON.stringify(r, null, 2));
|
|
334
|
+
console.log(
|
|
335
|
+
`Removed ${r.removed} entries, freed ${r.freedBytes} bytes (${r.before} → ${r.after})`,
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
ipfs
|
|
340
|
+
.command("set-quota <bytes>")
|
|
341
|
+
.description("Set storage quota (bytes)")
|
|
342
|
+
.option("--json", "JSON output")
|
|
343
|
+
.action((bytes, opts) => {
|
|
344
|
+
const db = _dbFromCtx(ipfs);
|
|
345
|
+
const r = setQuota(db, bytes);
|
|
346
|
+
if (opts.json) return console.log(JSON.stringify(r, null, 2));
|
|
347
|
+
if (!r.set) return console.log(`Failed: ${r.reason}`);
|
|
348
|
+
console.log(`Quota set to ${r.quotaBytes} bytes`);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
/* ── Knowledge attachments ────────────────────────── */
|
|
352
|
+
|
|
353
|
+
ipfs
|
|
354
|
+
.command("attach <knowledgeId>")
|
|
355
|
+
.description("Attach content to a knowledge item (auto-pinned)")
|
|
356
|
+
.option("-f, --file <path>", "Read content from file")
|
|
357
|
+
.option("-t, --text <text>", "Inline text content")
|
|
358
|
+
.option("--filename <name>", "Filename metadata")
|
|
359
|
+
.option("--mime <type>", "MIME type")
|
|
360
|
+
.option("--json", "JSON output")
|
|
361
|
+
.action((knowledgeId, opts) => {
|
|
362
|
+
const db = _dbFromCtx(ipfs);
|
|
363
|
+
const content = _readContent({ file: opts.file, text: opts.text });
|
|
364
|
+
if (content == null) return console.log("Must provide --file or --text");
|
|
365
|
+
const r = addKnowledgeAttachment(db, knowledgeId, content, {
|
|
366
|
+
filename: opts.filename,
|
|
367
|
+
mimeType: opts.mime,
|
|
368
|
+
});
|
|
369
|
+
if (opts.json) return console.log(JSON.stringify(r, null, 2));
|
|
370
|
+
if (!r.added) return console.log(`Failed: ${r.reason}`);
|
|
371
|
+
console.log(
|
|
372
|
+
`Attached cid=${r.cid} size=${r.size} to knowledge=${knowledgeId}`,
|
|
373
|
+
);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
ipfs
|
|
377
|
+
.command("attachments <knowledgeId>")
|
|
378
|
+
.description("List attachments for a knowledge item")
|
|
379
|
+
.option("--json", "JSON output")
|
|
380
|
+
.action((knowledgeId, opts) => {
|
|
381
|
+
const db = _dbFromCtx(ipfs);
|
|
382
|
+
const rows = getKnowledgeAttachments(db, knowledgeId);
|
|
383
|
+
if (opts.json) return console.log(JSON.stringify(rows, null, 2));
|
|
384
|
+
if (rows.length === 0) return console.log("No attachments");
|
|
385
|
+
for (const r of rows)
|
|
386
|
+
console.log(
|
|
387
|
+
` ${r.cid.slice(0, 16)}… ${String(r.size).padStart(7)} ${r.filename || "(unnamed)"}`,
|
|
388
|
+
);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
program.addCommand(ipfs);
|
|
392
|
+
}
|